@santi020k/lumen-astro 0.3.0 → 1.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 (158) hide show
  1. package/README.md +100 -2
  2. package/components/Accordion.astro +4 -4
  3. package/components/Affix.astro +2 -2
  4. package/components/Agenda.astro +3 -2
  5. package/components/Alert.astro +9 -9
  6. package/components/AlertDialog.astro +7 -13
  7. package/components/Anchor.astro +29 -3
  8. package/components/AnimatedLogo.astro +2 -1
  9. package/components/AnimatedNumber.astro +5 -2
  10. package/components/AnimatedPortrait.astro +46 -36
  11. package/components/AspectRatio.astro +10 -8
  12. package/components/Attachment.astro +24 -24
  13. package/components/Autocomplete.astro +3 -2
  14. package/components/Avatar.astro +4 -3
  15. package/components/BackToTop.astro +2 -2
  16. package/components/Badge.astro +9 -9
  17. package/components/BarChart.astro +211 -0
  18. package/components/Breadcrumb.astro +3 -3
  19. package/components/Bubble.astro +7 -7
  20. package/components/Button.astro +18 -16
  21. package/components/ButtonGroup.astro +3 -3
  22. package/components/ButtonLink.astro +9 -3
  23. package/components/Calendar.astro +43 -31
  24. package/components/Callout.astro +2 -2
  25. package/components/Card.astro +12 -10
  26. package/components/CardContent.astro +26 -0
  27. package/components/CardDescription.astro +26 -0
  28. package/components/CardFooter.astro +26 -0
  29. package/components/CardHeader.astro +26 -0
  30. package/components/CardTitle.astro +26 -0
  31. package/components/Carousel.astro +6 -6
  32. package/components/Cascader.astro +10 -4
  33. package/components/Chart.astro +32 -6
  34. package/components/Checkbox.astro +3 -2
  35. package/components/CheckboxGroup.astro +39 -0
  36. package/components/Code.astro +55 -52
  37. package/components/CodeTabs.astro +6 -6
  38. package/components/Collapsible.astro +3 -3
  39. package/components/ColorPicker.astro +3 -2
  40. package/components/Combobox.astro +4 -5
  41. package/components/Command.astro +6 -6
  42. package/components/Container.astro +29 -0
  43. package/components/ContextMenu.astro +7 -13
  44. package/components/CopyButton.astro +55 -0
  45. package/components/CoverImage.astro +19 -9
  46. package/components/DataTable.astro +50 -49
  47. package/components/DatePicker.astro +20 -17
  48. package/components/DateRangePicker.astro +3 -2
  49. package/components/Descriptions.astro +3 -3
  50. package/components/Dialog.astro +8 -14
  51. package/components/Direction.astro +3 -3
  52. package/components/Drawer.astro +7 -13
  53. package/components/DropdownMenu.astro +7 -13
  54. package/components/Empty.astro +6 -6
  55. package/components/ErrorSummary.astro +54 -0
  56. package/components/Eyebrow.astro +2 -2
  57. package/components/Field.astro +7 -6
  58. package/components/FieldError.astro +29 -0
  59. package/components/FileUpload.astro +2 -2
  60. package/components/FloatingBadge.astro +2 -2
  61. package/components/Form.astro +33 -0
  62. package/components/FormattedDate.astro +2 -2
  63. package/components/GradientDivider.astro +2 -2
  64. package/components/Grid.astro +46 -0
  65. package/components/HoverCard.astro +7 -13
  66. package/components/Icon.astro +8 -6
  67. package/components/Image.astro +2 -1
  68. package/components/Input.astro +6 -5
  69. package/components/InputGroup.astro +3 -3
  70. package/components/InputOTP.astro +5 -3
  71. package/components/Item.astro +11 -8
  72. package/components/Kbd.astro +3 -3
  73. package/components/Label.astro +3 -2
  74. package/components/LanguageToggle.astro +2 -2
  75. package/components/LineChart.astro +244 -0
  76. package/components/Link.astro +6 -5
  77. package/components/ListBox.astro +154 -0
  78. package/components/Marker.astro +6 -6
  79. package/components/Mentions.astro +30 -10
  80. package/components/Menubar.astro +7 -13
  81. package/components/Message.astro +5 -5
  82. package/components/MessageScroller.astro +6 -6
  83. package/components/Meter.astro +1 -0
  84. package/components/NativeSelect.astro +4 -5
  85. package/components/NavigationMenu.astro +9 -14
  86. package/components/Note.astro +1 -1
  87. package/components/NumberField.astro +3 -2
  88. package/components/Pagination.astro +3 -3
  89. package/components/Particles.astro +58 -2
  90. package/components/PasswordField.astro +73 -0
  91. package/components/PhoneInput.astro +5 -7
  92. package/components/PieChart.astro +141 -0
  93. package/components/Pill.astro +15 -9
  94. package/components/Popconfirm.astro +8 -3
  95. package/components/Popover.astro +7 -13
  96. package/components/Progress.astro +12 -4
  97. package/components/Prose.astro +2 -2
  98. package/components/QRCode.astro +13 -5
  99. package/components/RadioGroup.astro +3 -3
  100. package/components/Rating.astro +3 -1
  101. package/components/Resizable.astro +8 -7
  102. package/components/RevealGroup.astro +4 -2
  103. package/components/RichTextEditor.astro +3 -2
  104. package/components/Schedule.astro +3 -2
  105. package/components/ScrollArea.astro +6 -6
  106. package/components/ScrollProgress.astro +38 -0
  107. package/components/ScrollReveal.astro +4 -2
  108. package/components/SearchField.astro +3 -2
  109. package/components/Segmented.astro +4 -6
  110. package/components/Select.astro +23 -14
  111. package/components/Separator.astro +5 -5
  112. package/components/Sheet.astro +7 -13
  113. package/components/Sidebar.astro +9 -14
  114. package/components/Skeleton.astro +4 -3
  115. package/components/SkipLink.astro +2 -2
  116. package/components/Slider.astro +3 -2
  117. package/components/Sonner.astro +3 -3
  118. package/components/Sparkline.astro +72 -0
  119. package/components/SpeedDial.astro +3 -3
  120. package/components/Spinner.astro +4 -3
  121. package/components/Stack.astro +46 -0
  122. package/components/Stat.astro +14 -8
  123. package/components/StatDescription.astro +16 -0
  124. package/components/StatIcon.astro +15 -0
  125. package/components/StatLabel.astro +16 -0
  126. package/components/StatTrend.astro +28 -0
  127. package/components/StatValue.astro +16 -0
  128. package/components/Stepper.astro +11 -8
  129. package/components/Switch.astro +3 -2
  130. package/components/Table.astro +7 -6
  131. package/components/Tabs.astro +6 -6
  132. package/components/TagGroup.astro +3 -2
  133. package/components/Textarea.astro +3 -2
  134. package/components/ThemeBuilder.astro +3 -2
  135. package/components/ThemeToggle.astro +5 -3
  136. package/components/TimeField.astro +3 -2
  137. package/components/Timeline.astro +2 -1
  138. package/components/TimelineItem.astro +2 -1
  139. package/components/Toast.astro +10 -16
  140. package/components/Toggle.astro +3 -2
  141. package/components/ToggleGroup.astro +3 -3
  142. package/components/Toolbar.astro +2 -2
  143. package/components/Tooltip.astro +3 -3
  144. package/components/Tour.astro +3 -3
  145. package/components/Transfer.astro +16 -5
  146. package/components/Tree.astro +3 -2
  147. package/components/TreeGrid.astro +3 -2
  148. package/components/TreeSelect.astro +10 -4
  149. package/components/Typography.astro +3 -3
  150. package/components/VirtualList.astro +3 -2
  151. package/components/VisuallyHidden.astro +27 -0
  152. package/components/Watermark.astro +11 -7
  153. package/forms.ts +46 -0
  154. package/index.ts +31 -0
  155. package/package.json +12 -8
  156. package/runtime/UIPrimitives.astro +3764 -3142
  157. package/runtime/controllers/motion.ts +13 -16
  158. package/index.d.ts +0 -126
@@ -1,37 +1,31 @@
1
1
  ---
2
- import { resolveLumenAstroProps, resolveLumenGlass } from '@santi020k/lumen-core'
3
-
4
2
  import type { HTMLAttributes } from 'astro/types'
5
3
 
4
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
6
  interface Props extends HTMLAttributes<'aside'> {
7
7
  class?: string | null
8
8
  className?: string
9
9
  glass?: boolean | 'subtle' | 'strong'
10
- /**
11
- * @deprecated Use `glass` instead. `surface="glass"` maps to `glass={true}`.
12
- */
13
- surface?: 'default' | 'glass'
14
10
  }
15
11
 
16
12
  const {
17
13
  class: classProp = '',
18
14
  className = '',
19
15
  glass = false,
20
- surface = 'default',
21
16
  ...rest
22
17
  } = Astro.props
23
18
 
24
- const resolvedGlass = resolveLumenGlass(glass, surface)
25
19
  const { classList, passthrough } = resolveLumenAstroProps(rest, [
26
- 'ui-hover-card',
27
- resolvedGlass && 'ui-hover-card--glass',
28
- glass === 'subtle' && 'ui-glass-subtle',
29
- glass === 'strong' && 'ui-glass-strong'
20
+ 'ui-hover-card',
21
+ glass && 'ui-hover-card--glass',
22
+ glass === 'subtle' && 'ui-glass-subtle',
23
+ glass === 'strong' && 'ui-glass-strong'
30
24
  ], classProp, className)
31
25
  ---
32
26
 
33
27
  <aside
34
- data-surface={resolvedGlass ? 'glass' : 'default'}
28
+ data-surface={glass ? 'glass' : 'default'}
35
29
  data-ui-hover-card
36
30
  class:list={classList}
37
31
  {...passthrough}
@@ -1,11 +1,10 @@
1
1
  ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
2
4
  import {
5
+ type LumenIconName,
3
6
  renderLumenIconSvg,
4
- resolveLumenAstroProps,
5
- type LumenIconName
6
- } from '@santi020k/lumen-core'
7
-
8
- import type { HTMLAttributes } from 'astro/types'
7
+ resolveLumenAstroProps } from '@santi020k/lumen-core'
9
8
 
10
9
  type IconSize = 'default' | 'lg' | 'sm' | 'xl'
11
10
 
@@ -32,7 +31,10 @@ const isDecorative = decorative ?? !label
32
31
  const iconSvg = name ? renderLumenIconSvg(name) : ''
33
32
 
34
33
  if (name && !iconSvg && import.meta.env.DEV) {
35
- console.warn(`[Lumen Icon] Unknown Lucide icon "${name}". Use the default slot for brand or project-specific SVGs.`)
34
+ console.warn(
35
+ `[Lumen Icon] Unknown icon "${name}". ` +
36
+ 'Register an icon pack or use the default slot for project-specific SVGs.'
37
+ )
36
38
  }
37
39
 
38
40
  const { classList, passthrough } = resolveLumenAstroProps(rest, [
@@ -1,11 +1,12 @@
1
1
  ---
2
- import { resolveLumenAstroProps } from '@santi020k/lumen-core'
3
2
  import {
4
3
  Image as AstroImage,
5
4
  type LocalImageProps,
6
5
  type RemoteImageProps
7
6
  } from 'astro:assets'
8
7
 
8
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
9
+
9
10
  type Props = (LocalImageProps | RemoteImageProps) & {
10
11
  class?: string | null
11
12
  className?: string
@@ -1,8 +1,8 @@
1
1
  ---
2
- import { resolveLumenAstroProps } from '@santi020k/lumen-core'
3
-
4
2
  import type { HTMLAttributes } from 'astro/types'
5
3
 
4
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
6
  interface Props extends Omit<HTMLAttributes<'input'>, 'size'> {
7
7
  class?: string | null
8
8
  className?: string
@@ -10,10 +10,11 @@ interface Props extends Omit<HTMLAttributes<'input'>, 'size'> {
10
10
  }
11
11
 
12
12
  const { class: classProp = '', className = '', size = 'default', type = 'text', ...rest } = Astro.props
13
+
13
14
  const { classList, passthrough } = resolveLumenAstroProps(rest, [
14
- 'ui-input',
15
- size === 'sm' && 'ui-input--sm',
16
- size === 'lg' && 'ui-input--lg'
15
+ 'ui-input',
16
+ size === 'sm' && 'ui-input--sm',
17
+ size === 'lg' && 'ui-input--lg'
17
18
  ], classProp, className)
18
19
  ---
19
20
 
@@ -1,8 +1,8 @@
1
1
  ---
2
- import { resolveLumenAstroProps } from '@santi020k/lumen-core'
3
-
4
2
  import type { HTMLAttributes } from 'astro/types'
5
3
 
4
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
6
  interface Props extends HTMLAttributes<'div'> {
7
7
  class?: string | null
8
8
  className?: string
@@ -15,7 +15,7 @@ const {
15
15
  } = Astro.props
16
16
 
17
17
  const { classList, passthrough } = resolveLumenAstroProps(rest, [
18
- 'ui-input-group'
18
+ 'ui-input-group'
19
19
  ], classProp, className)
20
20
  ---
21
21
 
@@ -1,8 +1,8 @@
1
1
  ---
2
- import { resolveLumenAstroProps } from '@santi020k/lumen-core'
3
-
4
2
  import type { HTMLAttributes } from 'astro/types'
5
3
 
4
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
6
  interface Props extends Omit<HTMLAttributes<'input'>, 'size'> {
7
7
  class?: string | null
8
8
  className?: string
@@ -20,9 +20,11 @@ const {
20
20
  pattern = '[0-9]*',
21
21
  ...rest
22
22
  } = Astro.props
23
+
23
24
  const { classList, passthrough } = resolveLumenAstroProps(rest, ['ui-input-otp-field'
24
25
  ], classProp, className)
25
- const segments = Array.from({ length: Math.max(1, Number(length) || 6) }, (_, index) => index)
26
+
27
+ const segments = Array.from({ length: Math.max(1, length || 6) }, (_, index) => index)
26
28
  ---
27
29
 
28
30
  <div
@@ -1,15 +1,17 @@
1
1
  ---
2
- import { resolveLumenAstroProps } from '@santi020k/lumen-core'
3
-
4
2
  import type { HTMLAttributes } from 'astro/types'
5
3
 
4
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
6
  interface Props extends HTMLAttributes<'div'> {
7
+ as?: 'article' | 'div' | 'li' | 'section'
7
8
  class?: string | null
8
9
  className?: string
9
10
  glass?: boolean | 'subtle' | 'strong'
10
11
  }
11
12
 
12
13
  const {
14
+ as: Tag = 'div',
13
15
  class: classProp = '',
14
16
  className = '',
15
17
  glass = false,
@@ -17,16 +19,17 @@ const {
17
19
  } = Astro.props
18
20
 
19
21
  const { classList, passthrough } = resolveLumenAstroProps(rest, [
20
- 'ui-item',
21
- glass && 'ui-item--glass',
22
- glass === 'subtle' && 'ui-glass-subtle',
23
- glass === 'strong' && 'ui-glass-strong'
22
+ 'ui-item',
23
+ glass && 'ui-item--glass',
24
+ glass === 'subtle' && 'ui-glass-subtle',
25
+ glass === 'strong' && 'ui-glass-strong'
24
26
  ], classProp, className)
25
27
  ---
26
28
 
27
- <div
29
+ <Tag
28
30
  class:list={classList}
31
+ data-slot="item"
29
32
  {...passthrough}
30
33
  >
31
34
  <slot />
32
- </div>
35
+ </Tag>
@@ -1,8 +1,8 @@
1
1
  ---
2
- import { resolveLumenAstroProps } from '@santi020k/lumen-core'
3
-
4
2
  import type { HTMLAttributes } from 'astro/types'
5
3
 
4
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
6
  interface Props extends HTMLAttributes<'kbd'> {
7
7
  class?: string | null
8
8
  className?: string
@@ -15,7 +15,7 @@ const {
15
15
  } = Astro.props
16
16
 
17
17
  const { classList, passthrough } = resolveLumenAstroProps(rest, [
18
- 'ui-kbd'
18
+ 'ui-kbd'
19
19
  ], classProp, className)
20
20
  ---
21
21
 
@@ -1,14 +1,15 @@
1
1
  ---
2
- import { resolveLumenAstroProps } from '@santi020k/lumen-core'
3
-
4
2
  import type { HTMLAttributes } from 'astro/types'
5
3
 
4
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
6
  interface Props extends HTMLAttributes<'label'> {
7
7
  class?: string | null
8
8
  className?: string
9
9
  }
10
10
 
11
11
  const { class: classProp = '', className = '', ...rest } = Astro.props
12
+
12
13
  const { classList, passthrough } = resolveLumenAstroProps(rest, ['ui-label'
13
14
  ], classProp, className)
14
15
  ---
@@ -1,8 +1,8 @@
1
1
  ---
2
- import { resolveLumenAstroProps } from '@santi020k/lumen-core'
3
-
4
2
  import type { HTMLAttributes } from 'astro/types'
5
3
 
4
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
6
  interface Props extends HTMLAttributes<'button'> {
7
7
  class?: string | null
8
8
  className?: string
@@ -0,0 +1,244 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import {
5
+ alignLumenChartSeries,
6
+ createLumenLineGeometry,
7
+ getLumenChartCategories,
8
+ getLumenChartDomain,
9
+ getLumenChartTicks,
10
+ getLumenChartToneClassName,
11
+ hasLumenChartData,
12
+ type LumenChartSeries,
13
+ resolveLumenChartTone,
14
+ scaleLumenChartValue
15
+ } from '@santi020k/lumen-core'
16
+
17
+ import Chart from './Chart.astro'
18
+
19
+ interface Props extends HTMLAttributes<'figure'> {
20
+ area?: boolean
21
+ caption?: string
22
+ class?: string | null
23
+ className?: string
24
+ description?: string
25
+ emptyLabel?: string
26
+ formatCategory?: (category: number | string) => string
27
+ formatValue?: (value: number) => string
28
+ glass?: boolean | 'subtle' | 'strong'
29
+ heading?: string
30
+ markers?: 'all' | 'auto' | 'none' | boolean | number
31
+ presentation?: 'bare' | 'default'
32
+ referenceValue?: number
33
+ series: readonly LumenChartSeries[]
34
+ showLegend?: boolean
35
+ showTable?: boolean
36
+ }
37
+
38
+ const {
39
+ area = false,
40
+ caption,
41
+ class: classProp = '',
42
+ className = '',
43
+ description,
44
+ emptyLabel = 'No chart data available.',
45
+ formatCategory = String,
46
+ formatValue = String,
47
+ glass = false,
48
+ heading,
49
+ markers = 'auto',
50
+ referenceValue,
51
+ series,
52
+ showLegend = series.length > 1,
53
+ showTable = true,
54
+ ...rest
55
+ } = Astro.props
56
+
57
+ const width = 640
58
+ const height = 320
59
+ const padding = 44
60
+ const categories = getLumenChartCategories(series)
61
+ const hasData = hasLumenChartData(series)
62
+ const alignedSeries = series.map(item => alignLumenChartSeries(item, categories))
63
+
64
+ const domain = getLumenChartDomain([
65
+ ...alignedSeries.flatMap(item => item.data.map(datum => datum.y)),
66
+ referenceValue ?? null
67
+ ], false)
68
+
69
+ const geometries = alignedSeries.map(item => createLumenLineGeometry(item.data, {
70
+ domain,
71
+ height,
72
+ includeZero: false,
73
+ padding,
74
+ width
75
+ }))
76
+
77
+ const ticks = getLumenChartTicks(domain)
78
+ const labelStep = Math.max(1, Math.ceil(categories.length / 8))
79
+ let markerStep = 1
80
+
81
+ if (markers === false || markers === 'none') markerStep = Number.POSITIVE_INFINITY
82
+ else if (typeof markers === 'number') markerStep = Math.max(1, Math.round(markers))
83
+ else if (markers === 'auto') markerStep = Math.max(1, Math.ceil(categories.length / 24))
84
+
85
+ const valueFor = (item: LumenChartSeries, category: number | string) => item.data.find(datum => datum.x === category)
86
+
87
+ const categoryLabelFor = (category: number | string) => series.flatMap(item => item.data)
88
+ .find(datum => datum.x === category)?.xLabel ?? formatCategory(category)
89
+
90
+ const referenceY =
91
+ referenceValue === undefined ?
92
+ undefined :
93
+ scaleLumenChartValue(referenceValue, domain, height - padding, padding)
94
+ ---
95
+
96
+ <Chart
97
+ class:list={['ui-line-chart', classProp, className]}
98
+ {caption}
99
+ {description}
100
+ {glass}
101
+ {heading}
102
+ {...rest}
103
+ >
104
+ {
105
+ showLegend && hasData && (
106
+ <ul class="ui-chart__legend" aria-label="Chart legend">
107
+ {series.map((item, index) => (
108
+ <li class={getLumenChartToneClassName(item.tone, index)}>
109
+ <span aria-hidden="true" />
110
+ {item.label}
111
+ </li>
112
+ ))}
113
+ </ul>
114
+ )
115
+ }
116
+ {!hasData && <p class="ui-chart__empty" role="status">{emptyLabel}</p>}
117
+ <div class="ui-chart__plot" hidden={!hasData}>
118
+ <svg
119
+ aria-hidden="true"
120
+ preserveAspectRatio="xMidYMid meet"
121
+ viewBox={`0 0 ${width} ${height}`}
122
+ >
123
+ <g class="ui-chart__grid">
124
+ {
125
+ ticks.map(tick => {
126
+ const y = scaleLumenChartValue(
127
+ tick, domain, height - padding, padding
128
+ )
129
+
130
+ return (
131
+ <>
132
+ <line x1={padding} x2={width - padding} y1={y} y2={y} />
133
+ <text x={padding - 8} y={y}>
134
+ {formatValue(tick)}
135
+ </text>
136
+ </>
137
+ )
138
+ })
139
+ }
140
+ </g>
141
+ <g class="ui-chart__axis-labels">
142
+ {
143
+ categories.map((category, index) => {
144
+ if (
145
+ index % labelStep !== 0 &&
146
+ index !== categories.length - 1
147
+ )
148
+ return null
149
+
150
+ const denominator = Math.max(1, categories.length - 1)
151
+
152
+ const x =
153
+ padding + (index / denominator) * (width - padding * 2)
154
+
155
+ return (
156
+ <text text-anchor="middle" x={x} y={height - 14}>
157
+ {categoryLabelFor(category)}
158
+ </text>
159
+ )
160
+ })
161
+ }
162
+ </g>
163
+ {
164
+ referenceY !== undefined && (
165
+ <line
166
+ class="ui-chart__reference"
167
+ x1={padding}
168
+ x2={width - padding}
169
+ y1={referenceY}
170
+ y2={referenceY}
171
+ />
172
+ )
173
+ }
174
+ {
175
+ geometries.map((geometry, index) => {
176
+ const item = series[index]
177
+
178
+ if (!item) return null
179
+
180
+ const tone = resolveLumenChartTone(item.tone, index)
181
+
182
+ return (
183
+ <g class:list={['ui-line-chart__series', getLumenChartToneClassName(tone)]}>
184
+ {area &&
185
+ geometry.areaPaths.map(path => (
186
+ <path class="ui-line-chart__area" d={path} />
187
+ ))}
188
+ <path class="ui-line-chart__line" d={geometry.path} />
189
+ {Number.isFinite(markerStep) &&
190
+ geometry.points.map((point, pointIndex) => pointIndex % markerStep === 0 && (
191
+ <circle
192
+ class="ui-line-chart__point"
193
+ cx={point.xCoordinate}
194
+ cy={point.yCoordinate}
195
+ r="3"
196
+ >
197
+ <title>
198
+ {`${point.xLabel ?? formatCategory(point.x)} · ${item.label}: ${formatValue(point.y ?? 0)}`}
199
+ </title>
200
+ </circle>
201
+ ))}
202
+ </g>
203
+ )
204
+ })
205
+ }
206
+ </svg>
207
+ </div>
208
+ {
209
+ showTable && hasData && (
210
+ <details class="ui-chart__data">
211
+ <summary>View chart data</summary>
212
+ <div>
213
+ <table>
214
+ <thead>
215
+ <tr>
216
+ <th scope="col">Category</th>
217
+ {series.map(item => <th scope="col">{item.label}</th>)}
218
+ </tr>
219
+ </thead>
220
+ <tbody>
221
+ {categories.map(category => (
222
+ <tr>
223
+ <th scope="row">{categoryLabelFor(category)}</th>
224
+ {series.map(item => {
225
+ const datum = valueFor(item, category)
226
+
227
+ return (
228
+ <td>
229
+ {datum?.label ??
230
+ (datum?.y === null || datum === undefined ?
231
+ 'Not available' :
232
+ formatValue(datum.y))}
233
+ </td>
234
+ )
235
+ })}
236
+ </tr>
237
+ ))}
238
+ </tbody>
239
+ </table>
240
+ </div>
241
+ </details>
242
+ )
243
+ }
244
+ </Chart>
@@ -1,8 +1,8 @@
1
1
  ---
2
- import { resolveLumenAstroProps } from '@santi020k/lumen-core'
3
-
4
2
  import type { HTMLAttributes } from 'astro/types'
5
3
 
4
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
6
  interface Props extends HTMLAttributes<'a'> {
7
7
  class?: string | null
8
8
  className?: string
@@ -25,13 +25,14 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
25
25
  variant === 'inherit' && 'ui-link--inherit'
26
26
  ], classProp, className)
27
27
 
28
- const safeRel = newTab
29
- ? [...new Set(`${rel ?? ''} noopener noreferrer`.trim().split(/\s+/))].join(' ')
30
- : rel
28
+ const safeRel = newTab ?
29
+ [...new Set(`${rel ?? ''} noopener noreferrer`.trim().split(/\s+/))].join(' ') :
30
+ rel
31
31
  ---
32
32
 
33
33
  <a
34
34
  class:list={classList}
35
+ data-slot="link"
35
36
  data-variant={variant}
36
37
  rel={safeRel}
37
38
  target={newTab ? '_blank' : target}
@@ -0,0 +1,154 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
+ interface ListBoxOption {
7
+ disabled?: boolean
8
+ label: string
9
+ section?: string
10
+ value: string
11
+ }
12
+
13
+ interface Props extends Omit<HTMLAttributes<'select'>, 'size' | 'value'> {
14
+ class?: string | null
15
+ className?: string
16
+ emptyMessage?: string
17
+ label?: string
18
+ loading?: boolean
19
+ loadingMessage?: string
20
+ multiple?: boolean
21
+ options?: (ListBoxOption | string)[]
22
+ selectionFollowsFocus?: boolean
23
+ value?: string | string[]
24
+ }
25
+
26
+ const {
27
+ class: classProp = '',
28
+ className = '',
29
+ disabled = false,
30
+ emptyMessage = 'No options available',
31
+ id,
32
+ label,
33
+ loading = false,
34
+ loadingMessage = 'Loading options',
35
+ multiple = false,
36
+ name,
37
+ options = [],
38
+ required = false,
39
+ selectionFollowsFocus = false,
40
+ value,
41
+ ...rest
42
+ } = Astro.props
43
+
44
+ const normalizedOptions = options.map(option => typeof option === 'string' ? { label: option, value: option } : option)
45
+ const optionGroups: { label?: string, options: ListBoxOption[] }[] = []
46
+
47
+ for (const option of normalizedOptions) {
48
+ const currentGroup = optionGroups.at(-1)
49
+
50
+ if (!currentGroup || currentGroup.label !== option.section) {
51
+ optionGroups.push({
52
+ ...(option.section ? { label: option.section } : {}),
53
+ options: [option]
54
+ })
55
+ } else {
56
+ currentGroup.options.push(option)
57
+ }
58
+ }
59
+
60
+ let initialValues: string[] = []
61
+
62
+ if (Array.isArray(value)) initialValues = value
63
+ else if (value !== undefined) initialValues = [value]
64
+
65
+ const selectedValues = new Set(initialValues)
66
+ const controlId = id ?? `ui-list-box-${name ?? 'field'}`
67
+ const listId = `${controlId}-list`
68
+ const { classList, passthrough } = resolveLumenAstroProps(rest, ['ui-list-box'], classProp, className)
69
+ ---
70
+
71
+ <div
72
+ class:list={classList}
73
+ data-loading={loading ? 'true' : undefined}
74
+ data-multiple={multiple ? 'true' : undefined}
75
+ data-selection-follows-focus={selectionFollowsFocus ? 'true' : undefined}
76
+ data-ui-list-box
77
+ >
78
+ {label && <label class="ui-label" for={controlId}>{label}</label>}
79
+ <select
80
+ class="ui-list-box__native"
81
+ data-ui-list-box-native
82
+ disabled={disabled || loading}
83
+ id={controlId}
84
+ multiple={multiple}
85
+ name={name}
86
+ required={required}
87
+ size={Math.min(Math.max(normalizedOptions.length, 2), 8)}
88
+ {...passthrough}
89
+ >
90
+ {optionGroups.map(group => group.label ?
91
+ (
92
+ <optgroup label={group.label}>
93
+ {group.options.map(option => (
94
+ <option
95
+ disabled={option.disabled}
96
+ selected={selectedValues.has(option.value)}
97
+ value={option.value}
98
+ >
99
+ {option.label}
100
+ </option>
101
+ ))}
102
+ </optgroup>
103
+ ) :
104
+ group.options.map(option => (
105
+ <option
106
+ disabled={option.disabled}
107
+ selected={selectedValues.has(option.value)}
108
+ value={option.value}
109
+ >
110
+ {option.label}
111
+ </option>
112
+ )))}
113
+ </select>
114
+ <div
115
+ aria-busy={loading ? 'true' : undefined}
116
+ aria-disabled={disabled || loading ? 'true' : undefined}
117
+ aria-multiselectable={multiple ? 'true' : undefined}
118
+ class="ui-list-box__list"
119
+ data-ui-list-box-list
120
+ hidden
121
+ id={listId}
122
+ role="listbox"
123
+ tabindex={disabled || loading ? undefined : '0'}
124
+ >
125
+ {!loading && !normalizedOptions.length && (
126
+ <div aria-disabled="true" class="ui-list-box__empty" role="option">{emptyMessage}</div>
127
+ )}
128
+ {!loading && optionGroups.map((group, groupIndex) => {
129
+ const groupLabelId = group.label ? `${controlId}-group-${groupIndex}` : undefined
130
+
131
+ const renderedOptions = group.options.map(option => (
132
+ <div
133
+ aria-disabled={option.disabled ? 'true' : undefined}
134
+ aria-selected={selectedValues.has(option.value) ? 'true' : 'false'}
135
+ data-ui-list-box-option
136
+ data-value={option.value}
137
+ role="option"
138
+ >
139
+ {option.label}
140
+ </div>
141
+ ))
142
+
143
+ return group.label ?
144
+ (
145
+ <div aria-labelledby={groupLabelId} role="group">
146
+ <div class="ui-list-box__section" id={groupLabelId}>{group.label}</div>
147
+ {renderedOptions}
148
+ </div>
149
+ ) :
150
+ renderedOptions
151
+ })}
152
+ </div>
153
+ {loading && <p class="ui-list-box__status" role="status">{loadingMessage}</p>}
154
+ </div>