@astryxdesign/core 0.6.1 → 0.6.2-canary.176dfa6
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/BottomSheet/BottomSheet.d.ts +1 -1
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +3 -1
- package/dist/BottomSheet/BottomSheetPanel.d.ts +7 -5
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +56 -19
- 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/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +46 -30
- 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/ToggleButton/ToggleButton.d.ts +2 -1
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +7 -1
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +15 -6
- package/dist/astryx.css +5 -2
- package/dist/hooks/scrollKeyboardDelegation.d.ts +3 -0
- package/dist/hooks/scrollKeyboardDelegation.d.ts.map +1 -0
- package/dist/hooks/scrollKeyboardDelegation.js +146 -0
- package/dist/hooks/useScrollableArea.d.ts +6 -2
- package/dist/hooks/useScrollableArea.d.ts.map +1 -1
- package/dist/hooks/useScrollableArea.js +17 -5
- package/locales/en.json +16 -0
- package/locales/pseudo.json +12 -0
- package/package.json +7 -5
- package/scripts/agent-doc-state.mjs +1 -1
- package/src/BottomSheet/BottomSheet.doc.mjs +8 -1
- package/src/BottomSheet/BottomSheet.spec.md +46 -20
- package/src/BottomSheet/BottomSheet.test.tsx +6 -3
- package/src/BottomSheet/BottomSheet.tsx +3 -1
- package/src/BottomSheet/BottomSheetKeyboard.test.tsx +195 -0
- package/src/BottomSheet/BottomSheetPanel.test.tsx +11 -1
- package/src/BottomSheet/BottomSheetPanel.tsx +49 -16
- package/src/BottomSheet/__tests__/BottomSheetKeyboard.a11y.browser.spec.ts +344 -0
- 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/PowerSearch/PowerSearchEditPopover.test.tsx +150 -1
- package/src/PowerSearch/PowerSearchEditPopover.tsx +51 -28
- package/src/ScrollableArea/modules/useScrollableArea.spec.md +50 -22
- 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/ToggleButton/ToggleButton.test.tsx +133 -0
- package/src/ToggleButton/ToggleButton.tsx +9 -2
- package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +209 -0
- package/src/Tokenizer/Tokenizer.spec.md +142 -75
- package/src/Typeahead/BaseTypeahead.spec.md +4 -3
- package/src/Typeahead/BaseTypeahead.tsx +15 -6
- package/src/Typeahead/Typeahead.test.tsx +53 -0
- package/src/hooks/scrollKeyboardDelegation.test.ts +155 -0
- package/src/hooks/scrollKeyboardDelegation.ts +233 -0
- package/src/hooks/useScrollableArea.doc.mjs +15 -3
- package/src/hooks/useScrollableArea.test.tsx +59 -1
- package/src/hooks/useScrollableArea.ts +34 -10
- package/src/theme/derivedVarRegistry.test.ts +6 -4
|
@@ -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: {
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: 3
|
|
3
|
+
template_version: 4
|
|
4
|
+
kind: component
|
|
5
|
+
id: component:FileInput
|
|
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]
|
|
13
|
+
verified_by:
|
|
14
|
+
[
|
|
15
|
+
packages/core/src/FileInput/FileInput.test.tsx,
|
|
16
|
+
packages/core/src/theme/themingTargets.test.ts,
|
|
17
|
+
scripts/check-knowledge.mjs,
|
|
18
|
+
]
|
|
19
|
+
modules: []
|
|
20
|
+
families: [family:input-fields]
|
|
21
|
+
design_specs: []
|
|
22
|
+
architecture: [architecture:component-theming-surface]
|
|
23
|
+
contributing: []
|
|
24
|
+
system_specs: []
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# FileInput component contract
|
|
28
|
+
|
|
29
|
+
## Intent
|
|
30
|
+
|
|
31
|
+
FileInput presents a labelled file-selection field in compact input or dropzone
|
|
32
|
+
form. This contract records its current consumer anatomy and approves separate theme
|
|
33
|
+
ownership for the upload affordance that FileInput paints through Icon.
|
|
34
|
+
|
|
35
|
+
## Compatibility and migration
|
|
36
|
+
|
|
37
|
+
- Released default preserved: `yes`
|
|
38
|
+
- Compatibility class: additive public theming target; no existing target,
|
|
39
|
+
runtime default, DOM, prop, interaction, or accessibility behavior changes
|
|
40
|
+
- Controlled/uncontrolled behavior: unchanged; FileInput remains controlled
|
|
41
|
+
- Migration decision: `component:FileInput/DEC-1`
|
|
42
|
+
|
|
43
|
+
Consumer migration instructions belong in consumer docs and release notes.
|
|
44
|
+
|
|
45
|
+
## Ownership boundary
|
|
46
|
+
|
|
47
|
+
**Owns**
|
|
48
|
+
|
|
49
|
+
- The visible file-selection surface and its input/dropzone mode.
|
|
50
|
+
- Whether, where, and at what default size the upload affordance appears.
|
|
51
|
+
- Reflecting FileInput's mode on its locally owned theme targets.
|
|
52
|
+
|
|
53
|
+
**Does not own / non-goals**
|
|
54
|
+
|
|
55
|
+
- The upload artwork or Icon's base color, size, and accessibility semantics —
|
|
56
|
+
owned by `component:Icon`.
|
|
57
|
+
- Label, description, clear-control, and validation-message presentation — owned
|
|
58
|
+
by `component:Field` and `component:FieldStatus`.
|
|
59
|
+
- Loading-indicator presentation — owned by `component:Spinner`.
|
|
60
|
+
- A new prop, variant, icon slot, or custom property.
|
|
61
|
+
|
|
62
|
+
## Public concepts
|
|
63
|
+
|
|
64
|
+
No consumer prop changes. The `file-input-icon` target gives themes a
|
|
65
|
+
same-element seam for the Upload icon and reflects the existing `mode` axis. The
|
|
66
|
+
existing `file-input` target remains on the visible selection surface and keeps
|
|
67
|
+
its `mode` and `status` axes.
|
|
68
|
+
|
|
69
|
+
## Behavioral and layout contract
|
|
70
|
+
|
|
71
|
+
| ID | Candidate invariant | Basis | Review state |
|
|
72
|
+
| --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | ------------------------------- |
|
|
73
|
+
| FR1 | The visible selection surface carries `file-input` and reflects the existing `mode` and resolved status. | Current source, public docs, and focused tests | Verified current behavior |
|
|
74
|
+
| FR2 | When not loading, input mode renders an upload affordance at the small Icon size. Dropzone mode renders it at the medium Icon size only while no file is selected. | Current source and focused tests | Verified current behavior |
|
|
75
|
+
| FR3 | Icon owns the rendered glyph's base size, color, and accessibility semantics; FileInput owns the affordance's mode-dependent placement and default size. | Current composition and component boundaries | Verified current composition |
|
|
76
|
+
| FR4 | The rendered upload affordance MUST carry `file-input-icon` with the existing `mode` reflected, so a theme can restyle the glyph box without structural selectors or changing every Icon that uses the same artwork. | Owner-approved target contract | Approved additive contract |
|
|
77
|
+
| FR5 | Adding the target MUST NOT change the default artwork, computed layout, interaction, file-selection behavior, accessible name, or decorative Icon semantics. | Compatibility policy and focused regression tests | Required compatibility behavior |
|
|
78
|
+
|
|
79
|
+
### Allowed variation
|
|
80
|
+
|
|
81
|
+
- **AV1 — Theme paint.** A theme may change standard visual
|
|
82
|
+
properties such as the upload glyph's size or color through
|
|
83
|
+
`file-input-icon`; FileInput still owns whether and where the affordance renders.
|
|
84
|
+
- **AV2 — Artwork.** Icon registry and future icon-slot decisions may change the
|
|
85
|
+
artwork without changing this CSS target's ownership of the painted glyph box.
|
|
86
|
+
|
|
87
|
+
### Representative states
|
|
88
|
+
|
|
89
|
+
| State | Required invariant | Allowed variation |
|
|
90
|
+
| ------------------------ | ------------------------------------------------------------------------ | --------------------------------------- |
|
|
91
|
+
| Input, empty or selected | Small upload affordance renders on the input surface when not loading. | Files, placeholder, status, theme paint |
|
|
92
|
+
| Dropzone, empty | Medium upload affordance renders above the placeholder when not loading. | Drag state, placeholder, theme paint |
|
|
93
|
+
| Dropzone, selected | File names replace the upload affordance. | File names and status |
|
|
94
|
+
| Loading | Spinner replaces the upload affordance. | Mode and loading presentation |
|
|
95
|
+
|
|
96
|
+
### Transformation and precedence order
|
|
97
|
+
|
|
98
|
+
- **ORD1 — Content selection.** Resolve loading and selected-file state, choose
|
|
99
|
+
input or dropzone content, then render the mode-sized upload affordance only in
|
|
100
|
+
the states recorded by FR2.
|
|
101
|
+
- **ORD2 — Theme composition.** Icon applies its base size and color, then the
|
|
102
|
+
same-element FileInput target participates in the existing theme layer and
|
|
103
|
+
standard Icon styling merge order.
|
|
104
|
+
|
|
105
|
+
### Performance and resources
|
|
106
|
+
|
|
107
|
+
- **PR1 — No new work.** The additive target performs no measurement, listener,
|
|
108
|
+
observer, state update, or additional render pass.
|
|
109
|
+
|
|
110
|
+
## Accessibility contract
|
|
111
|
+
|
|
112
|
+
The upload affordance remains decorative. The existing focusable file-selection
|
|
113
|
+
trigger, label, description, required/invalid state, disabled explanation, and
|
|
114
|
+
selection announcements remain unchanged.
|
|
115
|
+
|
|
116
|
+
## Design relationships
|
|
117
|
+
|
|
118
|
+
| Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
|
|
119
|
+
| ---------------- | ----------------------------------------------------------------------------------- | ---------------------------------------- | -------------- | ------------------ |
|
|
120
|
+
| Drop zone | Presents the visible file-selection surface in input or dropzone form. | Current source and public docs | Prominent | FR1 |
|
|
121
|
+
| Upload icon | Hints at the upload action and changes default size with the selected mode. | Current source and owner-approved target | Supporting | FR2, FR3, FR4 |
|
|
122
|
+
| Shared feedback | Uses Field, FieldStatus, and Spinner for labels, validation, and loading treatment. | Current shared composition | Supporting | FR5 |
|
|
123
|
+
|
|
124
|
+
### Theming anatomy
|
|
125
|
+
|
|
126
|
+
<!-- anatomy-theming:v1 -->
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"Label": {
|
|
131
|
+
"delegatesTo": {"owner": "component:Field", "target": "field-label"}
|
|
132
|
+
},
|
|
133
|
+
"Description": {
|
|
134
|
+
"none": {
|
|
135
|
+
"reason": "unsettled: No current public target reaches the stable Description; future exposure still needs an owner decision"
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
"Drop zone": {"target": "file-input"},
|
|
139
|
+
"Upload icon": {"target": "file-input-icon"},
|
|
140
|
+
"Placeholder": {"inherits": "file-input"},
|
|
141
|
+
"File name display": {"inherits": "file-input"},
|
|
142
|
+
"Clear button": {
|
|
143
|
+
"delegatesTo": {
|
|
144
|
+
"owner": "component:Field",
|
|
145
|
+
"target": "input-clear-button"
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
"Spinner": {
|
|
149
|
+
"delegatesTo": {"owner": "component:Spinner", "target": "spinner"}
|
|
150
|
+
},
|
|
151
|
+
"Status message": {
|
|
152
|
+
"delegatesTo": {
|
|
153
|
+
"owner": "component:FieldStatus",
|
|
154
|
+
"target": "field-status"
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The `file-input-icon` disposition records the approved target state. Icon still
|
|
161
|
+
owns the general `icon` target and base glyph semantics; FileInput's narrower
|
|
162
|
+
target owns only this stable upload position and its existing mode distinction.
|
|
163
|
+
|
|
164
|
+
## Family and system relationships
|
|
165
|
+
|
|
166
|
+
- `family:input-fields` owns shared labelled-field and validation behavior.
|
|
167
|
+
- `architecture:component-theming-surface` owns target qualification, anatomy
|
|
168
|
+
mapping, and the requirement that public targets sit on stable painted parts.
|
|
169
|
+
- Field, FieldStatus, Icon, and Spinner retain their existing public target
|
|
170
|
+
contracts when composed by FileInput.
|
|
171
|
+
|
|
172
|
+
## Verification map
|
|
173
|
+
|
|
174
|
+
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
|
|
175
|
+
| ------------------- | ----------------------------------------------------------------- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------- |
|
|
176
|
+
| FR1, FR2 | `FileInput.test.tsx` rendering and target suites | Input/dropzone; empty/selected/loading | Moving the root target or changing when/at what size the affordance renders breaks focused assertions. | `audit:FileInput/theming` |
|
|
177
|
+
| FR3, FR4, FR5 | `FileInput.test.tsx`, `themingTargets.test.ts`, probe-theme check | Both modes and same-element Icon target | Missing the target, reflecting the wrong mode, or moving it off the glyph fails source/docs/probe coverage. | `audit:FileInput/theming` |
|
|
178
|
+
| Theming anatomy map | `scripts/check-knowledge.mjs` | Nine anatomy entries and two locally owned targets | Missing, extra, prefixed, stale, or unclaimed current mappings fail validation. | `audit:FileInput/anatomy` |
|
|
179
|
+
|
|
180
|
+
## Decision log
|
|
181
|
+
|
|
182
|
+
### DEC-1 — Upload icon is stable FileInput theme anatomy
|
|
183
|
+
|
|
184
|
+
**Reference:** `component:FileInput/DEC-1`
|
|
185
|
+
**Decider:** cixzhang, 2026-09-14
|
|
186
|
+
|
|
187
|
+
The upload icon is a stable, consumer-recognizable FileInput affordance whose
|
|
188
|
+
mode-dependent placement and default size belong to FileInput. It receives the
|
|
189
|
+
`file-input-icon` target on the same Icon element that paints the glyph, while
|
|
190
|
+
Icon retains its general target and base glyph semantics.
|
|
191
|
+
|
|
192
|
+
## Open questions
|
|
193
|
+
|
|
194
|
+
None.
|
|
195
|
+
|
|
196
|
+
## Content boundary
|
|
197
|
+
|
|
198
|
+
This file does not duplicate consumer prop tables, examples, implementation
|
|
199
|
+
steps, or shared-component contracts. It links to their owners.
|
|
@@ -119,6 +119,20 @@ describe('FileInput', () => {
|
|
|
119
119
|
expect(screen.getByText('Drop here')).toBeInTheDocument();
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
+
it.each([
|
|
123
|
+
{mode: 'input' as const, size: 'sm'},
|
|
124
|
+
{mode: 'dropzone' as const, size: 'md'},
|
|
125
|
+
])('exposes the upload icon as a $mode theme target', ({mode, size}) => {
|
|
126
|
+
render(
|
|
127
|
+
<FileInput label="Upload" mode={mode} value={null} onChange={() => {}} />,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const icon = document.querySelector('.astryx-file-input-icon');
|
|
131
|
+
expect(icon).toHaveClass('astryx-icon');
|
|
132
|
+
expect(icon).toHaveAttribute('data-mode', mode);
|
|
133
|
+
expect(icon).toHaveAttribute('data-size', size);
|
|
134
|
+
});
|
|
135
|
+
|
|
122
136
|
it('displays selected file name', () => {
|
|
123
137
|
const file = createFile('report.pdf', 1024, 'application/pdf');
|
|
124
138
|
render(<FileInput label="Document" value={file} onChange={() => {}} />);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* @file FileInput.tsx
|
|
7
7
|
* @input Uses React, useId, Field, Icon, Spinner, VisuallyHidden
|
|
8
|
-
* @output Exports FileInput component,
|
|
8
|
+
* @output Exports FileInput component, public types, and its root/icon theme targets
|
|
9
9
|
* @position Core implementation; consumed by index.ts, tested by FileInput.test.tsx
|
|
10
10
|
*
|
|
11
11
|
* SYNC: When modified, update these files to stay in sync:
|
|
@@ -707,7 +707,12 @@ export function FileInput({
|
|
|
707
707
|
}
|
|
708
708
|
return (
|
|
709
709
|
<>
|
|
710
|
-
<Icon
|
|
710
|
+
<Icon
|
|
711
|
+
icon="arrowUp"
|
|
712
|
+
size="md"
|
|
713
|
+
color="secondary"
|
|
714
|
+
{...themeProps('file-input-icon', {mode})}
|
|
715
|
+
/>
|
|
711
716
|
<span {...stylex.props(styles.placeholderText)}>
|
|
712
717
|
{isDragOver ? t('@astryx.fileInput.dropHint') : displayPlaceholder}
|
|
713
718
|
</span>
|
|
@@ -728,7 +733,12 @@ export function FileInput({
|
|
|
728
733
|
}
|
|
729
734
|
return (
|
|
730
735
|
<>
|
|
731
|
-
<Icon
|
|
736
|
+
<Icon
|
|
737
|
+
icon="arrowUp"
|
|
738
|
+
size="sm"
|
|
739
|
+
color="secondary"
|
|
740
|
+
{...themeProps('file-input-icon', {mode})}
|
|
741
|
+
/>
|
|
732
742
|
<span
|
|
733
743
|
{...stylex.props(
|
|
734
744
|
hasFiles ? styles.fileNameText : styles.placeholderText,
|