@gtivr4/a1-design-system-react 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/package.json +2 -1
  2. package/src/components/accordion/Accordion.d.ts +20 -0
  3. package/src/components/banner/Banner.d.ts +19 -0
  4. package/src/components/bleed/Bleed.d.ts +17 -0
  5. package/src/components/blockquote/Blockquote.d.ts +23 -0
  6. package/src/components/breadcrumb/Breadcrumb.d.ts +22 -0
  7. package/src/components/button/Button.d.ts +17 -0
  8. package/src/components/button-container/ButtonContainer.d.ts +11 -0
  9. package/src/components/calendar/Calendar.d.ts +75 -0
  10. package/src/components/card/Card.d.ts +31 -0
  11. package/src/components/checkbox-group/CheckboxGroup.d.ts +37 -0
  12. package/src/components/choice-group/ChoiceGroup.d.ts +68 -0
  13. package/src/components/circular-progress/CircularProgress.d.ts +37 -0
  14. package/src/components/circular-progress/CircularProgress.jsx +85 -0
  15. package/src/components/circular-progress/circular-progress.css +104 -0
  16. package/src/components/cluster/Cluster.d.ts +21 -0
  17. package/src/components/code/Code.d.ts +15 -0
  18. package/src/components/data-table/DataTable.d.ts +83 -0
  19. package/src/components/definition-list/DefinitionList.d.ts +51 -0
  20. package/src/components/definition-list/DefinitionList.jsx +154 -0
  21. package/src/components/definition-list/definition-list.css +111 -0
  22. package/src/components/dialog/Dialog.d.ts +22 -0
  23. package/src/components/dialog/Dialog.jsx +57 -11
  24. package/src/components/dialog/dialog.css +36 -3
  25. package/src/components/divider/Divider.d.ts +22 -0
  26. package/src/components/field/SelectField.d.ts +22 -0
  27. package/src/components/field/TextField.d.ts +21 -0
  28. package/src/components/field/TextareaField.d.ts +28 -0
  29. package/src/components/field-row/FieldRow.d.ts +8 -0
  30. package/src/components/fieldset/Fieldset.d.ts +27 -0
  31. package/src/components/figure/Figure.d.ts +39 -0
  32. package/src/components/grid/Grid.d.ts +38 -0
  33. package/src/components/heading/Heading.d.ts +43 -0
  34. package/src/components/heading/Heading.jsx +2 -2
  35. package/src/components/icon/Icon.d.ts +35 -0
  36. package/src/components/icon/Icon.jsx +32 -3
  37. package/src/components/icon/icon.css +21 -1
  38. package/src/components/icon-button/IconButton.d.ts +14 -0
  39. package/src/components/inset/Inset.d.ts +17 -0
  40. package/src/components/inverse/Inverse.d.ts +9 -0
  41. package/src/components/link/Link.d.ts +15 -0
  42. package/src/components/list/List.d.ts +40 -0
  43. package/src/components/menu/Menu.d.ts +41 -0
  44. package/src/components/message/Message.d.ts +34 -0
  45. package/src/components/notification/Notification.d.ts +23 -0
  46. package/src/components/page-layout/PageLayout.d.ts +24 -0
  47. package/src/components/page-nav/PageNav.d.ts +19 -0
  48. package/src/components/pagination/Pagination.d.ts +19 -0
  49. package/src/components/paragraph/Paragraph.d.ts +23 -0
  50. package/src/components/radio-group/RadioGroup.d.ts +37 -0
  51. package/src/components/section/Section.d.ts +33 -0
  52. package/src/components/segmented-control/SegmentedControl.d.ts +23 -0
  53. package/src/components/side-nav/SideNav.d.ts +62 -0
  54. package/src/components/spacer/Spacer.d.ts +16 -0
  55. package/src/components/stack/Stack.d.ts +38 -0
  56. package/src/components/status-bar/StatusBar.d.ts +42 -0
  57. package/src/components/switch/Switch.d.ts +27 -0
  58. package/src/components/system-banner/SystemBanner.d.ts +17 -0
  59. package/src/components/tabs/Tabs.d.ts +53 -0
  60. package/src/index.js +2 -0
  61. package/src/tokens.css +22 -0
@@ -0,0 +1,83 @@
1
+ import * as React from "react";
2
+
3
+ export interface DataTableColumn {
4
+ key: string;
5
+ label: string;
6
+ type?: "text" | "number" | "currency" | "date" | "badge" | "avatar" | "link" | "actions";
7
+ align?: "start" | "center" | "end";
8
+ width?: string;
9
+ sortable?: boolean;
10
+ sortAccessor?: (row: Record<string, unknown>) => unknown;
11
+ searchAccessor?: (row: Record<string, unknown>) => unknown;
12
+ statusMap?: Record<string, "neutral" | "info" | "success" | "warn" | "error">;
13
+ currencySymbol?: string;
14
+ }
15
+
16
+ export interface DataTableSortState {
17
+ key: string;
18
+ direction: "asc" | "desc";
19
+ }
20
+
21
+ export interface DataTableFilter {
22
+ key: string;
23
+ label: string;
24
+ type?: "single" | "multi";
25
+ options: Array<{ value: string; label: string }>;
26
+ }
27
+
28
+ export interface DataTableProps extends React.HTMLAttributes<HTMLDivElement> {
29
+ columns?: DataTableColumn[];
30
+ rows?: Record<string, unknown>[];
31
+ /**
32
+ * Cell spacing density.
33
+ * Omit (default) to auto-select density based on the available container width.
34
+ */
35
+ size?: "comfortable" | "default" | "compact";
36
+ /** Alternate row shading. Default: false */
37
+ zebra?: boolean;
38
+ /** Enable horizontal scroll on the table. Default: false */
39
+ scrollable?: boolean;
40
+ /** Accessible caption for the table */
41
+ caption?: string;
42
+ /** Controlled current page (1-based) */
43
+ page?: number;
44
+ /** Uncontrolled initial page. Default: 1 */
45
+ defaultPage?: number;
46
+ /** Number of rows per page for built-in pagination */
47
+ pageSize?: number;
48
+ /** Total page count for server-side pagination */
49
+ totalPages?: number;
50
+ /** Total row count for server-side row counter */
51
+ totalRows?: number;
52
+ /** Called when the active page changes */
53
+ onPageChange?: (page: number) => void;
54
+ /** Controlled sort state */
55
+ sort?: DataTableSortState | null;
56
+ /** Uncontrolled initial sort state */
57
+ defaultSort?: DataTableSortState;
58
+ /** Called when sort changes */
59
+ onSortChange?: (sort: DataTableSortState | null) => void;
60
+ filters?: DataTableFilter[];
61
+ filterValue?: Record<string, unknown>;
62
+ defaultFilterValue?: Record<string, unknown>;
63
+ onFilterChange?: (value: Record<string, unknown>) => void;
64
+ searchValue?: string;
65
+ defaultSearchValue?: string;
66
+ onSearchChange?: (value: string) => void;
67
+ searchColumn?: string;
68
+ defaultSearchColumn?: string;
69
+ onSearchColumnChange?: (column: string) => void;
70
+ searchableColumns?: Array<{ key: string; label: string }>;
71
+ /** Enable row selection. Default: false */
72
+ selectable?: boolean;
73
+ selectedRowIds?: (string | number)[];
74
+ defaultSelectedRowIds?: (string | number)[];
75
+ onSelectedRowIdsChange?: (ids: string[]) => void;
76
+ onDeleteSelected?: (rows: Record<string, unknown>[], ids: string[]) => void;
77
+ getRowId?: (row: Record<string, unknown>, index: number) => string | number;
78
+ emptyTitle?: string;
79
+ emptyDescription?: string;
80
+ emptyIcon?: string;
81
+ }
82
+
83
+ export declare function DataTable(props: DataTableProps): React.ReactElement;
@@ -0,0 +1,51 @@
1
+ import * as React from "react";
2
+ import type { HeadingProps } from "../heading/Heading";
3
+
4
+ export type DefinitionListDirection = "row" | "column";
5
+ export type DefinitionListSize = "sm" | "md" | "lg";
6
+ export type DefinitionListLabelWidth = "auto" | "fixed";
7
+
8
+ export interface DefinitionListItem {
9
+ /** Stable key for this label/value pair. Falls back to label or index. */
10
+ id?: React.Key;
11
+ /** Label rendered in the `<dt>`. */
12
+ label: React.ReactNode;
13
+ /** Value rendered in the `<dd>`. */
14
+ value?: React.ReactNode;
15
+ /** Alternate value content for JSX object-literal ergonomics. */
16
+ children?: React.ReactNode;
17
+ /** Enables or disables the copy button for this item. Defaults to the list-level copyValue prop. */
18
+ copyValue?: boolean;
19
+ /** Exact text copied to the clipboard. Defaults to the rendered text value when it can be inferred. */
20
+ copyText?: string;
21
+ /** Accessible label for this item's copy button. Defaults to the list-level copyLabel. */
22
+ copyLabel?: string;
23
+ /** Accessible label shown after this item's value is copied. Defaults to the list-level copiedLabel. */
24
+ copiedLabel?: string;
25
+ /** Heading props for this item's value. Overrides the list-level valueHeadingProps. */
26
+ valueHeadingProps?: Omit<HeadingProps, "children" | "className">;
27
+ }
28
+
29
+ export interface DefinitionListProps extends React.HTMLAttributes<HTMLDListElement> {
30
+ /** Label/value pairs to render as `<dt>` and `<dd>` groups. */
31
+ items?: DefinitionListItem[];
32
+ /** Pair layout direction. Default: "row" */
33
+ direction?: DefinitionListDirection;
34
+ /** Spacing and body text size. Default: "md" */
35
+ size?: DefinitionListSize;
36
+ /**
37
+ * Row label sizing. "auto" lets each label hug content; "fixed" aligns values on a responsive label column.
38
+ * Only applies when direction="row". Default: "auto"
39
+ */
40
+ labelWidth?: DefinitionListLabelWidth;
41
+ /** Show copy buttons for copyable text values. Can be overridden per item. Default: false */
42
+ copyValue?: boolean;
43
+ /** Accessible label for copy buttons. Default: "Copy value" */
44
+ copyLabel?: string;
45
+ /** Accessible label used after a copy succeeds. Default: "Copied" */
46
+ copiedLabel?: string;
47
+ /** Render values with Heading, including Heading type and size support. Can be overridden per item. */
48
+ valueHeadingProps?: Omit<HeadingProps, "children" | "className">;
49
+ }
50
+
51
+ export declare function DefinitionList(props: DefinitionListProps): React.ReactElement;
@@ -0,0 +1,154 @@
1
+ import { Children, isValidElement, useEffect, useMemo, useRef, useState } from "react";
2
+ import { Heading } from "../heading/Heading.jsx";
3
+ import { IconButton } from "../icon-button/IconButton.jsx";
4
+ import "./definition-list.css";
5
+
6
+ const directions = ["row", "column"];
7
+ const sizes = ["sm", "md", "lg"];
8
+ const labelWidths = ["auto", "fixed"];
9
+
10
+ function textFromNode(node) {
11
+ if (node == null || typeof node === "boolean") return "";
12
+ if (typeof node === "string" || typeof node === "number") return String(node);
13
+ if (Array.isArray(node)) return node.map(textFromNode).join("");
14
+ if (isValidElement(node)) return textFromNode(node.props.children);
15
+ return "";
16
+ }
17
+
18
+ function writeClipboard(text) {
19
+ if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
20
+ return navigator.clipboard.writeText(text).catch(() => writeClipboardFallback(text));
21
+ }
22
+
23
+ return writeClipboardFallback(text);
24
+ }
25
+
26
+ function writeClipboardFallback(text) {
27
+ if (typeof document === "undefined") return Promise.reject(new Error("Clipboard unavailable"));
28
+
29
+ const textarea = document.createElement("textarea");
30
+ textarea.value = text;
31
+ textarea.setAttribute("readonly", "");
32
+ textarea.style.position = "fixed";
33
+ textarea.style.insetBlockStart = "0";
34
+ textarea.style.insetInlineStart = "0";
35
+ textarea.style.inlineSize = "1px";
36
+ textarea.style.blockSize = "1px";
37
+ textarea.style.opacity = "0";
38
+ document.body.appendChild(textarea);
39
+ textarea.focus();
40
+ textarea.select();
41
+
42
+ try {
43
+ textarea.setSelectionRange(0, textarea.value.length);
44
+ const copied = document.execCommand("copy");
45
+ if (!copied) throw new Error("Copy command failed");
46
+ return Promise.resolve();
47
+ } catch (error) {
48
+ return Promise.reject(error);
49
+ } finally {
50
+ document.body.removeChild(textarea);
51
+ }
52
+ }
53
+
54
+ function DefinitionValue({ item, inheritedHeadingProps }) {
55
+ const headingProps = item.valueHeadingProps ?? inheritedHeadingProps;
56
+ const value = item.value ?? item.children;
57
+
58
+ if (!headingProps) return value;
59
+
60
+ return (
61
+ <Heading as="p" type="heading" size="md" {...headingProps}>
62
+ {value}
63
+ </Heading>
64
+ );
65
+ }
66
+
67
+ function DefinitionCopyButton({ text, label = "Copy value", copiedLabel = "Copied" }) {
68
+ const [copied, setCopied] = useState(false);
69
+ const resetTimer = useRef(null);
70
+
71
+ useEffect(() => {
72
+ return () => {
73
+ if (resetTimer.current) window.clearTimeout(resetTimer.current);
74
+ };
75
+ }, []);
76
+
77
+ async function handleCopy() {
78
+ await writeClipboard(text);
79
+ setCopied(true);
80
+
81
+ if (resetTimer.current) window.clearTimeout(resetTimer.current);
82
+ resetTimer.current = window.setTimeout(() => {
83
+ setCopied(false);
84
+ resetTimer.current = null;
85
+ }, 2000);
86
+ }
87
+
88
+ return (
89
+ <IconButton
90
+ className="a1-definition-list__copy"
91
+ icon={copied ? "check" : "content_copy"}
92
+ label={copied ? copiedLabel : label}
93
+ onClick={handleCopy}
94
+ variant="tertiary"
95
+ />
96
+ );
97
+ }
98
+
99
+ export function DefinitionList({
100
+ items = [],
101
+ direction = "row",
102
+ size = "md",
103
+ labelWidth = "auto",
104
+ copyValue = false,
105
+ copyLabel = "Copy value",
106
+ copiedLabel = "Copied",
107
+ valueHeadingProps,
108
+ className = "",
109
+ ...props
110
+ }) {
111
+ const resolvedDirection = directions.includes(direction) ? direction : "row";
112
+ const resolvedSize = sizes.includes(size) ? size : "md";
113
+ const resolvedLabelWidth = labelWidths.includes(labelWidth) ? labelWidth : "auto";
114
+ const normalizedItems = useMemo(() => items.filter(Boolean), [items]);
115
+
116
+ const classes = [
117
+ "a1-definition-list",
118
+ `a1-definition-list--${resolvedDirection}`,
119
+ `a1-definition-list--${resolvedSize}`,
120
+ resolvedDirection === "row" && `a1-definition-list--label-${resolvedLabelWidth}`,
121
+ className,
122
+ ].filter(Boolean).join(" ");
123
+
124
+ return (
125
+ <dl className={classes} {...props}>
126
+ {normalizedItems.map((item, index) => {
127
+ const value = item.value ?? item.children;
128
+ const textToCopy = item.copyText ?? textFromNode(Children.toArray(value));
129
+ const shouldCopy = item.copyValue ?? copyValue;
130
+ const itemKey = item.id ?? (
131
+ typeof item.label === "string" || typeof item.label === "number" ? item.label : index
132
+ );
133
+
134
+ return (
135
+ <div className="a1-definition-list__item" key={itemKey}>
136
+ <dt className="a1-definition-list__label">{item.label}</dt>
137
+ <dd className="a1-definition-list__value">
138
+ <span className="a1-definition-list__value-content">
139
+ <DefinitionValue item={item} inheritedHeadingProps={valueHeadingProps} />
140
+ </span>
141
+ {shouldCopy && textToCopy && (
142
+ <DefinitionCopyButton
143
+ text={textToCopy}
144
+ label={item.copyLabel ?? copyLabel}
145
+ copiedLabel={item.copiedLabel ?? copiedLabel}
146
+ />
147
+ )}
148
+ </dd>
149
+ </div>
150
+ );
151
+ })}
152
+ </dl>
153
+ );
154
+ }
@@ -0,0 +1,111 @@
1
+ .a1-definition-list {
2
+ container-type: inline-size;
3
+ margin: 0;
4
+ font-family: var(--component-paragraph-font-family);
5
+ color: var(--semantic-color-text-default);
6
+ }
7
+
8
+ .a1-definition-list--sm {
9
+ --a1-definition-list-gap: var(--component-definition-list-gap-sm);
10
+ --a1-definition-list-row-gap: var(--component-definition-list-row-gap-sm);
11
+ --a1-definition-list-column-gap: var(--component-definition-list-column-gap-sm);
12
+ --a1-definition-list-font-size: var(--semantic-font-size-body-sm);
13
+ }
14
+
15
+ .a1-definition-list--md {
16
+ --a1-definition-list-gap: var(--component-definition-list-gap-md);
17
+ --a1-definition-list-row-gap: var(--component-definition-list-row-gap-md);
18
+ --a1-definition-list-column-gap: var(--component-definition-list-column-gap-md);
19
+ --a1-definition-list-font-size: var(--semantic-font-size-body-md);
20
+ }
21
+
22
+ .a1-definition-list--lg {
23
+ --a1-definition-list-gap: var(--component-definition-list-gap-lg);
24
+ --a1-definition-list-row-gap: var(--component-definition-list-row-gap-lg);
25
+ --a1-definition-list-column-gap: var(--component-definition-list-column-gap-lg);
26
+ --a1-definition-list-font-size: var(--semantic-font-size-body-lg);
27
+ }
28
+
29
+ .a1-definition-list__item {
30
+ min-inline-size: 0;
31
+ }
32
+
33
+ .a1-definition-list__label,
34
+ .a1-definition-list__value {
35
+ min-inline-size: 0;
36
+ margin: 0;
37
+ font-size: var(--a1-definition-list-font-size);
38
+ line-height: var(--semantic-font-line-height-body);
39
+ }
40
+
41
+ .a1-definition-list__label {
42
+ font-weight: var(--component-definition-list-label-font-weight);
43
+ color: var(--semantic-color-text-muted);
44
+ }
45
+
46
+ .a1-definition-list__value {
47
+ color: var(--semantic-color-text-default);
48
+ }
49
+
50
+ .a1-definition-list__value-content {
51
+ min-inline-size: 0;
52
+ }
53
+
54
+ .a1-definition-list__copy {
55
+ margin-block-start: calc((1lh - var(--component-icon-button-size)) / 2);
56
+ }
57
+
58
+ .a1-definition-list--column {
59
+ display: flex;
60
+ flex-direction: column;
61
+ gap: var(--a1-definition-list-gap);
62
+ }
63
+
64
+ .a1-definition-list--column .a1-definition-list__item {
65
+ display: flex;
66
+ flex-direction: column;
67
+ gap: var(--a1-definition-list-row-gap);
68
+ }
69
+
70
+ .a1-definition-list--row {
71
+ display: flex;
72
+ flex-direction: column;
73
+ gap: var(--a1-definition-list-row-gap);
74
+ }
75
+
76
+ .a1-definition-list--row .a1-definition-list__item {
77
+ display: grid;
78
+ grid-template-columns: var(--a1-definition-list-label-column) minmax(0, 1fr);
79
+ column-gap: var(--a1-definition-list-column-gap);
80
+ align-items: start;
81
+ }
82
+
83
+ .a1-definition-list--row.a1-definition-list--label-auto {
84
+ --a1-definition-list-label-column: max-content;
85
+ }
86
+
87
+ .a1-definition-list--row.a1-definition-list--label-fixed {
88
+ --a1-definition-list-label-column: clamp(
89
+ var(--component-definition-list-label-width-min),
90
+ var(--component-definition-list-label-width-preferred),
91
+ var(--component-definition-list-label-width-max)
92
+ );
93
+ }
94
+
95
+ .a1-definition-list__value {
96
+ display: flex;
97
+ align-items: flex-start;
98
+ gap: var(--component-definition-list-copy-gap);
99
+ }
100
+
101
+ .a1-definition-list__value-content {
102
+ flex: 1 1 auto;
103
+ }
104
+
105
+ @container (max-width: 360px) {
106
+ .a1-definition-list--row .a1-definition-list__item {
107
+ display: flex;
108
+ flex-direction: column;
109
+ gap: var(--a1-definition-list-row-gap);
110
+ }
111
+ }
@@ -0,0 +1,22 @@
1
+ import * as React from "react";
2
+
3
+ export interface DialogProps extends React.DialogHTMLAttributes<HTMLDialogElement> {
4
+ /** Whether the dialog is visible. Default: false */
5
+ open?: boolean;
6
+ /** Called when the user closes the dialog (Escape, close button, or backdrop click). Omit to hide the close button. */
7
+ onClose?: () => void;
8
+ /** Dialog title shown in the header */
9
+ title?: string;
10
+ /** Footer content — wrapped in a right-aligned `ButtonContainer` */
11
+ footer?: React.ReactNode;
12
+ /**
13
+ * Status variant — renders a full-bleed colored hero area at the top with a status icon.
14
+ * "success" | "error" | "warn" | "info" | "neutral"
15
+ */
16
+ status?: "success" | "error" | "warn" | "info" | "neutral";
17
+ /** Override the icon shown in the hero area. Defaults to the status icon when `status` is set. */
18
+ icon?: string;
19
+ children?: React.ReactNode;
20
+ }
21
+
22
+ export declare function Dialog(props: DialogProps): React.ReactElement;
@@ -1,8 +1,25 @@
1
1
  import { useEffect, useRef } from "react";
2
2
  import "./dialog.css";
3
+ import { Icon } from "../icon/Icon.jsx";
3
4
  import { IconButton } from "../icon-button/IconButton.jsx";
4
5
  import { ButtonContainer } from "../button-container/ButtonContainer.jsx";
5
6
 
7
+ const HERO_COLORS = {
8
+ success: "var(--semantic-color-status-success-background)",
9
+ error: "var(--semantic-color-status-error-background)",
10
+ warn: "var(--semantic-color-status-warn-background)",
11
+ info: "var(--semantic-color-status-info-background)",
12
+ neutral: "var(--semantic-color-surface-inverse)",
13
+ };
14
+
15
+ const STATUS_ICONS = {
16
+ success: "check_circle",
17
+ error: "error",
18
+ warn: "warning",
19
+ info: "info",
20
+ neutral: "info",
21
+ };
22
+
6
23
  const FOCUSABLE_SELECTORS = [
7
24
  "button:not([disabled])",
8
25
  "[href]",
@@ -12,7 +29,17 @@ const FOCUSABLE_SELECTORS = [
12
29
  '[tabindex]:not([tabindex="-1"])',
13
30
  ].join(", ");
14
31
 
15
- export function Dialog({ open = false, onClose, title, footer, children, ...props }) {
32
+ export function Dialog({
33
+ open = false,
34
+ onClose,
35
+ title,
36
+ footer,
37
+ status,
38
+ icon,
39
+ children,
40
+ className = "",
41
+ ...props
42
+ }) {
16
43
  const ref = useRef(null);
17
44
  const triggerRef = useRef(null);
18
45
 
@@ -65,17 +92,36 @@ export function Dialog({ open = false, onClose, title, footer, children, ...prop
65
92
  return () => el.removeEventListener("keydown", handleKeyDown);
66
93
  }, [open]);
67
94
 
95
+ const heroBg = status ? (HERO_COLORS[status] ?? HERO_COLORS.neutral) : null;
96
+ const resolvedIcon = icon ?? (status ? STATUS_ICONS[status] : null);
97
+ const hasHeader = title || onClose;
98
+
99
+ const classes = [
100
+ "a1-dialog",
101
+ status && "a1-dialog--has-hero",
102
+ className,
103
+ ].filter(Boolean).join(" ");
104
+
68
105
  return (
69
- <dialog ref={ref} className="a1-dialog" {...props}>
70
- <div className="a1-dialog__header">
71
- {title && <p className="a1-dialog__title">{title}</p>}
72
- <IconButton
73
- icon="close"
74
- label="Close dialog"
75
- onClick={onClose}
76
- className="a1-dialog__close"
77
- />
78
- </div>
106
+ <dialog ref={ref} className={classes} {...props}>
107
+ {status && (
108
+ <div className="a1-dialog__hero" style={{ "--a1-dialog-hero-bg": heroBg }}>
109
+ {resolvedIcon && <Icon name={resolvedIcon} aria-hidden="true" />}
110
+ </div>
111
+ )}
112
+ {hasHeader && (
113
+ <div className="a1-dialog__header">
114
+ {title && <p className="a1-dialog__title">{title}</p>}
115
+ {onClose && (
116
+ <IconButton
117
+ icon="close"
118
+ label="Close dialog"
119
+ onClick={onClose}
120
+ className="a1-dialog__close"
121
+ />
122
+ )}
123
+ </div>
124
+ )}
79
125
  <div className="a1-dialog__body" tabIndex={0}>{children}</div>
80
126
  {footer && (
81
127
  <div className="a1-dialog__footer">
@@ -1,4 +1,5 @@
1
1
  .a1-dialog {
2
+ --a1-dialog-padding-inline: var(--component-dialog-padding);
2
3
  box-sizing: border-box;
3
4
  border: var(--component-dialog-border-width) solid var(--semantic-color-border-strong);
4
5
  border-radius: var(--component-dialog-border-radius);
@@ -12,8 +13,12 @@
12
13
  }
13
14
 
14
15
  .a1-dialog[open] {
15
- display: flex;
16
- flex-direction: column;
16
+ /* CSS Grid instead of Flex: Safari collapses flex: 1 children when the
17
+ container has only max-height but no explicit height. Grid resolves
18
+ track sizes against the constrained container first, so 1fr works. */
19
+ display: grid;
20
+ grid-template-rows: auto minmax(0, 1fr);
21
+ grid-auto-rows: auto;
17
22
  gap: var(--base-spacing-16);
18
23
  }
19
24
 
@@ -45,8 +50,8 @@
45
50
  }
46
51
 
47
52
  .a1-dialog__body {
48
- flex: 1;
49
53
  overflow-y: auto;
54
+ min-height: 0;
50
55
  }
51
56
 
52
57
  .a1-dialog__footer {
@@ -54,8 +59,36 @@
54
59
  border-top: var(--component-dialog-footer-border-width) solid var(--semantic-color-border-subtle);
55
60
  }
56
61
 
62
+ /* ─── Hero icon (status variant) ─────────────────────────────────────────── */
63
+
64
+ .a1-dialog--has-hero {
65
+ overflow: hidden;
66
+ }
67
+
68
+ .a1-dialog--has-hero[open] {
69
+ /* Body is the 3rd child when a hero is present, so needs an extra auto row */
70
+ grid-template-rows: auto auto minmax(0, 1fr);
71
+ }
72
+
73
+ .a1-dialog__hero {
74
+ margin-top: calc(-1 * var(--component-dialog-padding));
75
+ margin-inline: calc(-1 * var(--a1-dialog-padding-inline, var(--component-dialog-padding)));
76
+ padding: var(--base-spacing-32) 0;
77
+ background: var(--a1-dialog-hero-bg, var(--semantic-color-action-background));
78
+ display: flex;
79
+ align-items: center;
80
+ justify-content: center;
81
+ --a1-icon-opsz: 48;
82
+ }
83
+
84
+ .a1-dialog__hero .a1-icon {
85
+ font-size: var(--base-spacing-64);
86
+ color: var(--semantic-color-text-inverse);
87
+ }
88
+
57
89
  @media (--bp-sm-down) {
58
90
  .a1-dialog {
91
+ --a1-dialog-padding-inline: var(--base-spacing-20);
59
92
  padding-inline: var(--base-spacing-20);
60
93
  }
61
94
  }
@@ -0,0 +1,22 @@
1
+ import * as React from "react";
2
+
3
+ type Breakpoints = "xs" | "sm" | "md" | "lg" | "xl";
4
+ type Orientation = "horizontal" | "vertical";
5
+
6
+ export interface DividerProps extends React.HTMLAttributes<HTMLHRElement> {
7
+ /**
8
+ * Line orientation. Responsive object syntax supported. Default: "horizontal"
9
+ * @example orientation={{ xs: "horizontal", md: "vertical" }}
10
+ */
11
+ orientation?: Orientation | Partial<Record<Breakpoints, Orientation>>;
12
+ /** Visual style. Default: "subtle" */
13
+ variant?: "subtle" | "strong" | "accent" | "dashed" | "dotted";
14
+ /** Line thickness. Default: "xs" */
15
+ size?: "xs" | "sm" | "md" | "lg";
16
+ /** Block-axis margin (space above and below for horizontal, left/right for vertical). Default: "sm" */
17
+ space?: "none" | "xs" | "sm" | "md" | "lg" | "xl" | "xxl";
18
+ /** Whether this divider is purely decorative (no semantic role). Default: true */
19
+ decorative?: boolean;
20
+ }
21
+
22
+ export declare function Divider(props: DividerProps): React.ReactElement;
@@ -0,0 +1,22 @@
1
+ import * as React from "react";
2
+
3
+ export interface SelectFieldProps extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, "size"> {
4
+ /** Visible label text */
5
+ label?: string;
6
+ /** Helper text shown below the field */
7
+ hint?: string;
8
+ /** Error message — replaces hint and marks the field invalid */
9
+ error?: string;
10
+ /** Size density. Inherits from parent `Fieldset` when omitted. Default: "default" */
11
+ size?: "comfortable" | "default" | "compact";
12
+ /** Label position. Inherits from parent `Fieldset` when omitted. Default: "above" */
13
+ labelPosition?: "above" | "before";
14
+ required?: boolean;
15
+ disabled?: boolean;
16
+ /** Element rendered inside the field control */
17
+ inputOverlay?: React.ReactNode;
18
+ /** `<option>` elements */
19
+ children?: React.ReactNode;
20
+ }
21
+
22
+ export declare const SelectField: React.ForwardRefExoticComponent<SelectFieldProps & React.RefAttributes<HTMLSelectElement>>;
@@ -0,0 +1,21 @@
1
+ import * as React from "react";
2
+
3
+ export interface TextFieldProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size" | "placeholder"> {
4
+ /** Visible label text */
5
+ label?: string;
6
+ /** Helper text shown below the field */
7
+ hint?: string;
8
+ /** Error message — replaces hint and marks the field invalid */
9
+ error?: string;
10
+ /** Size density. Inherits from parent `Fieldset` when omitted. Default: "default" */
11
+ size?: "comfortable" | "default" | "compact";
12
+ /** Label position. Inherits from parent `Fieldset` when omitted. Default: "above" */
13
+ labelPosition?: "above" | "before";
14
+ required?: boolean;
15
+ disabled?: boolean;
16
+ readOnly?: boolean;
17
+ /** Element rendered inside the field control (e.g. a unit suffix) */
18
+ inputOverlay?: React.ReactNode;
19
+ }
20
+
21
+ export declare const TextField: React.ForwardRefExoticComponent<TextFieldProps & React.RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,28 @@
1
+ import * as React from "react";
2
+
3
+ export interface TextareaFieldProps extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, "size" | "rows"> {
4
+ /** Visible label text */
5
+ label?: string;
6
+ /** Helper text shown below the field */
7
+ hint?: string;
8
+ /** Error message — replaces hint and marks the field invalid */
9
+ error?: string;
10
+ /** Size density. Inherits from parent `Fieldset` when omitted. Default: "default" */
11
+ size?: "comfortable" | "default" | "compact";
12
+ /** Label position. Inherits from parent `Fieldset` when omitted. Default: "above" */
13
+ labelPosition?: "above" | "before";
14
+ required?: boolean;
15
+ disabled?: boolean;
16
+ readOnly?: boolean;
17
+ /**
18
+ * Initial visible row height. Pass a number for exact rows or a size token.
19
+ * sm=2 · md=4 · lg=8 · xl=12. Default: "md"
20
+ */
21
+ rows?: "sm" | "md" | "lg" | "xl" | number;
22
+ /** Maximum character count */
23
+ maxLength?: number;
24
+ /** Show a character counter. Auto-enabled when `maxLength` is set. Default: false */
25
+ showCount?: boolean;
26
+ }
27
+
28
+ export declare const TextareaField: React.ForwardRefExoticComponent<TextareaFieldProps & React.RefAttributes<HTMLTextAreaElement>>;