diff --git a/openspec/specs/modal-height-constraint/spec.md b/openspec/specs/modal-height-constraint/spec.md new file mode 100644 index 0000000..2a76521 --- /dev/null +++ b/openspec/specs/modal-height-constraint/spec.md @@ -0,0 +1,49 @@ +## Purpose + +定义 Modal 弹窗的高度约束机制,确保弹窗在各种视窗尺寸下完整显示,并提供良好的内容滚动体验。 + +## Requirements + +### Requirement: Modal 最大高度限制 + +Modal 弹窗 SHALL 设置最大高度,确保弹窗始终在视窗内完整显示。 + +#### Scenario: 视窗高度充足 +- **WHEN** 浏览器视窗高度大于弹窗内容高度 +- **THEN** 弹窗高度自适应内容,不显示滚动条 + +#### Scenario: 视窗高度不足 +- **WHEN** 浏览器视窗高度小于弹窗内容高度 +- **THEN** 弹窗最大高度为 `calc(100dvh - 48px)`(兼容旧浏览器使用 `calc(100vh - 48px)` fallback) +- **THEN** 弹窗上下各预留 24px 边距 + +### Requirement: Modal 标题栏固定可见 + +Modal 标题栏(`.modal-header`)SHALL 始终可见,不被内容挤压或遮挡。 + +#### Scenario: 内容超出滚动 +- **WHEN** Modal body 内容超出高度限制 +- **THEN** 标题栏固定在弹窗顶部,高度不变 +- **THEN** 标题栏不参与滚动 + +### Requirement: Modal 底部栏固定可见 + +Modal 底部栏(`.modal-footer`,如存在)SHALL 始终可见,不被内容挤压或遮挡。 + +#### Scenario: 内容超出滚动且有 footer +- **WHEN** Modal body 内容超出高度限制且存在 footer +- **THEN** footer 固定在弹窗底部,高度不变 +- **THEN** footer 不参与滚动 + +### Requirement: Modal body 内容可滚动 + +Modal body(`.modal-body`)SHALL 在内容超出时支持滚动。 + +#### Scenario: 内容超出时滚动 +- **WHEN** Modal body 内容高度超过可用空间 +- **THEN** body 显示纵向滚动条 +- **THEN** 用户可滚动查看全部内容 + +#### Scenario: 内容未超出时不滚动 +- **WHEN** Modal body 内容高度未超过可用空间 +- **THEN** body 不显示滚动条 diff --git a/src/styles/components/modal/_index.scss b/src/styles/components/modal/_index.scss index aeb5871..07ace80 100644 --- a/src/styles/components/modal/_index.scss +++ b/src/styles/components/modal/_index.scss @@ -25,6 +25,10 @@ box-shadow: 0 8px 32px rgba(15, 23, 42, 0.16); width: 420px; max-width: 90vw; + max-height: calc(100vh - 48px); + max-height: calc(100dvh - 48px); + display: flex; + flex-direction: column; animation: modal-in 0.2s ease-out; } @@ -47,6 +51,7 @@ justify-content: space-between; padding: 16px 20px; border-bottom: 1px solid var(--color-border-2); + flex-shrink: 0; } .modal-title { @@ -79,6 +84,9 @@ font-size: $font-size-base; color: var(--color-text-2); line-height: 1.6; + flex: 1; + min-height: 0; + overflow-y: auto; } // Element: footer @@ -89,4 +97,5 @@ gap: 8px; padding: 12px 20px; border-top: 1px solid var(--color-border-2); + flex-shrink: 0; }