@santi020k/lumen-astro 3.0.1 → 4.1.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 (109) hide show
  1. package/README.md +424 -7
  2. package/components/AmountField.astro +21 -0
  3. package/components/AnimatedNumber.astro +1 -1
  4. package/components/ApprovalCard.astro +30 -0
  5. package/components/AttachmentList.astro +15 -0
  6. package/components/AttachmentPreview.astro +51 -0
  7. package/components/Avatar.astro +2 -0
  8. package/components/BarChart.astro +95 -31
  9. package/components/BoxPlot.astro +77 -0
  10. package/components/Breadcrumb.astro +2 -1
  11. package/components/BulletChart.astro +64 -0
  12. package/components/Button.astro +1 -1
  13. package/components/Calendar.astro +54 -65
  14. package/components/CalendarHeatmap.astro +76 -0
  15. package/components/Card.astro +4 -0
  16. package/components/ChangeSummary.astro +36 -0
  17. package/components/ChartDatumActions.astro +24 -0
  18. package/components/ChartMotion.astro +11 -0
  19. package/components/Code.astro +18 -3
  20. package/components/CodeTabs.astro +12 -0
  21. package/components/ComboChart.astro +93 -11
  22. package/components/CountryFlag.astro +23 -0
  23. package/components/DataTable.astro +15 -7
  24. package/components/DatePicker.astro +24 -17
  25. package/components/DescriptionDetail.astro +18 -0
  26. package/components/DescriptionItem.astro +18 -0
  27. package/components/DescriptionTerm.astro +18 -0
  28. package/components/DeviceFrame.astro +79 -0
  29. package/components/DialogBody.astro +20 -0
  30. package/components/DialogClose.astro +21 -0
  31. package/components/DialogFooter.astro +20 -0
  32. package/components/DialogHeader.astro +20 -0
  33. package/components/DialogTitle.astro +21 -0
  34. package/components/DumbbellChart.astro +8 -0
  35. package/components/FileUpload.astro +3 -1
  36. package/components/FilterBar.astro +25 -0
  37. package/components/FunnelChart.astro +50 -0
  38. package/components/Grid.astro +2 -2
  39. package/components/Heatmap.astro +61 -12
  40. package/components/Histogram.astro +30 -0
  41. package/components/Icon.astro +7 -3
  42. package/components/ImageComparison.astro +74 -0
  43. package/components/Input.astro +2 -2
  44. package/components/LineChart.astro +122 -66
  45. package/components/ListBox.astro +2 -1
  46. package/components/LollipopChart.astro +8 -0
  47. package/components/MediaFilmstrip.astro +19 -0
  48. package/components/MediaThumbnail.astro +29 -0
  49. package/components/MediaViewport.astro +38 -0
  50. package/components/Mentions.astro +2 -1
  51. package/components/MessageScroller.astro +6 -0
  52. package/components/MotionGroup.astro +19 -0
  53. package/components/NativeSelect.astro +2 -2
  54. package/components/Pagination.astro +2 -1
  55. package/components/PasswordField.astro +1 -1
  56. package/components/PhoneInput.astro +60 -9
  57. package/components/PhoneNumber.astro +19 -0
  58. package/components/PieChart.astro +26 -3
  59. package/components/Progress.astro +2 -4
  60. package/components/PromptComposer.astro +36 -0
  61. package/components/RangeChart.astro +38 -8
  62. package/components/ScatterChart.astro +62 -14
  63. package/components/Segmented.astro +5 -5
  64. package/components/Select.astro +8 -8
  65. package/components/Skeleton.astro +46 -5
  66. package/components/SourceCitation.astro +18 -0
  67. package/components/Sparkline.astro +9 -10
  68. package/components/Spinner.astro +4 -4
  69. package/components/Stack.astro +2 -2
  70. package/components/StreamMessage.astro +26 -0
  71. package/components/Tabs.astro +3 -0
  72. package/components/ToolActivity.astro +17 -0
  73. package/components/TreeSelect.astro +1 -0
  74. package/components/VirtualList.astro +5 -2
  75. package/components/VisualEffect.astro +21 -0
  76. package/components/WaterfallChart.astro +27 -0
  77. package/components/WorldMap.astro +213 -0
  78. package/forms.ts +17 -4
  79. package/index.ts +37 -0
  80. package/internal/ChartInspection.astro +27 -0
  81. package/internal/ComparisonChart.astro +69 -0
  82. package/internal/IntervalChart.astro +66 -0
  83. package/package.json +6 -4
  84. package/runtime/UIPrimitives.astro +739 -711
  85. package/runtime/chart-actions.ts +19 -0
  86. package/runtime/controllers/amount-field.ts +12 -0
  87. package/runtime/controllers/attachments.ts +13 -0
  88. package/runtime/controllers/chart-activation.ts +33 -0
  89. package/runtime/controllers/charts.ts +33 -0
  90. package/runtime/controllers/combobox.ts +39 -0
  91. package/runtime/controllers/comparison-reset.ts +20 -0
  92. package/runtime/controllers/data-table.ts +142 -0
  93. package/runtime/controllers/dialogs.ts +32 -32
  94. package/runtime/controllers/document-navigation.ts +54 -22
  95. package/runtime/controllers/file-upload.ts +89 -0
  96. package/runtime/controllers/image-comparison.ts +53 -0
  97. package/runtime/controllers/list-box-proxy.ts +209 -0
  98. package/runtime/controllers/media-selection.ts +20 -0
  99. package/runtime/controllers/media-viewport.ts +54 -0
  100. package/runtime/controllers/media-workspace.ts +8 -0
  101. package/runtime/controllers/message-scroller.ts +13 -0
  102. package/runtime/controllers/motion.ts +15 -16
  103. package/runtime/controllers/optional-media.ts +29 -0
  104. package/runtime/controllers/phone-input.ts +128 -39
  105. package/runtime/controllers/progress.ts +52 -0
  106. package/runtime/controllers/visual-interactions.ts +75 -0
  107. package/runtime/controllers/world-map.ts +154 -0
  108. package/styles/critical.css +696 -66
  109. package/styles/lumen.css +101 -0
package/README.md CHANGED
@@ -1,10 +1,79 @@
1
- # @santi020k/lumen-astro
1
+ <p align="center">
2
+ <a href="https://lumen.santi020k.com">
3
+ <picture>
4
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/santi020k/lumen/main/docs/assets/readme/package-dark.svg">
5
+ <img src="https://raw.githubusercontent.com/santi020k/lumen/main/docs/assets/readme/package-light.svg" alt="Lumen UI — Web. Native. Thoughtfully connected." width="1200" height="184">
6
+ </picture>
7
+ </a>
8
+ </p>
9
+
10
+ <h1 align="center">Lumen UI · Astro</h1>
11
+
12
+ <p align="center">Reference components · Progressive enhancement · Standalone CSS</p>
13
+
14
+ <p align="center">
15
+ <a href="https://www.npmjs.com/package/@santi020k/lumen-astro"><img src="https://img.shields.io/npm/v/@santi020k/lumen-astro?style=flat-square&color=0369a0" alt="npm version"></a>
16
+ <a href="https://github.com/santi020k/lumen/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-13967e?style=flat-square" alt="MIT license"></a>
17
+ </p>
18
+
19
+ <p align="center">
20
+ <a href="https://lumen.santi020k.com/docs/frameworks/astro">Documentation</a>
21
+ ·
22
+ <a href="https://www.npmjs.com/package/@santi020k/lumen-astro">npm</a>
23
+ ·
24
+ <a href="https://github.com/santi020k/lumen/tree/main/packages/astro">Source</a>
25
+ ·
26
+ <a href="https://github.com/santi020k/lumen/issues">Issues</a>
27
+ </p>
28
+
29
+ **Package:** `@santi020k/lumen-astro`
30
+
31
+ **On this page:** [Install](#install) · [Appearance presets](#appearance-presets) · [Resources](#resources)
32
+
33
+ <details>
34
+ <summary>Browse all sections</summary>
35
+
36
+ - [Install](#install)
37
+ - [Appearance presets](#appearance-presets)
38
+ - [Compound interactive components](#compound-interactive-components)
39
+ - [Context navigation](#context-navigation)
40
+ - [Forms and Astro Actions](#forms-and-astro-actions)
41
+ - [Error states](#error-states)
42
+ - [Long-form navigation](#long-form-navigation)
43
+ - [Data visualization](#data-visualization)
44
+ - [Code tabs](#code-tabs)
45
+ - [Optimized images](#optimized-images)
46
+ - [Kanban boards](#kanban-boards)
47
+ - [Motion](#motion)
48
+ - [Glass surfaces](#glass-surfaces)
49
+ - [Semantic stat roots](#semantic-stat-roots)
50
+ - [Consumer composition recipes](#consumer-composition-recipes)
51
+ - [Exact amount fields](#exact-amount-fields)
52
+ - [Phone presentation in v4](#phone-presentation-in-v4)
53
+ - [Combobox keyboard behavior](#combobox-keyboard-behavior)
54
+ - [Content flow](#content-flow)
55
+ - [Attachment composition](#attachment-composition)
56
+ - [Chart datum actions](#chart-datum-actions)
57
+ - [Dashboard composition](#dashboard-composition)
58
+ - [Visual interactions and product blocks](#visual-interactions-and-product-blocks)
59
+ - [Device demonstrations](#device-demonstrations)
60
+ - [World map](#world-map)
61
+ - [Studio media workspace](#studio-media-workspace)
62
+ - [Resources](#resources)
63
+
64
+ </details>
2
65
 
3
66
  Production-ready Astro primitives for Lumen UI. The stylesheet is standalone CSS, so no Tailwind
4
67
  configuration is required to render the components.
5
68
 
69
+ Form controls use `visualSize` (`visual-size` in Elements) with `default`, `sm` and `lg`.
70
+ Select, PhoneInput and Segmented follow Input and NativeSelect; numeric input/select `size` keeps
71
+ its native meaning. See the [v4 migration guide](https://github.com/santi020k/lumen/blob/main/docs/migrating-v3-to-v4.md#form-control-visual-sizing).
72
+
6
73
  ## Install
7
74
 
75
+ Requires Astro 5 or newer in the consuming application.
76
+
8
77
  ```bash
9
78
  pnpm add @santi020k/lumen-astro
10
79
  ```
@@ -53,11 +122,11 @@ import UIPrimitives from '@santi020k/lumen-astro/runtime'
53
122
 
54
123
  ```astro
55
124
  ---
56
- import { Button, Card, Input } from '@santi020k/lumen-astro'
125
+ import { Button, Card, Input, Label } from '@santi020k/lumen-astro'
57
126
  ---
58
127
 
59
128
  <Card>
60
- <label for="email">Email</label>
129
+ <Label for="email">Email</Label>
61
130
  <Input id="email" type="email" placeholder="you@example.com" />
62
131
  <Button>Subscribe</Button>
63
132
  </Card>
@@ -74,7 +143,10 @@ import { Button, Card, Input } from '@santi020k/lumen-astro'
74
143
  `PhoneInput` generates localized country names, supplementary flags, and calling codes when custom
75
144
  `countries` are not provided. Mount `UIPrimitives` once to enable as-you-type formatting,
76
145
  metadata-backed validation, automatic country detection for pasted international numbers, and the
77
- `ui:phone-change` event with a `LumenPhoneNumber` detail.
146
+ `ui:phone-change` event with a `LumenPhoneNumber` detail. Validation messages and inherited locale
147
+ resolve in the phone control's owning document, including after iframe adoption. Repeated runtime
148
+ initialization binds adopted phone controls to their current reset root; reset synchronization follows
149
+ the current form owner and honors cancellation without duplicating edit listeners.
78
150
 
79
151
  The phone metadata and normalization controller are selector-loaded only when a rendered page
80
152
  contains `PhoneInput`; pages without the component do not evaluate or download that controller.
@@ -83,16 +155,36 @@ contains `PhoneInput`; pages without the component do not evaluate or download t
83
155
  <PhoneInput countryValue="CO" locale="en-US" name="hospitalPhone" />
84
156
  ```
85
157
 
158
+ ## Appearance presets
159
+
160
+ Set `data-lumen-preset="studio"` and an optional `data-lumen-scheme="light|dark"` on the application root. See [appearance presets](https://github.com/santi020k/lumen/blob/main/docs/appearance-presets.md) for Default, Studio, Glass and scoped customization.
161
+
86
162
  ## Compound interactive components
87
163
 
164
+ DatePicker, Calendar, and InputOTP enhancement skips markup marked `data-ui-owner`, such as the
165
+ matching React controls, so those islands keep their ids, labels, and state before and after hydration.
166
+
88
167
  Popover, dropdown menu, tabs, and tooltip parts expose the DOM and ARIA contracts consumed by the
89
168
  shared progressive-enhancement runtime. Prefer these parts over recreating roles and `data-ui-*`
90
169
  attributes by hand.
91
170
 
171
+ Disclosure keyboard navigation skips hidden or inert regions, invisible controls, and native
172
+ disabled controls, including a disabled fieldset. Available controls in a fieldset's first legend
173
+ retain their native keyboard behavior; removing `inert` makes a region available again.
174
+
175
+ Mentions keeps suggestion navigation on the textarea through `aria-activedescendant`; suggestion
176
+ buttons are excluded from the Tab sequence. Returning focus before the delayed blur dismissal
177
+ preserves reopened suggestions. Static Toast markup is enhanced again after client
178
+ navigation, retaining Escape dismissal without duplicate listeners.
179
+
92
180
  Tabs keep the selected trigger visible when a narrow tab list scrolls horizontally and emit a
93
181
  typed `ui:tabs-change` event. Import `LumenTabsChangeDetail` or `LumenTabsChangeEvent` from this
94
182
  package when application behavior follows the selected value.
95
183
 
184
+ Keyboard tab navigation skips disabled triggers and stays within the current group when tabs are nested.
185
+ Dialogs preserve native `autofocus`, restore their connected opener on close, and dismiss only a
186
+ backdrop press that starts and ends outside the dialog. Alert dialogs retain explicit dismissal.
187
+
96
188
  ```astro
97
189
  ---
98
190
  import {
@@ -211,7 +303,7 @@ announcement.
211
303
  </ErrorState>
212
304
  ```
213
305
 
214
- See the repository [Astro error-handling guide](../../docs/error-handling.md#astro) for the complete
306
+ See the repository [Astro error-handling guide](https://github.com/santi020k/lumen/blob/main/docs/error-handling.md#astro) for the complete
215
307
  example, retry ownership, announcement behavior, and verification guidance.
216
308
 
217
309
  Use `Icon` for Lucide icons by name across framework adapters.
@@ -247,6 +339,24 @@ import { Anchor, CopyButton, ScrollProgress } from '@santi020k/lumen-astro'
247
339
  ]} />
248
340
  ```
249
341
 
342
+ Enhanced `DatePicker` controls mirror native input `aria-invalid`, `aria-describedby`, and
343
+ `aria-errormessage` attributes onto the visible trigger, including later attribute changes.
344
+ Valid updates and uncanceled resets clear generated validation feedback while preserving explicit
345
+ application errors.
346
+
347
+ `Skeleton` accepts `shape` (`rectangle`, `text`, `circle`), `width`, `height`, and `animation`
348
+ (`shimmer`, `pulse`, `none`). Numeric sizes are pixels; CSS lengths, percentages, and math or
349
+ custom-property expressions also work. Explicit styles take precedence. Empty placeholders default
350
+ to 1rem high, while text and element content can grow naturally. Reduced motion stops animations.
351
+ Unnamed Skeletons and Spinners are decorative. Give a standalone indicator an accessible name, or
352
+ wrap a group in one named status region with `VisuallyHidden` text.
353
+
354
+ ```astro
355
+ <Skeleton shape="circle" width={40} />
356
+ <Skeleton shape="text" width="80%" />
357
+ <Skeleton animation="pulse" height={8} width="60%" />
358
+ ```
359
+
250
360
  `Progress` can be updated after hydration by dispatching `ui:progress-change` from its root with
251
361
  `{ value, max? }`. Use `CopyButton` to copy arbitrary text or a referenced element without giving
252
362
  non-code content `Code` semantics.
@@ -266,6 +376,17 @@ sync with the accessible label; free-form default children remain the idle prese
266
376
 
267
377
  ## Data visualization
268
378
 
379
+ The web visualization milestone adds `WaterfallChart` for signed changes and explicit totals, and
380
+ `Histogram` for precomputed numeric bins (`frequency="density"` for unequal widths). Line charts
381
+ support explicit continuous axes, annotations, optional keyboard/pointer/touch inspection, and
382
+ synchronized cursors. Heatmaps show labeled axes, a color legend, and explicit missing cells.
383
+ See the [visualization contracts](https://github.com/santi020k/lumen/blob/main/docs/data-visualization.md) and
384
+ [interactive web example](https://lumen.santi020k.com/docs/web/data-visualization).
385
+
386
+ Set `interactive` on `LineChart` and mount `UIPrimitives` to enable inspection and legend buttons.
387
+ Static charts continue to render without client JavaScript. After moving an enhanced chart into another
388
+ document, rerun `UIPrimitives` initialization so cursor synchronization follows that document's peers.
389
+
269
390
  Use `Sparkline` beside a metric, `BarChart` for categorical comparison, `LineChart` for ordered
270
391
  trends, `PieChart` for a small part-to-whole breakdown, `ScatterChart` for numeric relationships,
271
392
  `Heatmap` for a matrix, `RangeChart` for intervals, and `ComboChart` for a shared-domain mix of bars,
@@ -291,7 +412,7 @@ const series = [{
291
412
  `variant="donut"` and also supports `variant="pie"`.
292
413
 
293
414
  Every chart includes a factual screen-reader summary by default and accepts `summary` for more
294
- useful domain context. See [data visualization](../../docs/data-visualization.md) for selection,
415
+ useful domain context. See [data visualization](https://github.com/santi020k/lumen/blob/main/docs/data-visualization.md) for selection,
295
416
  missing-data, live-data, and accessibility guidance.
296
417
 
297
418
  Lucide intentionally excludes brand marks. For GitHub and other project-specific icons, put the
@@ -395,6 +516,17 @@ content readable without JavaScript and honor reduced-motion preferences.
395
516
  <AnimatedNumber decimals={1} suffix="%" value={99.8} />
396
517
  ```
397
518
 
519
+ With the optional motion stylesheet, native `Collapsible` and `Accordion` content smoothly expands and collapses in browsers supporting
520
+ `::details-content` and `interpolate-size`. Other browsers retain the immediate native toggle.
521
+ Dialogs, drawers, sheets, menus, and toasts retain their existing shared-token transitions.
522
+
523
+ For optional insertion, removal, or state feedback, use
524
+ [`animateLumenPresence`](https://github.com/santi020k/lumen/blob/main/packages/core/README.md#presence-motion) on a DOM reference. Applications keep
525
+ ownership of state, focus, and DOM removal. Set `data-ui-motion="reduce"` on a container for a local
526
+ reduced-motion preference; the system preference is always respected.
527
+ Try dialogs, bottom drawers, and side sheets in the [motion playground](https://lumen.santi020k.com/docs/motion-playground).
528
+ System reduced motion shortens both panel and backdrop transitions.
529
+
398
530
  ## Glass surfaces
399
531
 
400
532
  Lumen includes glassmorphism tokens and reusable classes in the shared stylesheet. Cards use a
@@ -425,7 +557,292 @@ or `variant="glass"` for selective translucency.
425
557
 
426
558
  ## Consumer composition recipes
427
559
 
428
- See [consumer UI recipes](../../docs/consumer-ui-recipes.md) for static React icons, responsive
560
+ See [consumer UI recipes](https://github.com/santi020k/lumen/blob/main/docs/consumer-ui-recipes.md) for static React icons, responsive
429
561
  record tables, keyboard-aware native sheets, whole-unit amount fields, adaptive editors, and
430
562
  asynchronous action states. Each recipe identifies the public primitives and the behavior that
431
563
  remains owned by the application.
564
+
565
+ ## Exact amount fields
566
+
567
+ `AmountField` formats an ASCII decimal-string `value` in the requested locale and submits a
568
+ complete ungrouped value through its named hidden input. Include `UIPrimitives` for editing,
569
+ validity and reset behavior. Domain validation and monetary units remain application-owned.
570
+
571
+ ```astro
572
+ <AmountField name="amount" locale="es-CO" value="1234.50" aria-label="Amount COP" />
573
+ <UIPrimitives />
574
+ ```
575
+
576
+ See [consumer workflows](https://github.com/santi020k/lumen/blob/main/docs/consumer-ui-recipes.md#executable-consumer-workflows).
577
+
578
+ ### Accessible documentation controls
579
+
580
+ `Code` and `CodeTabs` accept `copyLabel`, `copiedLabel`, `errorLabel`, and
581
+ `codeLabel` strings. The first three customize clipboard controls and their live
582
+ success/error feedback; `codeLabel` names the keyboard-focusable region for an
583
+ unwrapped code block. A denied or unavailable clipboard reports the error label
584
+ and keeps the source available for manual selection. `CodeTabs` forwards these
585
+ labels to each code example.
586
+
587
+ `Code` with `highlighted` enhances the supplied `pre` when the Astro runtime loads.
588
+ For keyboard scrolling before JavaScript loads, give that `pre` `tabindex="0"`,
589
+ `role="region"`, and an accessible name; existing attributes are preserved.
590
+ Wrapped blocks do not add an extra tab stop.
591
+
592
+ `NavigationMenu` contains ordinary site links, so every enabled link remains in
593
+ native Tab order. Use `Menubar` or `Toolbar` for their documented composite-widget
594
+ keyboard behavior. A `ThemeToggle` with `controlled` respects the host theme at
595
+ initialization and leaves persistence and theme changes to its owner.
596
+
597
+ ### Virtual list and editor ownership
598
+
599
+ `VirtualList` displays fixed-height rows with inert spacers that retain the full scroll extent.
600
+ Rows stay mounted; use pagination when the initial DOM cost matters. Scrolling, resizing, sizing
601
+ changes and direct row changes refresh the window. Focused rows remain available. After document
602
+ adoption, rerun the standard initializer to rebind the controller to the destination document. See the
603
+ [fixed-height list contract](https://github.com/santi020k/lumen/blob/main/docs/ai-usage.md#fixed-height-virtual-lists).
604
+
605
+ External rich-text engines should handle the cancelable `ui:editor-command-request` event before
606
+ execution and use `ui:editor-command` only for completion notifications. React also supports
607
+ `useRichTextEditor({ commandHandler })`. Disable native toolbar state syncing when the external
608
+ engine owns it. See the [editor guidance](https://github.com/santi020k/lumen/blob/main/docs/ai-usage.md).
609
+
610
+ ## Phone presentation in v4
611
+
612
+ Phone inputs bundle the same offline flag artwork on every platform. The selected country shows
613
+ its flag and calling code inside one continuous input border. Country names remain in the native
614
+ picker and its accessible name; flags are supplementary. Unknown flag codes fall back to text.
615
+
616
+ Astro and React `PhoneInput` accept `disabled`, `readOnly`, `required`, `errorMessage`,
617
+ `showValidationError`, and `inputProps`. Their `id` targets the number input in v4; React also
618
+ accepts `inputRef`. Web Components use `disabled`, `readonly`, `required`, `error-message`,
619
+ `show-validation-error="false"`, and `input-id`, with native input attributes on the host.
620
+ Both controls lock together and validation remains associated with the input. Astro forwards
621
+ `inputProps.form` to the number input, country picker and read-only country fallback so external
622
+ form submission and reset include every part.
623
+
624
+ The `phone-input`, `phone-country`, and `country-flag` styling parts plus `--ui-phone-height`,
625
+ `--ui-phone-padding`, and `--ui-phone-country-gap` replace consumer CSS overlays.
626
+
627
+ Astro and React also export `CountryFlag` (`regionCode`, optional `decorative`) and `PhoneNumber`
628
+ (`value: LumenPhoneNumber`, optional `link`). A telephone link is rendered only for a complete
629
+ E.164 value. Use the model returned by the phone normalizer; keep domain persistence in your app.
630
+ Artwork attribution is shipped with the core package in `PHONE_FLAG_LICENSE.txt`.
631
+
632
+ ## Combobox keyboard behavior
633
+
634
+ In v4, Combobox retains input focus and exposes its active option through `aria-activedescendant`.
635
+ Enter commits an active option; text editing and composition remain native. Escape dismisses one
636
+ nested control at a time. See the [shared keyboard contract](https://github.com/santi020k/lumen/blob/main/docs/ai-usage.md#combobox-keyboard-behavior-in-v4)
637
+ for dynamic options, controlled inputs and migration guidance.
638
+
639
+ ## Content flow
640
+
641
+ Stack and Grid own sibling spacing. Their gap accepts `related`, `group` (default) and `section`,
642
+ or canonical `none`, `xs`, `sm`, `md`, `lg`, `xl`, `2xl`, `3xl` sizes. Card owns the inset and gap
643
+ between its visible parts; use `density="compact"`, `"comfortable"` (default) or `"spacious"`.
644
+ Elements uses the same names as attributes. Use a nested Stack for CardContent groups and Field
645
+ for label/control/feedback. See [content flow](https://github.com/santi020k/lumen/blob/main/docs/content-flow.md) and the
646
+ [v4 migration guide](https://github.com/santi020k/lumen/blob/main/docs/migrating-to-lumen.md) for ownership and changed explicit gaps.
647
+
648
+ ### Reading and complete compositions
649
+
650
+ Prose and Typography trim outer child margins and separate headings from preceding text.
651
+ Container gutters grow from 16px to 32px with viewport width; override `--ui-container-gutter`
652
+ when a product needs fixed gutters. Card allows interactive overflow; use AspectRatio to clip media.
653
+ Install `content-flow-header`, `content-flow-settings`, `content-flow-list` or `content-flow-actions`
654
+ with `lumen add <recipe> --target astro|react|elements`. MCP returns the same complete examples.
655
+ Connect application actions and replace sample IDs before reuse. See
656
+ [content flow](https://github.com/santi020k/lumen/blob/main/docs/content-flow.md) for composition and migration guidance.
657
+
658
+ ### Large fixed-height collections
659
+
660
+ `VirtualList` supports an empty `mode="data"` root with the public Core data controller.
661
+ Only the visible window, overscan and focused neighbors mount. Stable keys retain row identity;
662
+ applications own offscreen editing state. See [data rendering](https://github.com/santi020k/lumen/blob/main/docs/virtual-list-data.md) for
663
+ setup, lifecycle, accessibility and the mounted-mode tradeoff.
664
+
665
+ Action-error normalization ignores malformed fields and non-string messages. Enhanced forms include
666
+ externally associated native controls. Reinitialize with `LumenInitUiPrimitives(destination)` after
667
+ adopting an enhanced form into another document so blur validation follows its owner, including
668
+ controls created in another document. Timed toasts stay paused while hovered or focused.
669
+
670
+ ### Compound dialog tasks
671
+
672
+ Use `DialogHeader`, `DialogTitle`, `DialogBody`, `DialogFooter`, and `DialogClose`
673
+ for long forms. Associate the title explicitly so labeling works before enhancement.
674
+ `DialogBody` must be a direct child of `Dialog` for independent scrolling. Footer
675
+ submit actions target a form inside the body with the native `form` attribute.
676
+
677
+ ```astro
678
+ <Dialog id="record-dialog" aria-labelledby="record-title">
679
+ <DialogHeader><DialogTitle id="record-title">Edit record</DialogTitle></DialogHeader>
680
+ <DialogBody><Form id="record-form"><Input name="name" aria-label="Name" /></Form></DialogBody>
681
+ <DialogFooter>
682
+ <DialogClose variant="outline">Cancel</DialogClose>
683
+ <Button type="submit" form="record-form">Save</Button>
684
+ </DialogFooter>
685
+ </Dialog>
686
+ ```
687
+
688
+ Mount `UIPrimitives` once and open the dialog with a button carrying
689
+ `data-ui-dialog-trigger="record-dialog"`. Set `selectedFilesLabel="{count} files selected"`
690
+ on `FileUpload` to supply localized multiple-file feedback; the single-file label is its filename.
691
+ A trigger ignores its own click when that click arrives canceled (`event.preventDefault()`) or
692
+ when the trigger matches `:disabled` or `[aria-disabled="true"]`, matching close control behavior.
693
+ Register cancellation listeners before the runtime binds the trigger. Activating a trigger for an
694
+ already open dialog preserves its mode and original focus-return target.
695
+
696
+ ### Rich description rows
697
+
698
+ Use `Descriptions` with `DescriptionItem`, `DescriptionTerm`, and `DescriptionDetail`
699
+ when a value contains links, badges, formatted dates, or actions. Astro renders a
700
+ native `dl` containing `div`, `dt`, and `dd` elements. The existing `items` array
701
+ remains available and can be combined with child rows.
702
+
703
+ ```astro
704
+ <Descriptions>
705
+ <DescriptionItem>
706
+ <DescriptionTerm>Status</DescriptionTerm>
707
+ <DescriptionDetail><Badge variant="success">Active</Badge></DescriptionDetail>
708
+ </DescriptionItem>
709
+ </Descriptions>
710
+ ```
711
+
712
+ ## Attachment composition
713
+
714
+ Use `AttachmentList` to group native `li` children and `AttachmentPreview` for browser-owned images
715
+ with localized loading, error, and unsupported-file states. Compose independent actions rather
716
+ than nesting controls inside a linked Attachment. The application retains file validation,
717
+ authorization, persistence, and object URL cleanup. See the
718
+ [attachment composition recipe](https://github.com/santi020k/lumen/blob/main/docs/consumer-ui-recipes.md#attachment-previews-and-file-lists)
719
+ for adapter props, slots, child contracts, retry identity, and safe state events.
720
+
721
+ ## Chart datum actions
722
+
723
+ Set `drilldown` on BarChart, LineChart, PieChart, ScatterChart, ComboChart, Heatmap, or RangeChart
724
+ and mount `UIPrimitives` once. Each available plotted datum has a corresponding native button in
725
+ the actions disclosure, including when `showTable={false}`. Marks remain decorative to assistive
726
+ technology. The figure emits `ui:chart-datum-activate` with a validated
727
+ `LumenChartDatumActivationDetail`; apps decide whether to navigate, filter, or open a detail view.
728
+
729
+ Translate `labels.exploreData` and `labels.formatDatumAction(context)`, and format visible values
730
+ with the chart's existing formatters. Missing observations have no action; pie actions cover only
731
+ positive slices. A line's `markers="none"` hides visible markers while retaining pointer targets
732
+ and native keyboard actions. See [data visualization](https://github.com/santi020k/lumen/blob/main/docs/data-visualization.md#astro-chart-actions).
733
+
734
+ ## Dashboard composition
735
+
736
+ `FilterBar` groups host-owned filtering controls, active criteria, reset actions, and a polite
737
+ result announcement. `ChangeSummary` presents explicit before/after values and application-owned
738
+ changed state. Neither component owns requests, persistence, parsing, or financial policy.
739
+
740
+ ScatterChart supports independent X/Y formatting, explicit domains, logarithmic positive X values,
741
+ and labeled reference lines/regions. Automatic domains leave room for complete bubbles; explicit
742
+ domain limits stay exact and can intentionally crop marks. ComboChart and RangeChart include
743
+ category labels and value scales. See [consumer UI recipes](https://github.com/santi020k/lumen/blob/main/docs/consumer-ui-recipes.md)
744
+ for dashboard tables, freshness, import review, activity inbox, and persistent Kanban patterns.
745
+
746
+ ### Actual-versus-target charts
747
+
748
+ `BulletChart` compares a nullable actual `value` with a finite `target` and optional
749
+ labeled `ranges`. A strong actual bar, target marker, readable value labels, and expandable exact
750
+ data work together. Domains include zero and all measurements; invalid inputs fail closed, including malformed range containers.
751
+ Null values stay distinct from zero. See the [chart guide](https://github.com/santi020k/lumen/blob/main/docs/data-visualization.md#actual-values-and-targets)
752
+ for the input, localization, and domain contracts.
753
+
754
+ ### Rankings and paired comparisons
755
+
756
+ Use `LollipopChart` for zero-based rankings and `DumbbellChart` for paired measurements (native
757
+ `LumenLollipopChart` and `LumenDumbbellChart`). Supply ordered comparison data with `id`, `label`,
758
+ nullable `value`, optional nullable `reference`, and optional `tone`. Both charts preserve missing
759
+ values and expose exact data. See the [shared visualization contract](https://github.com/santi020k/lumen/blob/main/docs/data-visualization.md#rankings-and-paired-comparisons).
760
+
761
+ ### Calendars, funnels, and distributions
762
+
763
+ Use `CalendarHeatmap` for daily date-only observations with an explicit start/end range,
764
+ `FunnelChart` for ordered nonnegative stages, and `BoxPlot` for precomputed quartiles, whiskers,
765
+ and outliers. All retain missing values and expose exact data. See the
766
+ [data visualization guide](https://github.com/santi020k/lumen/blob/main/docs/data-visualization.md#calendars-funnels-and-box-plots).
767
+
768
+ Media controllers for ImageComparison and FileUpload load only when matching components are present.
769
+ Keep mounting `UIPrimitives` once; no additional consumer setup is required. Image comparisons
770
+ follow their current native form owner for reset and honor canceled resets. Reinitialize after
771
+ document adoption to register destination reset delegation without duplicate input listeners.
772
+
773
+ ## Visual interactions and product blocks
774
+
775
+ See [visual interactions](https://github.com/santi020k/lumen/blob/main/docs/visual-interactions.md) for keyed motion, semantic effects,
776
+ chart continuity, AI surfaces, optional SDK integrations, and the four installable product recipes.
777
+ Import `@santi020k/lumen/styles/motion.css` alongside the base stylesheet to opt into native
778
+ disclosure height transitions and the CSS reduction scope. This small optional stylesheet works
779
+ with Astro, React, and Elements and keeps those effects out of the default stylesheet. The presence
780
+ helper's system and local reduced-motion checks work without this CSS import.
781
+
782
+ ## Device demonstrations
783
+
784
+ `DeviceFrame` presents slotted HTML, an image, or a titled iframe inside `macbook-pro`, `macbook-air`, `imac`,
785
+ `iphone`, `pixel`, and `ipad-pro` frames, with generic `laptop`, `desktop`, `android`, and `tablet` options.
786
+ Device-specific enclosures include a MacBook Pro display notch and full front edge, a tapered MacBook Air deck, curved iMac
787
+ stand and chin, rounded phone glass with separate rails and buttons, and an iPad Pro with even
788
+ bezels and no Home button. The generic `tablet` retains its classic enclosure.
789
+ Shells are decorative and do not emulate device hardware. `orientation` selects portrait or landscape; `tone` selects
790
+ light or dark chrome independently of the screen content. Use `color="white"`,
791
+ `color="black"`, or a 3-, 4-, 6-, or 8-digit hex color such as `color="#a9b8ac"` for
792
+ a custom hardware finish. `color` takes precedence over `tone`; invalid values fall back to the
793
+ tone. The finish stays independent of the content theme. For CSS-driven updates, set
794
+ `--ui-device-color` to a CSS color on the frame.
795
+
796
+ Iframe layouts use the preset screen viewport and scale to the available width. Override
797
+ `screenWidth` and `screenHeight` for a custom viewport (1–16384 CSS pixels). Slotted HTML shares
798
+ the host viewport; container queries can adapt it to the screen. Images preserve their proportions.
799
+ `scroll={false}` clips HTML overflow; an iframe manages its own scrolling.
800
+
801
+ Supply image alternative text and iframe titles. Consumers own iframe `sandbox`, `allow`,
802
+ loading, and referrer policies. Remote sites can refuse embedding through their response headers.
803
+ The decorative shell does not alter focus or intercept interactions. Camera and home-indicator
804
+ details occupy separate chrome outside live HTML and iframe viewports. Direct phone images extend
805
+ under the decorative camera and home indicator for a full-bleed presentation.
806
+ Screen presets are 1280 × 800 for MacBook Pro and MacBook Air, 1440 × 810 for iMac, 390 × 844 for iPhone,
807
+ 412 × 915 for Pixel, and 834 × 1194 for iPad Pro (1194 × 834 in landscape). These are demonstration
808
+ viewports, not physical display specifications.
809
+
810
+ ```astro
811
+ ---
812
+ import { DeviceFrame } from '@santi020k/lumen-astro'
813
+ ---
814
+ <DeviceFrame device="iphone">
815
+ <iframe src="/demo" title="Mobile application demo" loading="lazy"></iframe>
816
+ </DeviceFrame>
817
+ ```
818
+
819
+ The component bundles its resize behavior; `UIPrimitives` is not required for this frame.
820
+
821
+ ## World map
822
+
823
+ WorldMap supports highlighted countries, location markers, dotted or solid styles, country selection,
824
+ and theme customization. Use `initialView="highlighted"` (Elements: `initial-view`) to start with a
825
+ regional view. Zoom toward the cursor with Ctrl/Cmd-scroll, or fit highlighted countries using the
826
+ map controls. Adopted maps emit selection events in their current document; reinitialization follows
827
+ interaction mode changes without duplicating listeners or resetting zoom. Import geography explicitly from
828
+ `@santi020k/lumen-core/world-map-data`; it is excluded from root exports. See the
829
+ [WorldMap usage guide](https://github.com/santi020k/lumen/blob/main/docs/world-map.md) for adapter examples, events, localization,
830
+ accessibility, and customization.
831
+
832
+ ## Studio media workspace
833
+
834
+ Compose MediaViewport, MediaThumbnail, MediaFilmstrip and ImageComparison modes with the
835
+ [Studio media workspace recipes](https://github.com/santi020k/lumen/blob/main/docs/studio-media-workspaces.md). Applications retain
836
+ media loading, selection, adjustment algorithms, processing, export and persistence.
837
+
838
+ ## Resources
839
+
840
+ | Guide | What you will find |
841
+ | --- | --- |
842
+ | [Styling contract](https://github.com/santi020k/lumen/blob/main/docs/styling-contract.md) | Stable styling parts, component variables, and overrides. |
843
+ | [Error handling](https://github.com/santi020k/lumen/blob/main/docs/error-handling.md) | Field feedback, summaries, alerts, and recovery surfaces. |
844
+ | [Contributing](https://github.com/santi020k/lumen/blob/main/CONTRIBUTING.md) | Setup, checks, and contribution workflow. |
845
+ | [Release history](https://github.com/santi020k/lumen/releases) | Published releases and version notes. |
846
+
847
+ Part of [Lumen UI](https://lumen.santi020k.com), created by [Santiago Molina](https://santi020k.com).
848
+ Licensed under [MIT](https://github.com/santi020k/lumen/blob/main/LICENSE); third-party artwork retains its own notices.
@@ -0,0 +1,21 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import { formatLumenAmountDraft, getLumenAmountValue, type LumenAmountOptions, resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
+ interface Props extends Omit<HTMLAttributes<'input'>, 'value' | 'type' | 'min' | 'max' | 'step'>, LumenAmountOptions {
7
+ value?: string
8
+ defaultValue?: string
9
+ invalidMessage?: string
10
+ className?: string
11
+ }
12
+
13
+ const { value, defaultValue, locale = 'en-US', fractionDigits = 2, allowNegative = false, invalidMessage, name, form, disabled, class: classProp = '', className = '', ...rest } = Astro.props
14
+ const initial = value ?? defaultValue ?? ''
15
+ const { classList, passthrough } = resolveLumenAstroProps(rest, ['ui-input', 'ui-amount-field'], classProp, className)
16
+ ---
17
+
18
+ <span data-ui-amount-field locale={locale} fraction-digits={fractionDigits} allow-negative={allowNegative ? '' : undefined} value={value} default-value={defaultValue ?? initial} invalid-message={invalidMessage}>
19
+ <input {...passthrough} class:list={classList} data-ui-amount-input type="text" inputmode="decimal" value={formatLumenAmountDraft(initial, { locale, fractionDigits, allowNegative })} form={form} disabled={disabled} />
20
+ <input data-ui-amount-value type="hidden" name={name} form={form} disabled={disabled} value={getLumenAmountValue(initial) ?? ''} />
21
+ </span>
@@ -28,7 +28,7 @@ const {
28
28
  ...rest
29
29
  } = Astro.props
30
30
 
31
- const safeDecimals = Math.max(0, Math.min(20, Math.floor(decimals)))
31
+ const safeDecimals = Math.max(0, Math.min(20, (Math.floor(decimals) || 0)))
32
32
 
33
33
  const formatter = new Intl.NumberFormat(locale, {
34
34
  maximumFractionDigits: safeDecimals,
@@ -0,0 +1,30 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import type { LumenApprovalStatus } from '@santi020k/lumen-core'
5
+
6
+ import Button from './Button.astro'
7
+ import Card from './Card.astro'
8
+ import Stack from './Stack.astro'
9
+
10
+ interface Props extends HTMLAttributes<'div'> {
11
+ approveLabel?: string
12
+ disabled?: boolean
13
+ label: string
14
+ rejectLabel?: string
15
+ requestId: string
16
+ status?: LumenApprovalStatus
17
+ statusLabel: string
18
+ }
19
+
20
+ const { approveLabel = 'Approve', class: className, disabled = false, label, rejectLabel = 'Reject', requestId, status = 'pending', statusLabel, ...rest } = Astro.props
21
+ ---
22
+
23
+ <Card {...rest} aria-label={label} class:list={['ui-approval-card', className]} data-disabled={String(disabled)} data-ui-approval-card data-request-id={requestId} data-status={status}>
24
+ <Stack gap="group"><strong>{label}</strong><slot /><span role="status">{statusLabel}</span>
25
+ <Stack direction="horizontal" gap="related" wrap>
26
+ <Button data-ui-approval-response="approve" disabled={disabled || status !== 'pending'} type="button">{approveLabel}</Button>
27
+ <Button data-ui-approval-response="reject" disabled={disabled || status !== 'pending'} type="button" variant="secondary">{rejectLabel}</Button>
28
+ </Stack>
29
+ </Stack>
30
+ </Card>
@@ -0,0 +1,15 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
+ interface Props extends HTMLAttributes<'ul'> {
7
+ class?: string | null
8
+ className?: string
9
+ }
10
+
11
+ const { class: classProp = '', className = '', ...rest } = Astro.props
12
+ const { classList, passthrough } = resolveLumenAstroProps(rest, ['ui-attachment-list'], classProp, className)
13
+ ---
14
+
15
+ <ul class:list={classList} data-slot="attachment-list" {...passthrough}><slot /></ul>
@@ -0,0 +1,51 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import {
5
+ type LumenAttachmentPreviewLabels,
6
+ lumenAttachmentPreviewLabels,
7
+ type LumenAttachmentPreviewState,
8
+ resolveLumenAstroProps,
9
+ resolveLumenAttachmentPreviewState
10
+ } from '@santi020k/lumen-core'
11
+
12
+ interface Props extends HTMLAttributes<'figure'> {
13
+ alt: string
14
+ class?: string | null
15
+ className?: string
16
+ contentType?: string
17
+ labels?: Partial<LumenAttachmentPreviewLabels>
18
+ retryKey?: number | string
19
+ src?: string
20
+ state?: LumenAttachmentPreviewState
21
+ }
22
+
23
+ const { alt, class: classProp = '', className = '', contentType, labels, retryKey, src, state: requested, ...rest } = Astro.props
24
+ const state = resolveLumenAttachmentPreviewState(src, contentType, requested)
25
+ const canPreview = resolveLumenAttachmentPreviewState(src, contentType) === 'ready'
26
+ const copy = { ...lumenAttachmentPreviewLabels, ...labels }
27
+ const { classList, passthrough } = resolveLumenAstroProps(rest, ['ui-attachment-preview'], classProp, className)
28
+ ---
29
+
30
+ <figure
31
+ class:list={classList}
32
+ {...passthrough}
33
+ aria-busy={state === 'loading'}
34
+ data-state={state}
35
+ data-ui-attachment-preview
36
+ data-ui-attachment-preview-state={requested}
37
+ data-ui-attachment-preview-content-type={contentType}
38
+ data-ui-attachment-preview-retry-key={retryKey}
39
+ data-ui-attachment-preview-error-label={copy.error}
40
+ data-ui-attachment-preview-loading-label={copy.loading}
41
+ data-ui-attachment-preview-unavailable-label={copy.unavailable}
42
+ >
43
+ <div data-slot="attachment-preview-media" hidden={state !== 'ready'}>
44
+ {/* Browser-owned private URLs and object URLs must not enter Astro's server image optimizer. */}
45
+ {canPreview && <img src={src} alt={alt} data-ui-attachment-preview-image loading="eager" />}
46
+ </div>
47
+ <p role="status" data-slot="attachment-preview-fallback" data-ui-attachment-preview-message hidden={state === 'ready'}>{state === 'ready' ? '' : copy[state]}</p>
48
+ {Astro.slots.has('caption') && <figcaption><slot name="caption" /></figcaption>}
49
+ <slot />
50
+ {Astro.slots.has('actions') && <div data-slot="attachment-preview-actions"><slot name="actions" /></div>}
51
+ </figure>
@@ -30,6 +30,8 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
30
30
 
31
31
  <span
32
32
  class:list={classList}
33
+ role={!src && alt ? 'img' : undefined}
34
+ aria-label={!src && alt ? alt : undefined}
33
35
  {...passthrough}
34
36
  >
35
37
  {src ? <img alt={alt} src={src} /> : fallbackText && <span aria-hidden={fallback ? undefined : 'true'}>{fallbackText}</span>}