Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -817,7 +817,7 @@ export function ComponentDetailPage({
</DialogHeading>
<DialogClose />
</DialogHeader>
<DialogBody className="component-dialog-example__body">{renderDialogConfigurationContent()}</DialogBody>
<DialogBody>{renderDialogConfigurationContent()}</DialogBody>
<DialogFooter appearance="floating">
<Button onClick={closePreview} variant="fill">
{t("components.preview.modalCancel")}
Expand Down
5 changes: 0 additions & 5 deletions design-system/apps/design-lab/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -3744,11 +3744,6 @@ body,
min-width: 0;
}

.component-dialog-example__body {
padding-block-end: var(--openbitfun-overlay-dialog-footer-content-inset);
scroll-padding-block-end: var(--openbitfun-overlay-dialog-footer-content-inset);
}

.component-dialog-example__hint {
margin: 0;
color: var(--openbitfun-color-content-muted);
Expand Down
8 changes: 4 additions & 4 deletions design-system/packages/design-tokens/src/system.tokens.json
Original file line number Diff line number Diff line change
Expand Up @@ -263,10 +263,10 @@
},
"dialog": {
"fontFamily": { "$value": "{font.family.sans}" },
"fontSize": { "$value": "{font.size.3xl}" },
"fontWeight": { "$value": "{font.weight.semibold}" },
"lineHeight": { "$value": "{lineHeight.tight}" },
"letterSpacing": { "$value": "{letterSpacing.tight}" }
"fontSize": { "$value": "{font.size.3xl-plus}" },
"fontWeight": { "$value": "{font.weight.bold}" },
"lineHeight": { "$value": 1.2083333333 },
"letterSpacing": { "$value": "{letterSpacing.normal}" }
},
"display": {
"fontFamily": { "$value": "{font.family.sans}" },
Expand Down
2 changes: 2 additions & 0 deletions design-system/packages/ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -415,3 +415,5 @@ caption color directly, avoiding a second opacity multiplier. The nested-menu
Pattern includes a scrolling toggle for keyboard and submenu verification.

Compact tabs use `size="sm"` (30px, 14px icons, 4px icon gap); standard tabs retain 40px and 16px icons. Tabs share the outline-button surface contract and keep selection separate from pointer press. `SegmentedControl size="md"` uses a borderless 36px bar with 30px segments, 3px inset, 4px gaps and 12px segment padding. The default `sm` bar keeps its 28px outer height; separate pills retain their existing heights. Mobile controls own their touch geometry independently.

Dialog titles use 24px bold type with their own 29px line box and normal tracking. `DialogHeader` and `DialogFooter` omit separators by default; pass `separator` for a deliberate divider. A direct `DialogBody` sibling of `DialogFooter appearance="floating"` owns the trailing scroll inset automatically. The floating footer provides the 68px centered action area and a masked blur/gradient using the current theme surface; reduced transparency and forced colors use an opaque fallback. Keep scrollable form content inside `DialogBody` instead of adding a second viewport with independent footer spacing.
12 changes: 10 additions & 2 deletions design-system/packages/ui/src/components/Dialog/Dialog.meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@ import type { ComponentMeta } from "../../registry.types";

export const dialogMeta = {
category: "feedback",
description: "A compound dialog with attached or opaque floating footer anatomy on the shared overlay kernel.",
description: "A compound dialog with attached or frosted floating footer anatomy on the shared overlay kernel.",
maturity: "stable",
name: "Dialog",
props: [
{ name: "open", type: "boolean" },
{ name: "onOpenChange", type: "(open: false, reason: DialogCloseReason) => void" },
{ defaultValue: "md", name: "size", type: "sm | md | lg | xl | 2xl" },
{ defaultValue: "false", name: "DialogHeader.separator", type: "boolean" },
{ defaultValue: "false", name: "DialogFooter.separator", type: "boolean" },
{ defaultValue: "true", name: "closeOnEscape", type: "boolean" },
{ defaultValue: "true", name: "closeOnPointerOutside", type: "boolean" },
],
states: ["default", "open", "alert", "scrolling", "opaque-floating-footer"],
states: ["default", "open", "alert", "scrolling", "floating-footer"],
tokens: [
"color.overlay.scrim",
"color.surface.raised",
Expand All @@ -27,12 +29,18 @@ export const dialogMeta = {
"overlay.dialog.footerPaddingBlockStart",
"overlay.dialog.footerPaddingBlockEnd",
"overlay.dialog.footerActionMinWidth",
"overlay.dialog.footerHeight",
"overlay.dialog.footerFadeExtent",
"overlay.dialog.footerContentInset",
"overlay.dialog.footerBlur",
"shadow.overlay",
"motion.distance.sm",
"type.support",
"type.heading.dialog.fontFamily",
"type.heading.dialog.fontSize",
"type.heading.dialog.fontWeight",
"type.heading.dialog.lineHeight",
"type.heading.dialog.letterSpacing",
],
} as const satisfies ComponentMeta;

Expand Down
44 changes: 42 additions & 2 deletions design-system/packages/ui/src/components/Dialog/Dialog.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@
gap: var(--openbitfun-space-3);
padding-block: var(--openbitfun-overlay-dialog-header-padding-block-start) var(--openbitfun-overlay-dialog-header-padding-block-end);
padding-inline: var(--openbitfun-overlay-dialog-header-padding-inline);
}

.header[data-separator="true"] {
border-block-end: var(--openbitfun-border-width-default) solid var(--openbitfun-color-border-subtle);
}

Expand All @@ -95,7 +98,7 @@
font-family: var(--openbitfun-type-heading-dialog-font-family);
font-size: var(--openbitfun-type-heading-dialog-font-size);
font-weight: var(--openbitfun-type-heading-dialog-font-weight);
line-height: var(--openbitfun-type-body-sm-line-height);
line-height: var(--openbitfun-type-heading-dialog-line-height);
letter-spacing: var(--openbitfun-type-heading-dialog-letter-spacing);
white-space: nowrap;
}
Expand Down Expand Up @@ -133,6 +136,15 @@
padding: var(--openbitfun-overlay-dialog-content-padding-lg);
}

.header + .body[data-inset="standard"] {
padding-block-start: 0;
}

.surface:has(> .footer[data-appearance="floating"]) > .body {
padding-block-end: var(--openbitfun-overlay-dialog-footer-content-inset);
scroll-padding-block-end: var(--openbitfun-overlay-dialog-footer-content-inset);
}

.footer {
display: flex;
flex: 0 0 auto;
Expand All @@ -141,6 +153,9 @@
gap: var(--openbitfun-space-2);
padding-block: var(--openbitfun-overlay-dialog-footer-padding-block-start) var(--openbitfun-overlay-dialog-footer-padding-block-end);
padding-inline: var(--openbitfun-overlay-dialog-footer-padding-inline);
}

.footer[data-separator="true"] {
border-block-start: var(--openbitfun-border-width-default) solid var(--openbitfun-color-border-subtle);
}

Expand All @@ -149,11 +164,26 @@
z-index: var(--openbitfun-layer-content);
inset-inline: 0;
inset-block-end: 0;
min-block-size: var(--openbitfun-overlay-dialog-footer-height);
justify-content: center;
background: var(--openbitfun-color-surface-raised);
isolation: isolate;
background: transparent;
pointer-events: auto;
}

.footer[data-appearance="floating"]::before {
position: absolute;
z-index: -1;
inset: 0;
inset-block-start: calc(-1 * var(--openbitfun-overlay-dialog-footer-fade-extent));
background: linear-gradient(to bottom, transparent, var(--openbitfun-color-surface-raised));
-webkit-backdrop-filter: var(--openbitfun-overlay-dialog-footer-blur);
backdrop-filter: var(--openbitfun-overlay-dialog-footer-blur);
mask-image: linear-gradient(to bottom, transparent, var(--openbitfun-color-surface-raised) var(--openbitfun-overlay-dialog-footer-fade-extent));
content: "";
pointer-events: none;
}

.footer[data-appearance="floating"] > * {
min-inline-size: var(--openbitfun-overlay-dialog-footer-action-min-width);
pointer-events: auto;
Expand All @@ -179,4 +209,14 @@
animation-duration: 1ms;
}
}

@media (forced-colors: active), (prefers-reduced-transparency: reduce) {
.footer[data-appearance="floating"]::before {
inset-block-start: 0;
background: var(--openbitfun-color-surface-raised);
-webkit-backdrop-filter: none;
backdrop-filter: none;
mask-image: none;
}
}
}
14 changes: 10 additions & 4 deletions design-system/packages/ui/src/components/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,13 @@ export const Sheet = forwardRef<HTMLDivElement, SheetProps>(function Sheet({
);
});

export const DialogHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
function DialogHeader({ className, ...props }, ref) {
return <header {...props} className={classNames(styles.header, className)} data-openbitfun-part="header" ref={ref} />;
export interface DialogHeaderProps extends HTMLAttributes<HTMLDivElement> {
separator?: boolean;
}

export const DialogHeader = forwardRef<HTMLDivElement, DialogHeaderProps>(
function DialogHeader({ className, separator = false, ...props }, ref) {
return <header {...props} className={classNames(styles.header, className)} data-openbitfun-part="header" data-separator={separator} ref={ref} />;
},
);

Expand Down Expand Up @@ -291,15 +295,17 @@ export const DialogBody = forwardRef<HTMLDivElement, DialogBodyProps>(

export interface DialogFooterProps extends HTMLAttributes<HTMLElement> {
appearance?: DialogFooterAppearance;
separator?: boolean;
}

export const DialogFooter = forwardRef<HTMLElement, DialogFooterProps>(
function DialogFooter({ appearance = "attached", className, ...props }, ref) {
function DialogFooter({ appearance = "attached", className, separator = false, ...props }, ref) {
return (
<footer
{...props}
className={classNames(styles.footer, className)}
data-appearance={appearance}
data-separator={separator}
data-openbitfun-part="footer"
ref={ref}
/>
Expand Down
1 change: 1 addition & 0 deletions design-system/packages/ui/src/components/Dialog/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export {
type DialogCloseReason,
type DialogFooterAppearance,
type DialogFooterProps,
type DialogHeaderProps,
type DialogProps,
type DialogSize,
type SheetPlacement,
Expand Down
3 changes: 3 additions & 0 deletions design-system/packages/ui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ export {
type DialogBodyProps,
type DialogCloseProps,
type DialogCloseReason,
type DialogFooterAppearance,
type DialogFooterProps,
type DialogHeaderProps,
type DialogProps,
type DialogSize,
type SheetPlacement,
Expand Down
17 changes: 13 additions & 4 deletions design-system/packages/ui/tests/dialog.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createElement } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { readFile } from "node:fs/promises";
import test from "node:test";
import { DialogFooter } from "../dist/index.js";
import { DialogFooter, DialogHeader } from "../dist/index.js";

test("Dialog and Sheet compose the shared overlay kernel and compound anatomy", async () => {
const source = await readFile(
Expand Down Expand Up @@ -69,7 +69,7 @@ test("Dialog geometry and typography use public design tokens", async () => {
assert.doesNotMatch(styles, /#[0-9a-f]{3,8}/i);
});

test("DialogFooter exposes a centered opaque floating action layer", async () => {
test("DialogFooter exposes a centered frosted floating action layer", async () => {
const markup = renderToStaticMarkup(
createElement(
DialogFooter,
Expand All @@ -83,8 +83,17 @@ test("DialogFooter exposes a centered opaque floating action layer", async () =>
assert.match(markup, /data-appearance="floating"/);
assert.match(styles, /\[data-appearance=floating\]\{[^}]*position:absolute/);
assert.match(styles, /\[data-appearance=floating\]\{[^}]*justify-content:center/);
assert.match(styles, /\[data-appearance=floating\]\{[^}]*background:var\(--openbitfun-color-surface-raised\)/);
assert.doesNotMatch(styles, /\[data-appearance=floating\]\{[^}]*background:transparent/);
assert.match(styles, /\[data-appearance=floating\]:before\{[^}]*background:linear-gradient/);
assert.match(styles, /\[data-appearance=floating\]:before\{[^}]*backdrop-filter:var\(--openbitfun-overlay-dialog-footer-blur\)/);
assert.match(styles, /\[data-appearance=floating\]\{[^}]*pointer-events:auto/);
assert.match(styles, /--openbitfun-overlay-dialog-footer-action-min-width/);
});

test("dialog separators are explicit and do not leak native attributes", () => {
for (const component of [DialogHeader, DialogFooter]) {
assert.match(renderToStaticMarkup(createElement(component)), /data-separator="false"/);
const markup = renderToStaticMarkup(createElement(component, { separator: true }));
assert.match(markup, /data-separator="true"/);
assert.doesNotMatch(markup, / separator=/);
}
});
4 changes: 3 additions & 1 deletion design-system/packages/ui/tests/text-clipping.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ for (const [component, selector] of [
declarations["line-height"],
component === "ActionItem"
? /^var\(--openbitfun-type-action-row-line-height\)$/
: /^var\(--openbitfun-(?:line-height-base|type-body-sm-line-height|type-label-(?:xs|md)-line-height)\)$/,
: component === "Dialog"
? /^var\(--openbitfun-type-heading-dialog-line-height\)$/
: /^var\(--openbitfun-(?:line-height-base|type-body-sm-line-height|type-label-(?:xs|md)-line-height)\)$/,
);
assert.equal(declarations.overflow, "hidden");
assert.equal(declarations["text-overflow"], undefined);
Expand Down
8 changes: 4 additions & 4 deletions src/apps/data-migrator/ui/generated/design-system.css
Original file line number Diff line number Diff line change
Expand Up @@ -530,10 +530,10 @@
--openbitfun-type-heading-compact-page-letter-spacing: var(--openbitfun-letter-spacing-normal);
--openbitfun-type-heading-compact-page-line-height: var(--openbitfun-line-height-tight);
--openbitfun-type-heading-dialog-font-family: var(--openbitfun-font-family-sans);
--openbitfun-type-heading-dialog-font-size: var(--openbitfun-font-size-3xl);
--openbitfun-type-heading-dialog-font-weight: var(--openbitfun-font-weight-semibold);
--openbitfun-type-heading-dialog-letter-spacing: var(--openbitfun-letter-spacing-tight);
--openbitfun-type-heading-dialog-line-height: var(--openbitfun-line-height-tight);
--openbitfun-type-heading-dialog-font-size: var(--openbitfun-font-size-3xl-plus);
--openbitfun-type-heading-dialog-font-weight: var(--openbitfun-font-weight-bold);
--openbitfun-type-heading-dialog-letter-spacing: var(--openbitfun-letter-spacing-normal);
--openbitfun-type-heading-dialog-line-height: 1.2083333333;
--openbitfun-type-heading-display-font-family: var(--openbitfun-font-family-sans);
--openbitfun-type-heading-display-font-size: var(--openbitfun-font-size-4xl);
--openbitfun-type-heading-display-font-weight: var(--openbitfun-font-weight-semibold);
Expand Down
Loading