@santi020k/lumen-astro 3.0.0 → 4.0.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 (103) hide show
  1. package/README.md +403 -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 +127 -67
  45. package/components/LollipopChart.astro +8 -0
  46. package/components/MediaFilmstrip.astro +19 -0
  47. package/components/MediaThumbnail.astro +29 -0
  48. package/components/MediaViewport.astro +38 -0
  49. package/components/Mentions.astro +2 -1
  50. package/components/MessageScroller.astro +6 -0
  51. package/components/MotionGroup.astro +19 -0
  52. package/components/NativeSelect.astro +2 -2
  53. package/components/Pagination.astro +2 -1
  54. package/components/PasswordField.astro +1 -1
  55. package/components/PhoneInput.astro +60 -9
  56. package/components/PhoneNumber.astro +19 -0
  57. package/components/PieChart.astro +26 -3
  58. package/components/PromptComposer.astro +36 -0
  59. package/components/RangeChart.astro +38 -8
  60. package/components/ScatterChart.astro +62 -14
  61. package/components/Segmented.astro +5 -5
  62. package/components/Select.astro +8 -8
  63. package/components/SourceCitation.astro +18 -0
  64. package/components/Sparkline.astro +9 -10
  65. package/components/Stack.astro +2 -2
  66. package/components/StreamMessage.astro +26 -0
  67. package/components/Tabs.astro +3 -0
  68. package/components/ToolActivity.astro +17 -0
  69. package/components/TreeSelect.astro +1 -0
  70. package/components/VirtualList.astro +5 -2
  71. package/components/VisualEffect.astro +21 -0
  72. package/components/WaterfallChart.astro +27 -0
  73. package/components/WorldMap.astro +213 -0
  74. package/forms.ts +17 -4
  75. package/index.ts +37 -0
  76. package/internal/ChartInspection.astro +27 -0
  77. package/internal/ComparisonChart.astro +69 -0
  78. package/internal/IntervalChart.astro +66 -0
  79. package/package.json +6 -4
  80. package/runtime/UIPrimitives.astro +670 -647
  81. package/runtime/chart-actions.ts +19 -0
  82. package/runtime/controllers/amount-field.ts +12 -0
  83. package/runtime/controllers/attachments.ts +13 -0
  84. package/runtime/controllers/chart-activation.ts +33 -0
  85. package/runtime/controllers/charts.ts +33 -0
  86. package/runtime/controllers/combobox.ts +39 -0
  87. package/runtime/controllers/comparison-reset.ts +20 -0
  88. package/runtime/controllers/data-table.ts +142 -0
  89. package/runtime/controllers/dialogs.ts +32 -32
  90. package/runtime/controllers/document-navigation.ts +54 -22
  91. package/runtime/controllers/file-upload.ts +89 -0
  92. package/runtime/controllers/image-comparison.ts +53 -0
  93. package/runtime/controllers/media-selection.ts +20 -0
  94. package/runtime/controllers/media-viewport.ts +54 -0
  95. package/runtime/controllers/media-workspace.ts +8 -0
  96. package/runtime/controllers/message-scroller.ts +13 -0
  97. package/runtime/controllers/motion.ts +15 -16
  98. package/runtime/controllers/optional-media.ts +29 -0
  99. package/runtime/controllers/phone-input.ts +128 -39
  100. package/runtime/controllers/visual-interactions.ts +75 -0
  101. package/runtime/controllers/world-map.ts +154 -0
  102. package/styles/critical.css +634 -61
  103. package/styles/lumen.css +101 -0
@@ -2,29 +2,37 @@
2
2
  import type { HTMLAttributes } from 'astro/types'
3
3
 
4
4
  import {
5
- alignLumenChartSeries,
6
- createLumenLineGeometry,
5
+ createLumenChartDatumActivation,
6
+ createLumenLineChartModel,
7
7
  formatLumenChartSummary,
8
- getLumenChartCategories,
9
- getLumenChartDomain,
10
- getLumenChartTicks,
8
+ getLumenChartCategoryLabel,
9
+ getLumenChartMotionKey,
11
10
  getLumenChartToneClassName,
12
11
  hasLumenChartData,
13
12
  type LumenChartLabels,
14
13
  type LumenChartSeries,
14
+ type LumenLineChartOptions,
15
15
  resolveLumenChartLabels,
16
16
  resolveLumenChartTone,
17
17
  scaleLumenChartValue
18
18
  } from '@santi020k/lumen-core'
19
19
 
20
+ import ChartInspection from '../internal/ChartInspection.astro'
21
+ import { createChartDatumAction } from '../runtime/chart-actions'
22
+
23
+ import Button from './Button.astro'
20
24
  import Chart from './Chart.astro'
25
+ import ChartDatumActions from './ChartDatumActions.astro'
21
26
 
22
- interface Props extends HTMLAttributes<'figure'> {
27
+ interface Props extends HTMLAttributes<'figure'>, LumenLineChartOptions {
28
+ interactive?: boolean
29
+ syncGroup?: string
23
30
  area?: boolean
24
31
  caption?: string
25
32
  class?: string | null
26
33
  className?: string
27
34
  description?: string
35
+ drilldown?: boolean
28
36
  emptyLabel?: string
29
37
  formatCategory?: (category: number | string) => string
30
38
  formatValue?: (value: number) => string
@@ -42,12 +50,21 @@ interface Props extends HTMLAttributes<'figure'> {
42
50
 
43
51
  const {
44
52
  area = false,
53
+ annotations = [],
54
+ domain: requestedDomain,
55
+ height: requestedHeight,
56
+ width: requestedWidth,
57
+ xDomain,
58
+ xScale = 'categorical',
59
+ interactive = false,
60
+ syncGroup,
45
61
  caption,
46
62
  class: classProp = '',
47
63
  className = '',
48
64
  description,
65
+ drilldown = false,
49
66
  emptyLabel,
50
- formatCategory = String,
67
+ formatCategory,
51
68
  formatValue = String,
52
69
  glass = false,
53
70
  heading,
@@ -62,29 +79,22 @@ const {
62
79
  } = Astro.props
63
80
 
64
81
  const resolvedLabels = resolveLumenChartLabels(labels)
65
- const summary = summaryProp ?? formatLumenChartSummary(series, formatValue, resolvedLabels)
66
- const width = 640
67
- const height = 320
68
- const padding = 44
69
- const categories = getLumenChartCategories(series)
70
- const hasData = hasLumenChartData(series)
71
- const alignedSeries = series.map(item => alignLumenChartSeries(item, categories))
72
-
73
- const domain = getLumenChartDomain([
74
- ...alignedSeries.flatMap(item => item.data.map(datum => datum.y)),
75
- referenceValue ?? null
76
- ], false)
77
-
78
- const geometries = alignedSeries.map(item => createLumenLineGeometry(item.data, {
79
- domain,
80
- height,
81
- includeZero: false,
82
- padding,
83
- width
84
- }))
85
-
86
- const ticks = getLumenChartTicks(domain)
87
- const labelStep = Math.max(1, Math.ceil(categories.length / 8))
82
+
83
+ const model = createLumenLineChartModel(series, {
84
+ annotations,
85
+ xScale,
86
+ ...(requestedDomain ? { domain: requestedDomain } : {}),
87
+ ...(requestedHeight === undefined ? {} : { height: requestedHeight }),
88
+ ...(requestedWidth === undefined ? {} : { width: requestedWidth }),
89
+ ...(xDomain ? { xDomain } : {}),
90
+ ...(referenceValue === undefined ? {} : { referenceValue }),
91
+ ...(formatCategory ? { formatCategory } : {}),
92
+ formatValue
93
+ })
94
+
95
+ const { width, height, padding, paddingLeft, categories, categoryTicks, domain, geometries, ticks, series: alignedSeries } = model
96
+ const hasData = hasLumenChartData(alignedSeries)
97
+ const summary = summaryProp ?? formatLumenChartSummary(alignedSeries, formatValue, resolvedLabels)
88
98
  let markerStep = 1
89
99
 
90
100
  if (markers === false || markers === 'none') markerStep = Number.POSITIVE_INFINITY
@@ -92,14 +102,29 @@ else if (typeof markers === 'number') markerStep = Math.max(1, Math.round(marker
92
102
  else if (markers === 'auto') markerStep = Math.max(1, Math.ceil(categories.length / 24))
93
103
 
94
104
  const valueFor = (item: LumenChartSeries, category: number | string) => item.data.find(datum => datum.x === category)
95
-
96
- const categoryLabelFor = (category: number | string) => series.flatMap(item => item.data)
97
- .find(datum => datum.x === category)?.xLabel ?? formatCategory(category)
105
+ const categoryLabelFor = (category: number | string) => getLumenChartCategoryLabel(alignedSeries, category, formatCategory, 'detail')
98
106
 
99
107
  const referenceY =
100
108
  referenceValue === undefined ?
101
109
  undefined :
102
110
  scaleLumenChartValue(referenceValue, domain, height - padding, padding)
111
+
112
+ const pointActions = drilldown ?
113
+ geometries.map((geometry, index) => {
114
+ const item = alignedSeries[index]
115
+
116
+ return geometry.points.map(point => item ?
117
+ createChartDatumAction(
118
+ createLumenChartDatumActivation(item.id, point),
119
+ `${categoryLabelFor(point.x)} · ${item.label}: ${formatValue(point.y ?? 0)}`,
120
+ resolvedLabels
121
+ ) :
122
+ null)
123
+ }) :
124
+ []
125
+
126
+ const datumActions = pointActions.flat()
127
+
103
128
  ---
104
129
 
105
130
  <Chart
@@ -109,22 +134,31 @@ const referenceY =
109
134
  {glass}
110
135
  {heading}
111
136
  {summary}
137
+ data-ui-chart-interactive={interactive || undefined}
138
+ data-ui-chart-sync={syncGroup}
112
139
  {...rest}
140
+ data-ui-chart-activation={drilldown || undefined}
113
141
  >
114
142
  {
115
143
  showLegend && hasData && (
116
144
  <ul class="ui-chart__legend" aria-label={resolvedLabels.chartLegend}>
117
145
  {series.map((item, index) => (
118
146
  <li class={getLumenChartToneClassName(item.tone, index)}>
119
- <span aria-hidden="true" />
120
- {item.label}
147
+ {interactive ? <Button aria-pressed="true" data-ui-chart-toggle={item.id} disabled size="sm" variant="ghost">{item.label}</Button> : <><span aria-hidden="true" />{item.label}</>}
121
148
  </li>
122
149
  ))}
123
150
  </ul>
124
151
  )
125
152
  }
126
153
  {!hasData && <p class="ui-chart__empty" role="status">{emptyLabel ?? resolvedLabels.empty}</p>}
127
- <div class="ui-chart__plot" hidden={!hasData}>
154
+ <div
155
+ aria-label={rest['aria-label'] ?? heading ?? resolvedLabels.chartData}
156
+ class="ui-chart__plot"
157
+ data-ui-chart-interaction-plot={interactive || undefined}
158
+ role="region"
159
+ tabindex="0"
160
+ hidden={!hasData}
161
+ >
128
162
  <svg
129
163
  aria-hidden="true"
130
164
  preserveAspectRatio="xMidYMid meet"
@@ -139,8 +173,8 @@ const referenceY =
139
173
 
140
174
  return (
141
175
  <>
142
- <line x1={padding} x2={width - padding} y1={y} y2={y} />
143
- <text x={padding - 8} y={y}>
176
+ <line x1={paddingLeft} x2={width - padding} y1={y} y2={y} />
177
+ <text x={paddingLeft - 8} y={y}>
144
178
  {formatValue(tick)}
145
179
  </text>
146
180
  </>
@@ -150,31 +184,26 @@ const referenceY =
150
184
  </g>
151
185
  <g class="ui-chart__axis-labels">
152
186
  {
153
- categories.map((category, index) => {
154
- if (
155
- index % labelStep !== 0 &&
156
- index !== categories.length - 1
157
- )
158
- return null
159
-
160
- const denominator = Math.max(1, categories.length - 1)
161
-
162
- const x =
163
- padding + (index / denominator) * (width - padding * 2)
164
-
165
- return (
166
- <text text-anchor="middle" x={x} y={height - 14}>
167
- {categoryLabelFor(category)}
168
- </text>
169
- )
170
- })
187
+ categoryTicks.map(tick => (
188
+ <text text-anchor={tick.textAnchor} x={tick.position} y={height - 14}>
189
+ {tick.label}
190
+ </text>
191
+ ))
171
192
  }
172
193
  </g>
194
+ <svg
195
+ x={paddingLeft}
196
+ y={padding}
197
+ width={width - padding - paddingLeft}
198
+ height={height - 2 * padding}
199
+ viewBox={`${paddingLeft} ${padding} ${width - padding - paddingLeft} ${height - 2 * padding}`}
200
+ overflow="hidden"
201
+ >
173
202
  {
174
203
  referenceY !== undefined && (
175
204
  <line
176
205
  class="ui-chart__reference"
177
- x1={padding}
206
+ x1={paddingLeft}
178
207
  x2={width - padding}
179
208
  y1={referenceY}
180
209
  y2={referenceY}
@@ -183,43 +212,73 @@ const referenceY =
183
212
  }
184
213
  {
185
214
  geometries.map((geometry, index) => {
186
- const item = series[index]
215
+ const item = alignedSeries[index]
187
216
 
188
217
  if (!item) return null
189
218
 
190
219
  const tone = resolveLumenChartTone(item.tone, index)
191
220
 
192
221
  return (
193
- <g class:list={['ui-line-chart__series', getLumenChartToneClassName(tone)]}>
194
- {area &&
195
- geometry.areaPaths.map(path => (
196
- <path class="ui-line-chart__area" d={path} />
197
- ))}
198
- <path class="ui-line-chart__line" d={geometry.path} />
222
+ <g
223
+ data-ui-chart-series={item.id}
224
+ class:list={['ui-line-chart__series', getLumenChartToneClassName(tone)]}
225
+ >
226
+ {area && geometry.areaPaths.length > 0 && (
227
+ <path
228
+ data-ui-chart-motion-key={JSON.stringify(['area', item.id])}
229
+ class="ui-line-chart__area"
230
+ d={geometry.areaPaths.join(' ')}
231
+ />
232
+ )}
233
+ <path
234
+ data-ui-chart-motion-key={JSON.stringify(['path', item.id])}
235
+ class="ui-line-chart__line"
236
+ d={geometry.path}
237
+ />
199
238
  {Number.isFinite(markerStep) &&
200
239
  geometry.points.map((point, pointIndex) => pointIndex % markerStep === 0 && (
201
240
  <circle
241
+ data-ui-chart-datum={pointActions[index]?.[pointIndex]?.serialized}
242
+ data-ui-chart-motion-key={getLumenChartMotionKey(item.id, point)}
202
243
  class="ui-line-chart__point"
203
244
  cx={point.xCoordinate}
204
245
  cy={point.yCoordinate}
205
246
  r="3"
206
247
  >
207
248
  <title>
208
- {`${point.xLabel ?? formatCategory(point.x)} · ${item.label}: ${formatValue(point.y ?? 0)}`}
249
+ {`${categoryLabelFor(point.x)} · ${item.label}: ${formatValue(point.y ?? 0)}`}
209
250
  </title>
210
251
  </circle>
211
252
  ))}
253
+ {drilldown && geometry.points.map((point, pointIndex) => (
254
+ <circle
255
+ class="ui-chart__datum-hit"
256
+ cx={point.xCoordinate}
257
+ cy={point.yCoordinate}
258
+ r="10"
259
+ data-ui-chart-datum={pointActions[index]?.[pointIndex]?.serialized}
260
+ />
261
+ ))}
212
262
  </g>
213
263
  )
214
264
  })
215
265
  }
266
+ </svg>
267
+ {model.annotationMarks.map(mark => (
268
+ <g class:list={['ui-chart__annotation', getLumenChartToneClassName(mark.tone)]}>
269
+ {mark.axis === 'x' ? <><line x1={mark.coordinate} x2={mark.coordinate} y1={padding} y2={height - padding} /><text x={mark.coordinate + 4} y={padding - 8}>{mark.label}</text></> : <><line x1={paddingLeft} x2={width - padding} y1={mark.coordinate} y2={mark.coordinate} /><text text-anchor="end" x={width - padding} y={mark.coordinate - 8}>{mark.label}</text></>}
270
+ </g>
271
+ ))}
272
+ {interactive && <line class="ui-chart__crosshair" data-ui-chart-crosshair style="display: none" y1={padding} y2={height - padding} />}
216
273
  </svg>
217
274
  </div>
275
+ {model.annotationMarks.length > 0 && <ul class="ui-sr-only">{model.annotationMarks.map(mark => <li>{mark.label}: {mark.axis === 'x' ? categoryLabelFor(mark.value) : formatValue(Number(mark.value))}</li>)}</ul>}
276
+ {interactive && hasData && <ChartInspection {model} {formatCategory} {formatValue} labels={resolvedLabels} />}
218
277
  {
219
278
  showTable && hasData && (
220
279
  <details class="ui-chart__data">
221
280
  <summary>{resolvedLabels.viewData}</summary>
222
- <div>
281
+ <div role="group" tabindex="0" aria-label={resolvedLabels.chartData}>
223
282
  <table>
224
283
  <thead>
225
284
  <tr>
@@ -231,7 +290,7 @@ const referenceY =
231
290
  {categories.map(category => (
232
291
  <tr>
233
292
  <th scope="row">{categoryLabelFor(category)}</th>
234
- {series.map(item => {
293
+ {alignedSeries.map(item => {
235
294
  const datum = valueFor(item, category)
236
295
 
237
296
  return (
@@ -251,4 +310,5 @@ const referenceY =
251
310
  </details>
252
311
  )
253
312
  }
313
+ {drilldown && <ChartDatumActions actions={datumActions} label={resolvedLabels.exploreData} />}
254
314
  </Chart>
@@ -0,0 +1,8 @@
1
+ ---
2
+ import type { ComponentProps } from 'astro/types'
3
+
4
+ import ComparisonChart from '../internal/ComparisonChart.astro'
5
+
6
+ type Props = Omit<ComponentProps<typeof ComparisonChart>, 'paired'>
7
+ ---
8
+ <ComparisonChart {...Astro.props} class:list={['ui-lollipop-chart', Astro.props.class, Astro.props.className]} paired={false} />
@@ -0,0 +1,19 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
+ interface Props extends HTMLAttributes<'section'> {
7
+ className?: string
8
+ label: string
9
+ selectionLabel: string
10
+ }
11
+
12
+ const { class: classProp, className, label, selectionLabel, ...rest } = Astro.props
13
+ const { classList, passthrough } = resolveLumenAstroProps(rest, ['ui-media-filmstrip'], classProp, className)
14
+ ---
15
+
16
+ <section {...passthrough} class:list={classList} aria-label={label}>
17
+ <div class="ui-media-filmstrip__heading"><span>{label}</span><span role="status" data-ui-media-filmstrip-selection>{selectionLabel}</span></div>
18
+ <ol class="ui-media-filmstrip__items"><slot /></ol>
19
+ </section>
@@ -0,0 +1,29 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
+ import Button from './Button.astro'
7
+
8
+ interface Props extends HTMLAttributes<'button'> {
9
+ className?: string
10
+ label: string
11
+ mediaId: string
12
+ order?: number
13
+ selected?: boolean
14
+ state?: 'ready' | 'loading' | 'error'
15
+ stateLabel?: string
16
+ }
17
+
18
+ const { class: classProp, className, disabled, label, mediaId, order, selected = false, state = 'ready', stateLabel, ...rest } = Astro.props
19
+ const position = order !== undefined && Number.isSafeInteger(order) && order > 0 ? order : undefined
20
+ const status = stateLabel ?? (state === 'loading' ? 'Loading' : 'Unavailable')
21
+ const { classList, passthrough } = resolveLumenAstroProps(rest, ['ui-media-thumbnail'], classProp, className)
22
+ ---
23
+
24
+ <Button {...passthrough} class:list={classList} variant="outline" data-ui-media-thumbnail data-media-id={mediaId} aria-busy={state === 'loading' || undefined} aria-pressed={selected} disabled={Boolean(disabled) || state !== 'ready'} data-state={state}>
25
+ <span class="ui-media-thumbnail__media" aria-hidden="true"><slot /></span>
26
+ <span class="ui-media-thumbnail__label">{label}</span>
27
+ {position !== undefined && <span class="ui-media-thumbnail__order">{position}</span>}
28
+ {state !== 'ready' && <span class="ui-media-thumbnail__state">{status}</span>}
29
+ </Button>
@@ -0,0 +1,38 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import { formatLumenMediaZoom, lumenMediaViewportActions, type LumenMediaViewportLabels, lumenMediaViewportLabels, type LumenMediaViewportValue, normalizeLumenMediaViewport, resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
+ import Button from './Button.astro'
7
+
8
+ interface Props extends HTMLAttributes<'figure'> {
9
+ className?: string
10
+ disabled?: boolean
11
+ label: string
12
+ labels?: Partial<LumenMediaViewportLabels>
13
+ locale?: string
14
+ maxZoom?: number
15
+ ratio?: number
16
+ value?: Partial<LumenMediaViewportValue>
17
+ }
18
+
19
+ const { class: classProp, className, disabled = false, label, labels, locale, maxZoom = 4, ratio = 16 / 9, value, ...rest } = Astro.props
20
+ const current = normalizeLumenMediaViewport(value, maxZoom)
21
+ const copy = { ...lumenMediaViewportLabels, ...labels }
22
+ const safeRatio = Number.isFinite(ratio) && ratio >= 0.1 && ratio <= 10 ? ratio : 16 / 9
23
+ const statusId = `media-viewport-${crypto.randomUUID()}`
24
+ const { classList, passthrough } = resolveLumenAstroProps(rest, ['ui-media-viewport'], classProp, className)
25
+ ---
26
+
27
+ <figure class:list={classList} {...passthrough} data-ui-media-viewport data-disabled={String(disabled)} data-max-zoom={maxZoom} data-zoom={current.zoom} data-pan-x={current.x} data-pan-y={current.y} data-locale={locale}>
28
+ <div class="ui-media-viewport__stage" data-ui-media-viewport-stage role="group" aria-label={label} aria-disabled={disabled} aria-describedby={statusId} tabindex="0" style={`aspect-ratio: ${safeRatio}`}>
29
+ <div class="ui-media-viewport__content" data-ui-media-viewport-content style={`transform: translate(${current.x * (current.zoom - 1) * 50}%, ${current.y * (current.zoom - 1) * 50}%) scale(${current.zoom})`}><slot /></div>
30
+ </div>
31
+ <figcaption>
32
+ <span>{label}</span>
33
+ <span id={statusId} role="status" aria-live="polite" data-ui-media-viewport-status>{formatLumenMediaZoom(current.zoom, locale)}</span>
34
+ <div class="ui-media-viewport__actions" role="group" aria-label={label}>
35
+ {lumenMediaViewportActions.map(action => <Button variant="outline" data-ui-media-viewport-action={action} disabled>{copy[action]}</Button>)}
36
+ </div>
37
+ </figcaption>
38
+ </figure>
@@ -49,7 +49,7 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
49
49
  />
50
50
  <ul class="ui-mentions__list" data-ui-mentions-list hidden id={listId} role="listbox">
51
51
  {normalized.map((option, index) => (
52
- <li>
52
+ <li role="presentation">
53
53
  <button
54
54
  aria-selected="false"
55
55
  class="ui-mentions__option"
@@ -57,6 +57,7 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
57
57
  data-value={option.value}
58
58
  id={`${listId}-option-${index}`}
59
59
  role="option"
60
+ tabindex={-1}
60
61
  type="button"
61
62
  >
62
63
  {option.label}
@@ -7,12 +7,16 @@ interface Props extends HTMLAttributes<'div'> {
7
7
  class?: string | null
8
8
  className?: string
9
9
  glass?: boolean | 'subtle' | 'strong'
10
+ autoScroll?: boolean
11
+ scrollThreshold?: number
10
12
  }
11
13
 
12
14
  const {
13
15
  class: classProp = '',
14
16
  className = '',
15
17
  glass = false,
18
+ autoScroll = false,
19
+ scrollThreshold = 32,
16
20
  ...rest
17
21
  } = Astro.props
18
22
 
@@ -25,6 +29,8 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
25
29
  ---
26
30
 
27
31
  <div
32
+ data-ui-message-scroller={autoScroll ? '' : undefined}
33
+ scroll-threshold={scrollThreshold}
28
34
  class:list={classList}
29
35
  {...passthrough}
30
36
  >
@@ -0,0 +1,19 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import { type LumenWorkflowDuration, resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
+ interface Props extends HTMLAttributes<'div'> {
7
+ class?: string | null
8
+ className?: string
9
+ duration?: LumenWorkflowDuration
10
+ enterExit?: boolean
11
+ }
12
+
13
+ const { class: classProp, className, duration = 'standard', enterExit = true, ...rest } = Astro.props
14
+ const { classList, passthrough } = resolveLumenAstroProps(rest, ['ui-motion-group'], classProp, className)
15
+ ---
16
+
17
+ <div class:list={classList} data-ui-motion-group data-ui-motion-duration={duration} data-ui-motion-enter-exit={String(enterExit)} {...passthrough}>
18
+ <slot />
19
+ </div>
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  import type { HTMLAttributes } from 'astro/types'
3
3
 
4
- import { resolveLumenAstroProps } from '@santi020k/lumen-core'
4
+ import { type LumenControlVisualSize, resolveLumenAstroProps } from '@santi020k/lumen-core'
5
5
 
6
6
  interface Option {
7
7
  disabled?: boolean
@@ -17,7 +17,7 @@ interface Props extends Omit<HTMLAttributes<'select'>, 'size'> {
17
17
  size?: HTMLAttributes<'select'>['size']
18
18
  options?: SelectOption[]
19
19
  placeholder?: string
20
- visualSize?: 'default' | 'lg' | 'sm'
20
+ visualSize?: LumenControlVisualSize
21
21
  }
22
22
 
23
23
  const {
@@ -9,6 +9,7 @@ interface Props extends HTMLAttributes<'nav'> {
9
9
  }
10
10
 
11
11
  const {
12
+ 'aria-label': ariaLabel = 'Pagination',
12
13
  class: classProp = '',
13
14
  className = '',
14
15
  ...rest
@@ -20,7 +21,7 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
20
21
  ---
21
22
 
22
23
  <nav
23
- aria-label="Pagination"
24
+ aria-label={ariaLabel}
24
25
  class:list={classList}
25
26
  {...passthrough}
26
27
  >
@@ -65,7 +65,7 @@ const { classList } = resolveLumenAstroProps({}, ['ui-password-field'], classPro
65
65
  type="button"
66
66
  variant="ghost"
67
67
  >
68
- <span aria-hidden="true" data-ui-password-toggle-label>Show</span>
68
+ <span aria-hidden="true" data-ui-password-toggle-label>{showLabel}</span>
69
69
  </Button>
70
70
  </div>
71
71
  {hint && <p class="ui-field__hint" data-ui-field-hint id={hintId}>{hint}</p>}