@santi020k/lumen-astro 1.6.0 → 1.7.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.
@@ -1,7 +1,15 @@
1
1
  ---
2
2
  import type { HTMLAttributes } from 'astro/types'
3
3
 
4
- import { resolveLumenAstroProps } from '@santi020k/lumen-core'
4
+ import {
5
+ getLumenPhoneCountries,
6
+ getLumenPhoneCountry,
7
+ type LumenPhoneCountry,
8
+ type LumenPhoneCountryOptions,
9
+ type LumenPhoneNumber,
10
+ resolveLumenAstroProps,
11
+ resolveLumenPhoneNumber
12
+ } from '@santi020k/lumen-core'
5
13
 
6
14
  interface CountryOption {
7
15
  disabled?: boolean
@@ -11,13 +19,20 @@ interface CountryOption {
11
19
 
12
20
  type Country = CountryOption | string
13
21
 
22
+ interface NormalizedCountryOption extends CountryOption {
23
+ regionCode?: string
24
+ }
25
+
14
26
  interface Props extends Omit<HTMLAttributes<'div'>, 'size'> {
15
27
  class?: string | null
16
28
  className?: string
17
29
  countries?: Country[]
30
+ countryOptions?: readonly LumenPhoneCountry[]
18
31
  countryLabel?: string
19
32
  countryName?: string
20
33
  countryValue?: string
34
+ invalidNumberMessage?: string
35
+ locale?: LumenPhoneCountryOptions['locale']
21
36
  name?: string
22
37
  placeholder?: string
23
38
  size?: 'default' | 'lg' | 'sm'
@@ -28,9 +43,12 @@ const {
28
43
  class: classProp = '',
29
44
  className = '',
30
45
  countries = [],
46
+ countryOptions,
31
47
  countryLabel = 'Country code',
32
48
  countryName = 'country',
33
- countryValue,
49
+ countryValue = 'US',
50
+ invalidNumberMessage = 'Enter a complete phone number.',
51
+ locale,
34
52
  name = 'phone',
35
53
  placeholder = 'Phone number',
36
54
  size = 'default',
@@ -38,8 +56,45 @@ const {
38
56
  ...rest
39
57
  } = Astro.props
40
58
 
41
- const normalizedCountries = countries.map(country => typeof country === 'string' ? { label: country, value: country } : country)
42
- const hasCountryValue = countryValue !== undefined
59
+ const phoneOptions = locale === undefined ? {} : { locale }
60
+ const metadataCountries = countryOptions ?? getLumenPhoneCountries(phoneOptions)
61
+ const usesMetadata = countries.length === 0
62
+
63
+ const normalizedCountries: NormalizedCountryOption[] = usesMetadata ?
64
+ metadataCountries.map(country => ({
65
+ label: country.pickerLabel,
66
+ regionCode: country.regionCode,
67
+ value: country.regionCode
68
+ })) :
69
+ countries.map(country => typeof country === 'string' ? { label: country, value: country } : country)
70
+
71
+ const selectedCountry = metadataCountries.find(country => (
72
+ country.regionCode === countryValue || country.callingCode === countryValue
73
+ )) ?? metadataCountries[0] ?? getLumenPhoneCountry('US', phoneOptions)
74
+
75
+ const detectedPhoneValue = selectedCountry && value ?
76
+ resolveLumenPhoneNumber(selectedCountry, value, phoneOptions) :
77
+ null
78
+
79
+ const normalizePhoneValue = (detectedValue: LumenPhoneNumber | null): LumenPhoneNumber | null => {
80
+ if (!detectedValue || !selectedCountry) return detectedValue
81
+
82
+ const detectedCountryIsAllowed = metadataCountries.some(country => (
83
+ country.regionCode === detectedValue.country.regionCode
84
+ ))
85
+
86
+ if (detectedCountryIsAllowed) return detectedValue
87
+
88
+ const allowedCountryValue = detectedValue.nationalNumber.startsWith('+') ?
89
+ detectedValue.nationalNumber.slice(1) :
90
+ detectedValue.nationalNumber
91
+
92
+ return resolveLumenPhoneNumber(selectedCountry, allowedCountryValue, phoneOptions)
93
+ }
94
+
95
+ const phoneValue = normalizePhoneValue(detectedPhoneValue)
96
+ const selectedCountryValue = usesMetadata ? phoneValue?.country.regionCode ?? selectedCountry?.regionCode : countryValue
97
+ const renderedValue = usesMetadata ? phoneValue?.nationalNumber ?? value : value
43
98
 
44
99
  const { classList, passthrough } = resolveLumenAstroProps(rest, [
45
100
  'ui-phone-input',
@@ -47,7 +102,14 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
47
102
  ], classProp, className)
48
103
  ---
49
104
 
50
- <div class:list={classList} {...passthrough}>
105
+ <div
106
+ class:list={classList}
107
+ data-e164={phoneValue?.e164}
108
+ data-invalid-number-message={invalidNumberMessage}
109
+ data-ui-phone-input={usesMetadata ? '' : undefined}
110
+ data-valid={phoneValue ? String(phoneValue.isValid) : undefined}
111
+ {...passthrough}
112
+ >
51
113
  <select
52
114
  aria-label={countryLabel}
53
115
  class:list={[
@@ -61,7 +123,8 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
61
123
  {normalizedCountries.map(country => (
62
124
  <option
63
125
  disabled={country.disabled}
64
- selected={hasCountryValue && country.value === countryValue}
126
+ data-region={country.regionCode}
127
+ selected={country.value === selectedCountryValue}
65
128
  value={country.value}
66
129
  >{country.label}
67
130
  </option>
@@ -79,6 +142,6 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
79
142
  name={name}
80
143
  placeholder={placeholder}
81
144
  type="tel"
82
- value={value}
145
+ value={renderedValue}
83
146
  />
84
147
  </div>
@@ -3,6 +3,7 @@ import type { HTMLAttributes } from 'astro/types'
3
3
 
4
4
  import {
5
5
  createLumenPieGeometry,
6
+ formatLumenChartSummary,
6
7
  getLumenChartToneClassName,
7
8
  getLumenPieChartVariantClassName,
8
9
  hasLumenPieData,
@@ -24,6 +25,7 @@ interface Props extends HTMLAttributes<'figure'> {
24
25
  series: LumenChartSeries
25
26
  showLegend?: boolean
26
27
  showTable?: boolean
28
+ summary?: string
27
29
  valueFormatter?: (value: number) => string
28
30
  variant?: LumenPieChartVariant
29
31
  }
@@ -41,6 +43,7 @@ const {
41
43
  showLegend = true,
42
44
  showTable = true,
43
45
  valueFormatter = String,
46
+ summary: summaryProp,
44
47
  variant = 'donut',
45
48
  ...rest
46
49
  } = Astro.props
@@ -48,6 +51,13 @@ const {
48
51
  const geometry = createLumenPieGeometry(series.data, { variant })
49
52
  const hasData = hasLumenPieData(series.data)
50
53
 
54
+ const renderedSeries = {
55
+ ...series,
56
+ data: series.data.filter(datum => datum.y !== null && Number.isFinite(datum.y) && datum.y > 0)
57
+ }
58
+
59
+ const summary = summaryProp ?? formatLumenChartSummary([renderedSeries], valueFormatter)
60
+
51
61
  const percentageFor = (percentage: number) => new Intl.NumberFormat(undefined, {
52
62
  maximumFractionDigits: percentage < 0.01 ? 1 : 0,
53
63
  style: 'percent'
@@ -60,6 +70,7 @@ const percentageFor = (percentage: number) => new Intl.NumberFormat(undefined, {
60
70
  {description}
61
71
  {glass}
62
72
  {heading}
73
+ {summary}
63
74
  {...rest}
64
75
  >
65
76
  {
@@ -0,0 +1,58 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import { createLumenRangeGeometry, getLumenChartTicks, getLumenChartToneClassName, type LumenChartTone, type LumenRangeDatum, scaleLumenChartValue } from '@santi020k/lumen-core'
5
+
6
+ import Chart from './Chart.astro'
7
+
8
+ interface Props extends HTMLAttributes<'figure'> {
9
+ caption?: string
10
+ class?: string | null
11
+ className?: string
12
+ data: readonly LumenRangeDatum[]
13
+ description?: string
14
+ formatValue?: (value: number) => string
15
+ heading?: string
16
+ showTable?: boolean
17
+ summary?: string
18
+ tone?: LumenChartTone
19
+ }
20
+
21
+ const { caption, class: classProp = '', className = '', data, description, formatValue = String, heading, showTable = true, summary: summaryProp, tone = 'series-1', ...rest } = Astro.props
22
+ const width = 640
23
+ const height = 320
24
+ const padding = 44
25
+ const geometry = createLumenRangeGeometry(data, { height, padding, width })
26
+
27
+ const summary = summaryProp ?? (
28
+ geometry.points.length === 0 ?
29
+ 'No chart data available.' :
30
+ `${geometry.points.length} available ranges.`
31
+ )
32
+
33
+ const ticks = getLumenChartTicks(geometry.domain)
34
+ ---
35
+
36
+ <Chart class:list={['ui-range-chart', getLumenChartToneClassName(tone), classProp, className]} {caption} {description} {heading} {summary} {...rest}>
37
+ {geometry.points.length === 0 && <p class="ui-chart__empty" role="status">No chart data available.</p>}
38
+ <div class="ui-chart__plot" hidden={geometry.points.length === 0}><svg aria-hidden="true" viewBox={`0 0 ${width} ${height}`}><g class="
39
+ ui-chart__grid
40
+ "
41
+ >{ticks.map(tick => {
42
+ const y = scaleLumenChartValue(tick, geometry.domain, height - padding, padding)
43
+
44
+ return <><line x1={padding} x2={width - padding} y1={y} y2={y}/><text x={padding - 8} y={y}>{formatValue(tick)}</text></>
45
+ })}</g><path
46
+ class="ui-range-chart__area"
47
+ d={geometry.areaPath}
48
+ />{geometry.points.map(point => (
49
+ <line
50
+ class="ui-range-chart__interval"
51
+ x1={point.xCoordinate}
52
+ x2={point.xCoordinate}
53
+ y1={point.highCoordinate}
54
+ y2={point.lowCoordinate}
55
+ ><title>{`${point.xLabel ?? point.x}: ${point.label ?? `${formatValue(point.low ?? 0)}–${formatValue(point.high ?? 0)}`}`}</title></line>
56
+ ))}</svg></div>
57
+ {showTable && <details class="ui-chart__data"><summary>View chart data</summary><div><table><thead><tr><th scope="col">Category</th><th scope="col">Low</th><th scope="col">High</th></tr></thead><tbody>{data.map(item => <tr><th scope="row">{item.xLabel ?? item.x}</th><td>{item.low === null || !Number.isFinite(item.low) ? 'Not available' : formatValue(item.low)}</td><td>{item.high === null || !Number.isFinite(item.high) ? 'Not available' : formatValue(item.high)}</td></tr>)}</tbody></table></div></details>}
58
+ </Chart>
@@ -0,0 +1,78 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import {
5
+ createLumenScatterGeometry,
6
+ formatLumenChartSummary,
7
+ getLumenChartTicks,
8
+ getLumenChartToneClassName,
9
+ type LumenChartScaleType,
10
+ type LumenChartSeries,
11
+ scaleLumenChartValue } from '@santi020k/lumen-core'
12
+
13
+ import Chart from './Chart.astro'
14
+
15
+ interface Props extends HTMLAttributes<'figure'> {
16
+ caption?: string
17
+ class?: string | null
18
+ className?: string
19
+ description?: string
20
+ emptyLabel?: string
21
+ formatValue?: (value: number) => string
22
+ heading?: string
23
+ series: readonly LumenChartSeries[]
24
+ showLegend?: boolean
25
+ showTable?: boolean
26
+ summary?: string
27
+ xScale?: Exclude<LumenChartScaleType, 'categorical'>
28
+ }
29
+
30
+ const {
31
+ caption,
32
+ class: classProp = '',
33
+ className = '',
34
+ description,
35
+ emptyLabel = 'No chart data available.',
36
+ formatValue = String,
37
+ heading,
38
+ series,
39
+ showLegend = series.length > 1,
40
+ showTable = true,
41
+ summary: summaryProp,
42
+ xScale = 'linear',
43
+ ...rest
44
+ } = Astro.props
45
+
46
+ const geometry = createLumenScatterGeometry(series, { xScale })
47
+
48
+ const projectedSeries = series.map(item => ({
49
+ ...item,
50
+ data: geometry.points.filter(point => point.seriesId === item.id)
51
+ })).filter(item => item.data.length > 0)
52
+
53
+ const hasData = geometry.points.length > 0
54
+ const summary = summaryProp ?? formatLumenChartSummary(projectedSeries, formatValue)
55
+ const xTicks = getLumenChartTicks(geometry.xDomain)
56
+ const yTicks = getLumenChartTicks(geometry.domain)
57
+ ---
58
+
59
+ <Chart class:list={['ui-scatter-chart', classProp, className]} {caption} {description} {heading} {summary} {...rest}>
60
+ {showLegend && hasData && <ul class="ui-chart__legend" aria-label="Chart legend">{series.map((item, index) => <li class={getLumenChartToneClassName(item.tone, index)}><span aria-hidden="true" />{item.label}</li>)}</ul>}
61
+ {!hasData && <p class="ui-chart__empty" role="status">{emptyLabel}</p>}
62
+ <div class="ui-chart__plot" hidden={!hasData}>
63
+ <svg aria-hidden="true" viewBox={`0 0 ${geometry.width} ${geometry.height}`}>
64
+ <g class="ui-chart__grid">{yTicks.map(tick => {
65
+ const y = scaleLumenChartValue(tick, geometry.domain, geometry.height - 44, 44)
66
+
67
+ return <><line x1="44" x2={geometry.width - 44} y1={y} y2={y}/><text x="36" y={y}>{formatValue(tick)}</text></>
68
+ })}</g>
69
+ <g class="ui-chart__axis-labels">{xTicks.map(tick => {
70
+ const x = scaleLumenChartValue(tick, geometry.xDomain, 44, geometry.width - 44)
71
+
72
+ return <text text-anchor="middle" x={x} y={geometry.height - 14}>{formatValue(tick)}</text>
73
+ })}</g>
74
+ <g class="ui-scatter-chart__marks">{geometry.points.map(point => <circle class={getLumenChartToneClassName(point.tone)} cx={point.xCoordinate} cy={point.yCoordinate} r={point.radius}><title>{`${point.xLabel ?? point.x} · ${point.seriesLabel}: ${point.label ?? formatValue(point.y ?? 0)}`}</title></circle>)}</g>
75
+ </svg>
76
+ </div>
77
+ {showTable && hasData && <details class="ui-chart__data"><summary>View chart data</summary><div><table><thead><tr><th scope="col">X</th><th scope="col">Series</th><th scope="col">Value</th><th scope="col">Size</th></tr></thead><tbody>{geometry.points.map(point => <tr><th scope="row">{point.xLabel ?? point.x}</th><td>{point.seriesLabel}</td><td>{point.label ?? formatValue(point.y ?? 0)}</td><td>{point.size === undefined || point.size === null || !Number.isFinite(point.size) ? 'Not available' : formatValue(point.size)}</td></tr>)}</tbody></table></div></details>}
78
+ </Chart>
@@ -21,7 +21,8 @@ const {
21
21
  } = Astro.props
22
22
 
23
23
  const { classList, passthrough } = resolveLumenAstroProps(rest, [
24
- 'ui-sonner'
24
+ 'ui-sonner',
25
+ 'ui-tvp'
25
26
  ], classProp, className)
26
27
  ---
27
28
 
@@ -29,6 +30,7 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
29
30
  aria-atomic={ariaAtomic}
30
31
  aria-live={ariaLive}
31
32
  data-ui-sonner
33
+ data-ui-toast-viewport
32
34
  data-ui-toast-max={maxCount}
33
35
  data-placement={placement}
34
36
  class:list={classList}
@@ -0,0 +1,38 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
+ interface Props extends HTMLAttributes<'div'> {
7
+ class?: string | null
8
+ className?: string
9
+ maxCount?: number
10
+ placement?: 'bottom-center' | 'bottom-left' | 'bottom-right' | 'top-center' | 'top-left' | 'top-right'
11
+ }
12
+
13
+ const {
14
+ 'aria-atomic': ariaAtomic = 'false',
15
+ 'aria-live': ariaLive = 'polite',
16
+ class: classProp = '',
17
+ className = '',
18
+ maxCount,
19
+ placement = 'bottom-right',
20
+ ...rest
21
+ } = Astro.props
22
+
23
+ const { classList, passthrough } = resolveLumenAstroProps(rest, [
24
+ 'ui-tvp'
25
+ ], classProp, className)
26
+ ---
27
+
28
+ <div
29
+ aria-atomic={ariaAtomic}
30
+ aria-live={ariaLive}
31
+ data-ui-toast-viewport
32
+ data-ui-toast-max={maxCount}
33
+ data-placement={placement}
34
+ class:list={classList}
35
+ {...passthrough}
36
+ >
37
+ <slot />
38
+ </div>
package/index.ts CHANGED
@@ -38,6 +38,7 @@ export { default as CodeTabs } from './components/CodeTabs.astro'
38
38
  export { default as Collapsible } from './components/Collapsible.astro'
39
39
  export { default as ColorPicker } from './components/ColorPicker.astro'
40
40
  export { default as Combobox } from './components/Combobox.astro'
41
+ export { default as ComboChart } from './components/ComboChart.astro'
41
42
  export { default as Command } from './components/Command.astro'
42
43
  export { default as Container } from './components/Container.astro'
43
44
  export { default as ContextMenu } from './components/ContextMenu.astro'
@@ -57,6 +58,7 @@ export { default as DropdownMenuItem } from './components/DropdownMenuItem.astro
57
58
  export { default as DropdownMenuSeparator } from './components/DropdownMenuSeparator.astro'
58
59
  export { default as DropdownMenuTrigger } from './components/DropdownMenuTrigger.astro'
59
60
  export { default as Empty } from './components/Empty.astro'
61
+ export { default as ErrorState } from './components/ErrorState.astro'
60
62
  export { default as ErrorSummary } from './components/ErrorSummary.astro'
61
63
  export { default as Eyebrow } from './components/Eyebrow.astro'
62
64
  export { default as Field } from './components/Field.astro'
@@ -68,6 +70,7 @@ export { default as FormattedDate } from './components/FormattedDate.astro'
68
70
  export { default as GradientDivider } from './components/GradientDivider.astro'
69
71
  export { default as Graphic } from './components/Graphic.astro'
70
72
  export { default as Grid } from './components/Grid.astro'
73
+ export { default as Heatmap } from './components/Heatmap.astro'
71
74
  export { default as HoverCard } from './components/HoverCard.astro'
72
75
  export { default as Icon } from './components/Icon.astro'
73
76
  export { default as Illustration } from './components/Illustration.astro'
@@ -108,10 +111,12 @@ export { default as Progress } from './components/Progress.astro'
108
111
  export { default as Prose } from './components/Prose.astro'
109
112
  export { default as QRCode } from './components/QRCode.astro'
110
113
  export { default as RadioGroup } from './components/RadioGroup.astro'
114
+ export { default as RangeChart } from './components/RangeChart.astro'
111
115
  export { default as Rating } from './components/Rating.astro'
112
116
  export { default as Resizable } from './components/Resizable.astro'
113
117
  export { default as RevealGroup } from './components/RevealGroup.astro'
114
118
  export { default as RichTextEditor } from './components/RichTextEditor.astro'
119
+ export { default as ScatterChart } from './components/ScatterChart.astro'
115
120
  export { default as Schedule } from './components/Schedule.astro'
116
121
  export { default as ScrollArea } from './components/ScrollArea.astro'
117
122
  export { default as ScrollProgress } from './components/ScrollProgress.astro'
@@ -125,7 +130,6 @@ export { default as Sidebar } from './components/Sidebar.astro'
125
130
  export { default as Skeleton } from './components/Skeleton.astro'
126
131
  export { default as SkipLink } from './components/SkipLink.astro'
127
132
  export { default as Slider } from './components/Slider.astro'
128
- export { default as Sonner } from './components/Sonner.astro'
129
133
  export { default as Sparkline } from './components/Sparkline.astro'
130
134
  export { default as SpeedDial } from './components/SpeedDial.astro'
131
135
  export { default as Spinner } from './components/Spinner.astro'
@@ -151,6 +155,9 @@ export { default as TimeField } from './components/TimeField.astro'
151
155
  export { default as Timeline } from './components/Timeline.astro'
152
156
  export { default as TimelineItem } from './components/TimelineItem.astro'
153
157
  export { default as Toast } from './components/Toast.astro'
158
+ /** @deprecated Import ToastViewport instead. */
159
+ export { default as Sonner } from './components/Sonner.astro'
160
+ export { default as ToastViewport } from './components/ToastViewport.astro'
154
161
  export { default as Toggle } from './components/Toggle.astro'
155
162
  export { default as ToggleGroup } from './components/ToggleGroup.astro'
156
163
  export { default as Toolbar } from './components/Toolbar.astro'
@@ -170,4 +177,9 @@ export {
170
177
  type LumenControlIdMap,
171
178
  normalizeAstroActionErrors
172
179
  } from './forms.ts'
180
+ /** @deprecated Import the default export from @santi020k/lumen-astro/runtime instead. */
173
181
  export { default as UIPrimitives } from './runtime/UIPrimitives.astro'
182
+ export type {
183
+ LumenTabsChangeDetail,
184
+ LumenTabsChangeEvent
185
+ } from '@santi020k/lumen-core'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@santi020k/lumen-astro",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -8,6 +8,7 @@
8
8
  "type": "module",
9
9
  "sideEffects": [
10
10
  "./styles/lumen.css",
11
+ "./styles/critical.css",
11
12
  "./styles.css"
12
13
  ],
13
14
  "types": "./index.ts",
@@ -24,9 +25,11 @@
24
25
  "./runtime": "./runtime/UIPrimitives.astro",
25
26
  "./layers.css": "./styles/layers.css",
26
27
  "./styles.css": "./styles/lumen.css",
28
+ "./styles/critical.css": "./styles/critical.css",
27
29
  "./package.json": "./package.json"
28
30
  },
29
31
  "files": [
32
+ "bin",
30
33
  "components",
31
34
  "runtime",
32
35
  "styles",
@@ -35,10 +38,13 @@
35
38
  "README.md",
36
39
  "LICENSE"
37
40
  ],
41
+ "bin": {
42
+ "lumen": "./bin/lumen.mjs"
43
+ },
38
44
  "dependencies": {
39
45
  "uqr": "^0.1.3",
40
- "@santi020k/lumen-core": "^1.4.0",
41
- "@santi020k/lumen": "^1.6.0"
46
+ "@santi020k/lumen": "^1.7.0",
47
+ "@santi020k/lumen-core": "^1.7.0"
42
48
  },
43
49
  "devDependencies": {
44
50
  "@astrojs/ts-plugin": "^1.10.11",