@astryxdesign/core 0.6.1 → 0.6.2
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.
- package/CHANGELOG.md +33 -0
- package/dist/DateRangeInput/DateRangeInput.d.ts +3 -0
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +16 -9
- package/dist/Dialog/DialogHeader.d.ts +1 -1
- package/dist/Dialog/DialogHeader.d.ts.map +1 -1
- package/dist/Dialog/DialogHeader.js +10 -7
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +9 -3
- package/dist/Markdown/Markdown.d.ts +10 -2
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +58 -14
- package/dist/Markdown/index.d.ts +1 -1
- package/dist/Markdown/index.d.ts.map +1 -1
- package/dist/Markdown/parser.d.ts +126 -12
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +369 -34
- package/dist/Markdown/utils.d.ts +1 -1
- package/dist/Markdown/utils.d.ts.map +1 -1
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +5 -2
- package/dist/Spinner/Spinner.d.ts +1 -1
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +23 -15
- package/dist/astryx.css +2 -1
- package/locales/en.json +16 -0
- package/locales/pseudo.json +12 -0
- package/package.json +6 -4
- package/scripts/agent-doc-state.mjs +1 -1
- package/src/DateRangeInput/DateRangeInput.doc.mjs +35 -7
- package/src/DateRangeInput/DateRangeInput.spec.md +203 -0
- package/src/DateRangeInput/DateRangeInput.test.tsx +100 -4
- package/src/DateRangeInput/DateRangeInput.tsx +29 -20
- package/src/Dialog/Dialog.doc.mjs +3 -0
- package/src/Dialog/Dialog.spec.md +1 -1
- package/src/Dialog/DialogHeader.doc.mjs +38 -0
- package/src/Dialog/DialogHeader.test.tsx +49 -0
- package/src/Dialog/DialogHeader.tsx +23 -4
- package/src/Dialog/modules/DialogHeader.spec.md +152 -0
- package/src/FileInput/FileInput.doc.mjs +2 -0
- package/src/FileInput/FileInput.spec.md +199 -0
- package/src/FileInput/FileInput.test.tsx +14 -0
- package/src/FileInput/FileInput.tsx +13 -3
- package/src/Markdown/Markdown.doc.mjs +167 -42
- package/src/Markdown/Markdown.public.test.ts +157 -0
- package/src/Markdown/Markdown.spec.md +149 -70
- package/src/Markdown/Markdown.test.tsx +107 -3
- package/src/Markdown/Markdown.tsx +116 -35
- package/src/Markdown/incremental.test.ts +175 -7
- package/src/Markdown/index.ts +6 -0
- package/src/Markdown/parser.perf.test.ts +3 -1
- package/src/Markdown/parser.test.ts +122 -0
- package/src/Markdown/parser.ts +609 -81
- package/src/Markdown/utils.ts +6 -0
- package/src/Slider/Slider.doc.mjs +16 -0
- package/src/Slider/Slider.spec.md +61 -47
- package/src/Slider/Slider.test.tsx +18 -0
- package/src/Slider/Slider.tsx +12 -6
- package/src/Spinner/Spinner.doc.mjs +6 -3
- package/src/Spinner/Spinner.test.tsx +37 -0
- package/src/Spinner/Spinner.tsx +31 -14
- package/src/theme/derivedVarRegistry.test.ts +6 -4
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
type DayOfWeek,
|
|
55
55
|
type DayOfWeekName,
|
|
56
56
|
} from '../Calendar';
|
|
57
|
+
import {useCalendarConstraints} from '../Calendar/hooks';
|
|
57
58
|
import {usePopover} from '../Popover';
|
|
58
59
|
import {useTooltip} from '../Tooltip';
|
|
59
60
|
import {mergeProps} from '../utils';
|
|
@@ -376,6 +377,9 @@ export interface DateRangeInputProps extends Omit<
|
|
|
376
377
|
|
|
377
378
|
/**
|
|
378
379
|
* Preset date ranges shown as quick-select options beside the calendar.
|
|
380
|
+
* A preset is disabled when either endpoint violates `min`, `max`, or
|
|
381
|
+
* `dateConstraints`, or when its span violates `minRangeSpan` or
|
|
382
|
+
* `maxRangeSpan`.
|
|
379
383
|
*/
|
|
380
384
|
presets?: ReadonlyArray<DateRangePreset>;
|
|
381
385
|
|
|
@@ -542,6 +546,11 @@ export function DateRangeInput({
|
|
|
542
546
|
dialogLabel: t('@astryx.dateRangeInput.dialogLabel'),
|
|
543
547
|
closeButtonLabel: t('@astryx.dateInput.closeCalendar'),
|
|
544
548
|
});
|
|
549
|
+
const {isDateDisabled} = useCalendarConstraints({
|
|
550
|
+
min,
|
|
551
|
+
max,
|
|
552
|
+
dateConstraints,
|
|
553
|
+
});
|
|
545
554
|
|
|
546
555
|
const fireChange = useCallback(
|
|
547
556
|
(newValue: DateRange | null) => {
|
|
@@ -577,14 +586,6 @@ export function DateRangeInput({
|
|
|
577
586
|
[fireChange, popover],
|
|
578
587
|
);
|
|
579
588
|
|
|
580
|
-
const handlePresetClick = useCallback(
|
|
581
|
-
(preset: DateRangePreset) => {
|
|
582
|
-
fireChange(preset.getRange());
|
|
583
|
-
popover.hide();
|
|
584
|
-
},
|
|
585
|
-
[fireChange, popover],
|
|
586
|
-
);
|
|
587
|
-
|
|
588
589
|
const handleClear = useCallback(
|
|
589
590
|
(e: React.MouseEvent) => {
|
|
590
591
|
e.stopPropagation();
|
|
@@ -713,15 +714,17 @@ export function DateRangeInput({
|
|
|
713
714
|
<div
|
|
714
715
|
role="group"
|
|
715
716
|
aria-label={t('@astryx.dateRangeInput.presetDateRanges')}
|
|
716
|
-
{...
|
|
717
|
+
{...mergeProps(
|
|
718
|
+
themeProps('date-range-input-presets'),
|
|
719
|
+
stylex.props(styles.presetSidebar),
|
|
720
|
+
)}>
|
|
717
721
|
{presets.map(preset => {
|
|
718
722
|
const presetRange = preset.getRange();
|
|
719
723
|
const isActive = isRangeEqual(value, presetRange);
|
|
720
|
-
const isPresetDisabled =
|
|
721
|
-
presetRange,
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
);
|
|
724
|
+
const isPresetDisabled =
|
|
725
|
+
!isRangeWithinSpan(presetRange, maxRangeSpan, minRangeSpan) ||
|
|
726
|
+
isDateDisabled(plainDateFromISO(presetRange.start)) ||
|
|
727
|
+
isDateDisabled(plainDateFromISO(presetRange.end));
|
|
725
728
|
return (
|
|
726
729
|
<button
|
|
727
730
|
key={preset.label}
|
|
@@ -733,12 +736,18 @@ export function DateRangeInput({
|
|
|
733
736
|
// concept that contradicted the Tab interaction) (forms-5).
|
|
734
737
|
aria-current={isActive ? 'true' : undefined}
|
|
735
738
|
disabled={isPresetDisabled}
|
|
736
|
-
onClick={() =>
|
|
737
|
-
{...
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
739
|
+
onClick={() => handleRangeSelect(presetRange)}
|
|
740
|
+
{...mergeProps(
|
|
741
|
+
themeProps('date-range-input-preset', {
|
|
742
|
+
selected: isActive ? 'selected' : null,
|
|
743
|
+
disabled: isPresetDisabled ? 'disabled' : null,
|
|
744
|
+
}),
|
|
745
|
+
stylex.props(
|
|
746
|
+
focusOutlineStyles.focusVisible,
|
|
747
|
+
styles.presetButton,
|
|
748
|
+
isActive && styles.presetButtonActive,
|
|
749
|
+
isPresetDisabled && styles.presetButtonDisabled,
|
|
750
|
+
),
|
|
742
751
|
)}>
|
|
743
752
|
{preset.label}
|
|
744
753
|
</button>
|
|
@@ -30,6 +30,9 @@ export const docs = {
|
|
|
30
30
|
container: true,
|
|
31
31
|
targets: [
|
|
32
32
|
{className: 'astryx-dialog', visualProps: ['variant']},
|
|
33
|
+
{className: 'astryx-dialog-header'},
|
|
34
|
+
{className: 'astryx-dialog-header-title-block'},
|
|
35
|
+
{className: 'astryx-dialog-header-close-icon'},
|
|
33
36
|
],
|
|
34
37
|
vars: [
|
|
35
38
|
{name: '--_dialog-radius', description: 'Border radius of the dialog', default: 'var(--radius-container)', private: true},
|
|
@@ -17,7 +17,7 @@ verified_by:
|
|
|
17
17
|
packages/core/src/Dialog/__tests__/Dialog.a11y.test.tsx,
|
|
18
18
|
packages/core/src/Dialog/__tests__/Dialog.a11y.chromium.spec.ts,
|
|
19
19
|
]
|
|
20
|
-
modules: []
|
|
20
|
+
modules: [module:Dialog/DialogHeader]
|
|
21
21
|
families: [family:overlay-dismissal]
|
|
22
22
|
design_specs: []
|
|
23
23
|
architecture:
|
|
@@ -8,6 +8,14 @@ export const docs = {
|
|
|
8
8
|
displayName: 'Dialog Header',
|
|
9
9
|
isHiddenFromOverview: true,
|
|
10
10
|
description: 'Header for dialogs with a title, optional subtitle, close button, and start/end content slots.',
|
|
11
|
+
usage: {
|
|
12
|
+
description: 'Use DialogHeader to give a dialog a labelled title area and optional close control.',
|
|
13
|
+
anatomy: [
|
|
14
|
+
{name: 'Header row', required: true, description: 'Arranges the title block, optional start/end content, and close control.'},
|
|
15
|
+
{name: 'Title block', required: true, description: 'Groups the title and optional subtitle.'},
|
|
16
|
+
{name: 'Close icon', required: false, description: 'Visual close glyph inside the close button.'},
|
|
17
|
+
],
|
|
18
|
+
},
|
|
11
19
|
props: [
|
|
12
20
|
{
|
|
13
21
|
name: 'title',
|
|
@@ -72,6 +80,13 @@ export const docs = {
|
|
|
72
80
|
hasDivider: true,
|
|
73
81
|
},
|
|
74
82
|
},
|
|
83
|
+
theming: {
|
|
84
|
+
targets: [
|
|
85
|
+
{className: 'astryx-dialog-header'},
|
|
86
|
+
{className: 'astryx-dialog-header-title-block'},
|
|
87
|
+
{className: 'astryx-dialog-header-close-icon'},
|
|
88
|
+
],
|
|
89
|
+
},
|
|
75
90
|
examples: [
|
|
76
91
|
{
|
|
77
92
|
label: 'Basic',
|
|
@@ -117,6 +132,14 @@ export const docsZh = {
|
|
|
117
132
|
isHiddenFromOverview: true,
|
|
118
133
|
displayName: 'Dialog Header',
|
|
119
134
|
description: '对话框头部,包含标题、可选副标题、关闭按钮以及首尾内容插槽。',
|
|
135
|
+
usage: {
|
|
136
|
+
description: '使用 DialogHeader 为对话框提供带标签的标题区和可选的关闭控件。',
|
|
137
|
+
anatomy: [
|
|
138
|
+
{name: 'Header row', required: true, description: '排列标题区、可选的首尾内容和关闭控件。'},
|
|
139
|
+
{name: 'Title block', required: true, description: '组合标题和可选副标题。'},
|
|
140
|
+
{name: 'Close icon', required: false, description: '关闭按钮内的关闭图标。'},
|
|
141
|
+
],
|
|
142
|
+
},
|
|
120
143
|
props: [
|
|
121
144
|
{
|
|
122
145
|
name: 'title',
|
|
@@ -150,6 +173,13 @@ export const docsZh = {
|
|
|
150
173
|
default: 'true',
|
|
151
174
|
},
|
|
152
175
|
],
|
|
176
|
+
theming: {
|
|
177
|
+
targets: [
|
|
178
|
+
{className: 'astryx-dialog-header'},
|
|
179
|
+
{className: 'astryx-dialog-header-title-block'},
|
|
180
|
+
{className: 'astryx-dialog-header-close-icon'},
|
|
181
|
+
],
|
|
182
|
+
},
|
|
153
183
|
};
|
|
154
184
|
|
|
155
185
|
export const docsDense = {
|
|
@@ -157,6 +187,14 @@ export const docsDense = {
|
|
|
157
187
|
isHiddenFromOverview: true,
|
|
158
188
|
displayName: 'Dialog Header',
|
|
159
189
|
description: 'dialog header w/ title, optional subtitle, close button, start/end content slots',
|
|
190
|
+
usage: {
|
|
191
|
+
description: 'labelled dialog title area + optional close control',
|
|
192
|
+
anatomy: [
|
|
193
|
+
{name: 'Header row', required: true, description: 'arranges title block, optional start/end content, close control'},
|
|
194
|
+
{name: 'Title block', required: true, description: 'groups title + optional subtitle'},
|
|
195
|
+
{name: 'Close icon', required: false, description: 'close glyph inside close button'},
|
|
196
|
+
],
|
|
197
|
+
},
|
|
160
198
|
propDescriptions: {
|
|
161
199
|
title: 'dialog title (focused on open; labels dialog via aria-labelledby)',
|
|
162
200
|
subtitle: 'subtitle below title',
|
|
@@ -14,6 +14,13 @@ import {render, screen} from '@testing-library/react';
|
|
|
14
14
|
import userEvent from '@testing-library/user-event';
|
|
15
15
|
import {DialogHeader} from './DialogHeader';
|
|
16
16
|
import {LayoutDividerContext} from '../Layout/LayoutDividerContext';
|
|
17
|
+
import {defineTheme} from '../theme/defineTheme';
|
|
18
|
+
import {generateThemeCSS} from '../theme/generateThemeRules';
|
|
19
|
+
|
|
20
|
+
function generateThemeTestCSS(theme: Parameters<typeof generateThemeCSS>[0]) {
|
|
21
|
+
const {prose, component} = generateThemeCSS(theme);
|
|
22
|
+
return [prose, component].filter(Boolean).join('\n\n');
|
|
23
|
+
}
|
|
17
24
|
|
|
18
25
|
describe('DialogHeader', () => {
|
|
19
26
|
it('renders the title', () => {
|
|
@@ -56,6 +63,48 @@ describe('DialogHeader', () => {
|
|
|
56
63
|
expect(screen.getByRole('button', {name: /close/i})).toBeInTheDocument();
|
|
57
64
|
});
|
|
58
65
|
|
|
66
|
+
it('exposes theme targets for the header row, title block, and close icon', () => {
|
|
67
|
+
const {container} = render(
|
|
68
|
+
<DialogHeader
|
|
69
|
+
title="Title"
|
|
70
|
+
subtitle="Subtitle"
|
|
71
|
+
onOpenChange={() => {}}
|
|
72
|
+
/>,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect(container.querySelector('.astryx-dialog-header')).not.toBeNull();
|
|
76
|
+
expect(screen.getByRole('heading', {level: 2}).parentElement).toHaveClass(
|
|
77
|
+
'astryx-dialog-header-title-block',
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const closeIcon = screen
|
|
81
|
+
.getByRole('button', {name: /close/i})
|
|
82
|
+
.querySelector('.astryx-dialog-header-close-icon');
|
|
83
|
+
expect(closeIcon).toHaveClass('astryx-icon');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('lets themes set the two internal gaps and close-icon size', () => {
|
|
87
|
+
const theme = defineTheme({
|
|
88
|
+
name: 'dialog-header-targets-test',
|
|
89
|
+
components: {
|
|
90
|
+
'dialog-header': {base: {gap: '8px'}},
|
|
91
|
+
'dialog-header-title-block': {base: {gap: '4px'}},
|
|
92
|
+
'dialog-header-close-icon': {
|
|
93
|
+
base: {width: '16px', height: '16px', fontSize: '16px'},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
const css = generateThemeTestCSS(theme);
|
|
98
|
+
|
|
99
|
+
expect(css).toContain('.astryx-dialog-header {');
|
|
100
|
+
expect(css).toContain('gap: 8px');
|
|
101
|
+
expect(css).toContain('.astryx-dialog-header-title-block {');
|
|
102
|
+
expect(css).toContain('gap: 4px');
|
|
103
|
+
expect(css).toContain('.astryx-dialog-header-close-icon {');
|
|
104
|
+
expect(css).toContain('width: 16px');
|
|
105
|
+
expect(css).toContain('height: 16px');
|
|
106
|
+
});
|
|
107
|
+
|
|
59
108
|
it('does not render close button when onOpenChange is not provided', () => {
|
|
60
109
|
render(<DialogHeader title="Title" />);
|
|
61
110
|
expect(
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file DialogHeader.tsx
|
|
7
|
-
* @input Uses React,
|
|
7
|
+
* @input Uses React, StyleX, LayoutHeader, Button, Icon, Heading, Text, DialogContext, mergeProps, themeProps
|
|
8
8
|
* @output Exports DialogHeader component and DialogHeaderProps
|
|
9
9
|
* @position Dialog header component; used with Dialog and Layout
|
|
10
10
|
*
|
|
@@ -25,6 +25,8 @@ import {Icon} from '../Icon';
|
|
|
25
25
|
import {Heading} from '../Heading/Heading';
|
|
26
26
|
import {Text} from '../Text/Text';
|
|
27
27
|
import type {BaseProps} from '../BaseProps';
|
|
28
|
+
import {mergeProps} from '../utils';
|
|
29
|
+
import {themeProps} from '../utils/themeProps';
|
|
28
30
|
import {useDialogContext} from './DialogContext';
|
|
29
31
|
import {useTranslator} from '../i18n';
|
|
30
32
|
|
|
@@ -41,6 +43,9 @@ const styles = stylex.create({
|
|
|
41
43
|
marginInlineEnd: `calc(-1 * ${spacingVars['--spacing-2']})`,
|
|
42
44
|
},
|
|
43
45
|
titleWrapper: {
|
|
46
|
+
display: 'flex',
|
|
47
|
+
flexDirection: 'column',
|
|
48
|
+
gap: spacingVars['--spacing-0'],
|
|
44
49
|
flex: 1,
|
|
45
50
|
minWidth: 0,
|
|
46
51
|
// Visual centering: align title center with close button center
|
|
@@ -161,11 +166,19 @@ export function DialogHeader({
|
|
|
161
166
|
className={className}
|
|
162
167
|
style={style}
|
|
163
168
|
{...rest}>
|
|
164
|
-
<div
|
|
169
|
+
<div
|
|
170
|
+
{...mergeProps(
|
|
171
|
+
themeProps('dialog-header'),
|
|
172
|
+
stylex.props(styles.container),
|
|
173
|
+
)}>
|
|
165
174
|
{startContent && (
|
|
166
175
|
<div {...stylex.props(styles.actions)}>{startContent}</div>
|
|
167
176
|
)}
|
|
168
|
-
<div
|
|
177
|
+
<div
|
|
178
|
+
{...mergeProps(
|
|
179
|
+
themeProps('dialog-header-title-block'),
|
|
180
|
+
stylex.props(styles.titleWrapper),
|
|
181
|
+
)}>
|
|
169
182
|
<Heading
|
|
170
183
|
ref={titleRef}
|
|
171
184
|
id={titleId}
|
|
@@ -192,7 +205,13 @@ export function DialogHeader({
|
|
|
192
205
|
variant="ghost"
|
|
193
206
|
label={t('@astryx.dialog.close')}
|
|
194
207
|
tooltip={t('@astryx.dialog.close')}
|
|
195
|
-
icon={
|
|
208
|
+
icon={
|
|
209
|
+
<Icon
|
|
210
|
+
icon="close"
|
|
211
|
+
color="inherit"
|
|
212
|
+
{...themeProps('dialog-header-close-icon')}
|
|
213
|
+
/>
|
|
214
|
+
}
|
|
196
215
|
onClick={() => {
|
|
197
216
|
onOpenChange?.(false);
|
|
198
217
|
}}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: 3
|
|
3
|
+
template_version: 1
|
|
4
|
+
kind: module
|
|
5
|
+
id: module:Dialog/DialogHeader
|
|
6
|
+
authority: current
|
|
7
|
+
archive_reason: null
|
|
8
|
+
superseded_by: null
|
|
9
|
+
approved_by: cixzhang
|
|
10
|
+
approved_at: 2026-09-14
|
|
11
|
+
owners: [cixzhang, imdreamrunner]
|
|
12
|
+
review_triggers: [theming, layout]
|
|
13
|
+
verified_by:
|
|
14
|
+
[
|
|
15
|
+
packages/core/src/Dialog/DialogHeader.test.tsx,
|
|
16
|
+
packages/core/src/theme/themingTargets.test.ts,
|
|
17
|
+
scripts/check-knowledge.mjs,
|
|
18
|
+
]
|
|
19
|
+
parent_component: component:Dialog
|
|
20
|
+
references: [architecture:component-theming-surface]
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# DialogHeader module contract
|
|
24
|
+
|
|
25
|
+
## Intent
|
|
26
|
+
|
|
27
|
+
`DialogHeader` presents the title region of a Dialog and its optional close
|
|
28
|
+
control. This record owns the stable header-row, title-block, and close-icon
|
|
29
|
+
anatomy and their public theming targets.
|
|
30
|
+
|
|
31
|
+
## Compatibility and migration
|
|
32
|
+
|
|
33
|
+
- Released default preserved: `yes`
|
|
34
|
+
- Compatibility class: additive theme targets only; default layout, paint,
|
|
35
|
+
interaction, accessibility, and public props remain unchanged
|
|
36
|
+
- Migration decision: none
|
|
37
|
+
|
|
38
|
+
Consumer migration instructions belong in consumer docs and release notes.
|
|
39
|
+
|
|
40
|
+
## Ownership boundary
|
|
41
|
+
|
|
42
|
+
**Owns**
|
|
43
|
+
|
|
44
|
+
- The header row that arranges title content and trailing controls.
|
|
45
|
+
- The title block that groups the title and optional subtitle.
|
|
46
|
+
- The close icon rendered for the optional close action.
|
|
47
|
+
- The `dialog-header`, `dialog-header-title-block`, and
|
|
48
|
+
`dialog-header-close-icon` targets.
|
|
49
|
+
|
|
50
|
+
**Does not own / non-goals**
|
|
51
|
+
|
|
52
|
+
- Dialog modality, dismissal policy, or focus lifecycle — owned by
|
|
53
|
+
`component:Dialog` and its linked system records.
|
|
54
|
+
- Generic Button or Icon presentation outside this module — owned by those
|
|
55
|
+
components.
|
|
56
|
+
- New public props or default visual changes.
|
|
57
|
+
|
|
58
|
+
## Public API and concepts
|
|
59
|
+
|
|
60
|
+
No component prop changes. This contract adds three public theming surfaces for
|
|
61
|
+
existing anatomy.
|
|
62
|
+
|
|
63
|
+
| Concept | Closed values or states | Meaning | Default | Owner | Stability |
|
|
64
|
+
| --- | --- | --- | --- | --- | --- |
|
|
65
|
+
| Header row target | present | Styles the row that arranges title content and controls | Existing row visuals | `module:Dialog/DialogHeader` | stable |
|
|
66
|
+
| Title block target | present | Styles the title/subtitle grouping element | Existing title stack visuals | `module:Dialog/DialogHeader` | stable |
|
|
67
|
+
| Close icon target | present when close action renders | Styles the close glyph itself | Existing medium Icon visuals | `module:Dialog/DialogHeader` | stable |
|
|
68
|
+
|
|
69
|
+
## Behavioral contract
|
|
70
|
+
|
|
71
|
+
| ID | Invariant | Basis | Review state |
|
|
72
|
+
| --- | --- | --- | --- |
|
|
73
|
+
| FR1 | The header row MUST carry `dialog-header` on the element that applies its row layout and gap. | Current implementation and owner decision | settled |
|
|
74
|
+
| FR2 | The title block MUST carry `dialog-header-title-block` on the element that applies its title/subtitle layout and gap. | Current implementation and owner decision | settled |
|
|
75
|
+
| FR3 | The rendered close Icon MUST carry `dialog-header-close-icon` on the glyph element that applies icon presentation. | Current implementation and owner decision | settled |
|
|
76
|
+
| FR4 | Omitting `onOpenChange` MUST continue to omit the close action and its optional close-icon anatomy. | Released behavior | settled |
|
|
77
|
+
|
|
78
|
+
### Transformation and precedence order
|
|
79
|
+
|
|
80
|
+
- The module applies each target to its owning element before normal theme CSS
|
|
81
|
+
resolves through the shared theming pipeline.
|
|
82
|
+
|
|
83
|
+
### Performance and resources
|
|
84
|
+
|
|
85
|
+
- The additive class names and metadata introduce no listener, observer, timer,
|
|
86
|
+
measurement, or asynchronous resource.
|
|
87
|
+
|
|
88
|
+
## Accessibility contract
|
|
89
|
+
|
|
90
|
+
- Theme targets MUST NOT change the title's role, focus behavior, dialog naming,
|
|
91
|
+
or the close button's accessible name and operation.
|
|
92
|
+
|
|
93
|
+
## Design relationships
|
|
94
|
+
|
|
95
|
+
| Anatomy or state | Design requirement | Representation authority | Module contract |
|
|
96
|
+
| --- | --- | --- | --- |
|
|
97
|
+
| Header row | Owns arrangement and spacing among title content and controls. | This module | FR1 |
|
|
98
|
+
| Title block | Owns title/subtitle grouping and spacing. | This module | FR2 |
|
|
99
|
+
| Close icon | Owns the close glyph's visual box inside the Button-owned action. | This module with Icon rendering | FR3, FR4 |
|
|
100
|
+
|
|
101
|
+
### Theming anatomy
|
|
102
|
+
|
|
103
|
+
<!-- anatomy-theming:v1 -->
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"Header row": {"target": "dialog-header"},
|
|
108
|
+
"Title block": {"target": "dialog-header-title-block"},
|
|
109
|
+
"Close icon": {"target": "dialog-header-close-icon"}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Parent and system relationships
|
|
114
|
+
|
|
115
|
+
- `component:Dialog` owns modal behavior and the aggregate Dialog contract.
|
|
116
|
+
- `component:Layout` owns the composed LayoutHeader region outside the three
|
|
117
|
+
module-owned inner parts.
|
|
118
|
+
- `component:Button` owns the close action's control behavior and outer button
|
|
119
|
+
presentation.
|
|
120
|
+
- `component:Icon` renders the glyph; this module guarantees a distinct target
|
|
121
|
+
for the close icon's DialogHeader-specific visual contract.
|
|
122
|
+
- `architecture:component-theming-surface` owns target qualification, anatomy
|
|
123
|
+
mapping, and the requirement that each target sits on its painter.
|
|
124
|
+
|
|
125
|
+
## Verification map
|
|
126
|
+
|
|
127
|
+
| Contract | Verification | Representative states | Mutation or failure expectation |
|
|
128
|
+
| --- | --- | --- | --- |
|
|
129
|
+
| FR1–FR3 | `DialogHeader.test.tsx`, target inventory, generated probe theme, and source inspection | title only, title/subtitle, close action | A target is missing, undocumented, or moved away from its owning painter. |
|
|
130
|
+
| FR4 | Existing close-button presence tests | with and without `onOpenChange` | The optional target renders without the optional close action. |
|
|
131
|
+
| Theming anatomy map | `scripts/check-knowledge.mjs` | all three module anatomy entries and targets | Anatomy, docs, runtime targets, and the module map drift. |
|
|
132
|
+
|
|
133
|
+
## Decision log
|
|
134
|
+
|
|
135
|
+
### DEC-1 — Header targets follow stable anatomy and visual ownership
|
|
136
|
+
|
|
137
|
+
**Reference:** `module:Dialog/DialogHeader/DEC-1`
|
|
138
|
+
**Decider:** cixzhang, 2026-09-14
|
|
139
|
+
|
|
140
|
+
Each public target represents documented anatomy and is applied to the element
|
|
141
|
+
that owns the corresponding visuals. The header row, title block, and close icon
|
|
142
|
+
meet that admission rule and are approved as additive targets.
|
|
143
|
+
|
|
144
|
+
## Open questions
|
|
145
|
+
|
|
146
|
+
None.
|
|
147
|
+
|
|
148
|
+
## Content boundary
|
|
149
|
+
|
|
150
|
+
This record does not duplicate consumer signatures/examples, parent Dialog
|
|
151
|
+
behavior, implementation steps, or shared theming rules. It links to their
|
|
152
|
+
canonical owners.
|
|
@@ -142,6 +142,7 @@ export const docs = {
|
|
|
142
142
|
theming: {
|
|
143
143
|
targets: [
|
|
144
144
|
{className: 'astryx-file-input', visualProps: ['mode', 'status']},
|
|
145
|
+
{className: 'astryx-file-input-icon', visualProps: ['mode']},
|
|
145
146
|
],
|
|
146
147
|
},
|
|
147
148
|
usage: {
|
|
@@ -270,6 +271,7 @@ export const docsZh = {
|
|
|
270
271
|
theming: {
|
|
271
272
|
targets: [
|
|
272
273
|
{className: 'astryx-file-input', visualProps: ['mode', 'status']},
|
|
274
|
+
{className: 'astryx-file-input-icon', visualProps: ['mode']},
|
|
273
275
|
],
|
|
274
276
|
},
|
|
275
277
|
usage: {
|