@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.
- package/README.md +53 -4
- package/bin/lumen.mjs +2 -0
- package/components/BarChart.astro +5 -1
- package/components/Chart.astro +3 -0
- package/components/ComboChart.astro +118 -0
- package/components/ErrorState.astro +118 -0
- package/components/Heatmap.astro +32 -0
- package/components/Illustration.astro +8 -30
- package/components/Image.astro +8 -1
- package/components/LineChart.astro +5 -1
- package/components/PhoneInput.astro +70 -7
- package/components/PieChart.astro +11 -0
- package/components/RangeChart.astro +58 -0
- package/components/ScatterChart.astro +78 -0
- package/components/Sonner.astro +3 -1
- package/components/ToastViewport.astro +38 -0
- package/index.ts +13 -1
- package/package.json +9 -3
- package/runtime/UIPrimitives.astro +44 -229
- package/runtime/controllers/dialogs.ts +106 -0
- package/runtime/controllers/document-navigation.ts +128 -0
- package/runtime/controllers/phone-input.ts +82 -0
- package/styles/critical.css +2054 -0
package/README.md
CHANGED
|
@@ -9,6 +9,13 @@ configuration is required to render the components.
|
|
|
9
9
|
pnpm add @santi020k/lumen-astro
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
+
The adapter also exposes Lumen's discovery and diagnostics CLI:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm exec lumen show Tabs
|
|
16
|
+
pnpm exec lumen doctor
|
|
17
|
+
```
|
|
18
|
+
|
|
12
19
|
Import the CSS once in your root layout and mount `UIPrimitives` once in that same app shell if you
|
|
13
20
|
use interactive components such as dialogs, menus, popovers, tabs, carousels, toggles, command
|
|
14
21
|
lists, or toasts. Do not add `UIPrimitives` beside each component instance; one root include
|
|
@@ -18,6 +25,11 @@ enhances all matching Lumen markup on the page.
|
|
|
18
25
|
@import "@santi020k/lumen-astro/styles.css";
|
|
19
26
|
```
|
|
20
27
|
|
|
28
|
+
Applications using the essential forms, feedback, tabs, dialogs, calendar, and data-table surface
|
|
29
|
+
can instead load `@santi020k/lumen-astro/styles/critical.css`. The generated entry is about 50 KiB
|
|
30
|
+
raw versus 170 KiB for the complete catalog; switch to `styles.css` when using components outside
|
|
31
|
+
that documented critical set.
|
|
32
|
+
|
|
21
33
|
The stylesheet defaults `--ui-font` to `"Montserrat", "Avenir Next", "Segoe UI", sans-serif`.
|
|
22
34
|
It declares the family stack but does not bundle or load font files. Load Montserrat once through
|
|
23
35
|
your preferred delivery path, or override `--ui-font` in application CSS.
|
|
@@ -32,7 +44,7 @@ With Tailwind, keep the Lumen import in the same shared CSS entry:
|
|
|
32
44
|
|
|
33
45
|
```astro
|
|
34
46
|
---
|
|
35
|
-
import
|
|
47
|
+
import UIPrimitives from '@santi020k/lumen-astro/runtime'
|
|
36
48
|
---
|
|
37
49
|
|
|
38
50
|
<UIPrimitives />
|
|
@@ -52,20 +64,35 @@ import { Button, Card, Input } from '@santi020k/lumen-astro'
|
|
|
52
64
|
```
|
|
53
65
|
|
|
54
66
|
`Input` and `NativeSelect` preserve the native numeric `size` attribute. Use `visualSize="sm"` or
|
|
55
|
-
`visualSize="lg"` for presentation
|
|
56
|
-
incremental migrations, but new code should keep native behavior and visual styling separate.
|
|
67
|
+
`visualSize="lg"` for presentation so native behavior and visual styling remain separate.
|
|
57
68
|
|
|
58
69
|
```astro
|
|
59
70
|
<Input size={32} visualSize="sm" />
|
|
60
71
|
<NativeSelect size={8} visualSize="lg" />
|
|
61
72
|
```
|
|
62
73
|
|
|
74
|
+
`PhoneInput` generates localized country names, supplementary flags, and calling codes when custom
|
|
75
|
+
`countries` are not provided. Mount `UIPrimitives` once to enable as-you-type formatting,
|
|
76
|
+
metadata-backed validation, automatic country detection for pasted international numbers, and the
|
|
77
|
+
`ui:phone-change` event with a `LumenPhoneNumber` detail.
|
|
78
|
+
|
|
79
|
+
The phone metadata and normalization controller are selector-loaded only when a rendered page
|
|
80
|
+
contains `PhoneInput`; pages without the component do not evaluate or download that controller.
|
|
81
|
+
|
|
82
|
+
```astro
|
|
83
|
+
<PhoneInput countryValue="CO" locale="en-US" name="hospitalPhone" />
|
|
84
|
+
```
|
|
85
|
+
|
|
63
86
|
## Compound interactive components
|
|
64
87
|
|
|
65
88
|
Popover, dropdown menu, tabs, and tooltip parts expose the DOM and ARIA contracts consumed by the
|
|
66
89
|
shared progressive-enhancement runtime. Prefer these parts over recreating roles and `data-ui-*`
|
|
67
90
|
attributes by hand.
|
|
68
91
|
|
|
92
|
+
Tabs keep the selected trigger visible when a narrow tab list scrolls horizontally and emit a
|
|
93
|
+
typed `ui:tabs-change` event. Import `LumenTabsChangeDetail` or `LumenTabsChangeEvent` from this
|
|
94
|
+
package when application behavior follows the selected value.
|
|
95
|
+
|
|
69
96
|
```astro
|
|
70
97
|
---
|
|
71
98
|
import {
|
|
@@ -171,6 +198,22 @@ const errors = result?.error && isInputError(result.error)
|
|
|
171
198
|
|
|
172
199
|
See the [Astro Actions form guide](https://lumen.santi020k.com/docs/forms/astro-actions).
|
|
173
200
|
|
|
201
|
+
## Error states
|
|
202
|
+
|
|
203
|
+
Use `ErrorState` when a page or region cannot show its primary content. Pass recovery controls
|
|
204
|
+
through the `actions` slot; the application still owns retry and logging behavior. Static states are
|
|
205
|
+
not live regions by default, so set `announce="polite"` only when a newly rendered failure needs an
|
|
206
|
+
announcement.
|
|
207
|
+
|
|
208
|
+
```astro
|
|
209
|
+
<ErrorState title="Could not load projects" description="Check your connection and try again.">
|
|
210
|
+
<Button slot="actions">Try again</Button>
|
|
211
|
+
</ErrorState>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
See the repository [Astro error-handling guide](../../docs/error-handling.md#astro) for the complete
|
|
215
|
+
example, retry ownership, announcement behavior, and verification guidance.
|
|
216
|
+
|
|
174
217
|
Use `Icon` for Lucide icons by name across framework adapters.
|
|
175
218
|
|
|
176
219
|
```astro
|
|
@@ -216,7 +259,9 @@ non-code content `Code` semantics.
|
|
|
216
259
|
## Data visualization
|
|
217
260
|
|
|
218
261
|
Use `Sparkline` beside a metric, `BarChart` for categorical comparison, `LineChart` for ordered
|
|
219
|
-
trends,
|
|
262
|
+
trends, `PieChart` for a small part-to-whole breakdown, `ScatterChart` for numeric relationships,
|
|
263
|
+
`Heatmap` for a matrix, `RangeChart` for intervals, and `ComboChart` for a shared-domain mix of bars,
|
|
264
|
+
lines, and areas. The chart components accept
|
|
220
265
|
already-aggregated series, use Lumen data-color tokens, and include a revealable semantic data
|
|
221
266
|
table by default.
|
|
222
267
|
|
|
@@ -237,6 +282,10 @@ const series = [{
|
|
|
237
282
|
`PieChart` accepts one `LumenChartSeries`; its data points become slices. It defaults to
|
|
238
283
|
`variant="donut"` and also supports `variant="pie"`.
|
|
239
284
|
|
|
285
|
+
Every chart includes a factual screen-reader summary by default and accepts `summary` for more
|
|
286
|
+
useful domain context. See [data visualization](../../docs/data-visualization.md) for selection,
|
|
287
|
+
missing-data, live-data, and accessibility guidance.
|
|
288
|
+
|
|
240
289
|
Lucide intentionally excludes brand marks. For GitHub and other project-specific icons, put the
|
|
241
290
|
SVG in the default slot instead of passing an unsupported `name`; Astro warns about unknown named
|
|
242
291
|
icons during development.
|
package/bin/lumen.mjs
ADDED
|
@@ -3,6 +3,7 @@ import type { HTMLAttributes } from 'astro/types'
|
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
createLumenBarGeometry,
|
|
6
|
+
formatLumenChartSummary,
|
|
6
7
|
getLumenChartTicks,
|
|
7
8
|
getLumenChartToneClassName,
|
|
8
9
|
hasLumenChartData,
|
|
@@ -31,6 +32,7 @@ interface Props extends HTMLAttributes<'figure'> {
|
|
|
31
32
|
series: readonly LumenChartSeries[]
|
|
32
33
|
showLegend?: boolean
|
|
33
34
|
showTable?: boolean
|
|
35
|
+
summary?: string
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
const {
|
|
@@ -49,6 +51,7 @@ const {
|
|
|
49
51
|
series,
|
|
50
52
|
showLegend = series.length > 1,
|
|
51
53
|
showTable = true,
|
|
54
|
+
summary = formatLumenChartSummary(series, formatValue),
|
|
52
55
|
...rest
|
|
53
56
|
} = Astro.props
|
|
54
57
|
|
|
@@ -72,7 +75,7 @@ const labelFor = (item: LumenChartSeries, category: number | string) => (
|
|
|
72
75
|
const formattedValueFor = (item: LumenChartSeries, category: number | string) => {
|
|
73
76
|
const value = valueFor(item, category)
|
|
74
77
|
|
|
75
|
-
return value === null ? 'Not available' : formatValue(value)
|
|
78
|
+
return value === null || !Number.isFinite(value) ? 'Not available' : formatValue(value)
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
const categoryLabelFor = (category: number | string) => {
|
|
@@ -92,6 +95,7 @@ const margin = orientation === 'horizontal' ?
|
|
|
92
95
|
{description}
|
|
93
96
|
{glass}
|
|
94
97
|
{heading}
|
|
98
|
+
{summary}
|
|
95
99
|
{...rest}
|
|
96
100
|
>
|
|
97
101
|
{
|
package/components/Chart.astro
CHANGED
|
@@ -11,6 +11,7 @@ interface Props extends HTMLAttributes<'figure'> {
|
|
|
11
11
|
glass?: boolean | 'subtle' | 'strong'
|
|
12
12
|
heading?: string
|
|
13
13
|
presentation?: 'bare' | 'default'
|
|
14
|
+
summary?: string
|
|
14
15
|
value?: string
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -22,6 +23,7 @@ const {
|
|
|
22
23
|
glass = false,
|
|
23
24
|
heading,
|
|
24
25
|
presentation = 'default',
|
|
26
|
+
summary,
|
|
25
27
|
value,
|
|
26
28
|
...rest
|
|
27
29
|
} = Astro.props
|
|
@@ -53,6 +55,7 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
|
53
55
|
</header>
|
|
54
56
|
)
|
|
55
57
|
}
|
|
58
|
+
{summary && <p class="ui-sr-only" data-ui-chart-summary>{summary}</p>}
|
|
56
59
|
<slot />
|
|
57
60
|
{caption && <figcaption>{caption}</figcaption>}
|
|
58
61
|
</figure>
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
alignLumenChartSeries,
|
|
6
|
+
createLumenBarGeometry,
|
|
7
|
+
createLumenLineGeometry,
|
|
8
|
+
formatLumenChartSummary,
|
|
9
|
+
getLumenChartCategories,
|
|
10
|
+
getLumenChartDomain,
|
|
11
|
+
getLumenChartToneClassName,
|
|
12
|
+
hasLumenChartData,
|
|
13
|
+
type LumenChartDatum,
|
|
14
|
+
type LumenComboSeries,
|
|
15
|
+
resolveLumenChartTone } from '@santi020k/lumen-core'
|
|
16
|
+
|
|
17
|
+
import Chart from './Chart.astro'
|
|
18
|
+
|
|
19
|
+
interface Props extends HTMLAttributes<'figure'> {
|
|
20
|
+
caption?: string
|
|
21
|
+
class?: string | null
|
|
22
|
+
className?: string
|
|
23
|
+
description?: string
|
|
24
|
+
formatValue?: (value: number) => string
|
|
25
|
+
heading?: string
|
|
26
|
+
series: readonly LumenComboSeries[]
|
|
27
|
+
showLegend?: boolean
|
|
28
|
+
showTable?: boolean
|
|
29
|
+
summary?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const { caption, class: classProp = '', className = '', description, formatValue = String, heading, series, showLegend = true, showTable = true, summary = formatLumenChartSummary(series, formatValue), ...rest } = Astro.props
|
|
33
|
+
const width = 640
|
|
34
|
+
const height = 320
|
|
35
|
+
const padding = 44
|
|
36
|
+
const categories = getLumenChartCategories(series)
|
|
37
|
+
|
|
38
|
+
const alignedSeries = series.map(item => ({
|
|
39
|
+
...item,
|
|
40
|
+
data: alignLumenChartSeries(item, categories).data
|
|
41
|
+
}))
|
|
42
|
+
|
|
43
|
+
const domain = getLumenChartDomain(alignedSeries.flatMap(item => item.data.map(datum => datum.y)))
|
|
44
|
+
const barSeries = alignedSeries.filter(item => item.mark === 'bar')
|
|
45
|
+
const lineSeries = alignedSeries.filter(item => item.mark !== 'bar')
|
|
46
|
+
const bars = createLumenBarGeometry(barSeries, { domain, height, width })
|
|
47
|
+
|
|
48
|
+
const categoryPositions = new Map(
|
|
49
|
+
bars.categories.map(category => [`${typeof category.category}:${String(category.category)}`, category.x])
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
const drawableWidth = width - padding * 2
|
|
53
|
+
|
|
54
|
+
const alignComboLineDatum = (datum: LumenChartDatum): LumenChartDatum => {
|
|
55
|
+
if (barSeries.length === 0) return datum
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
...datum,
|
|
59
|
+
x: ((categoryPositions.get(`${typeof datum.x}:${String(datum.x)}`) ?? padding) - padding) / drawableWidth
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const lineGeometryOptions = {
|
|
64
|
+
domain,
|
|
65
|
+
height,
|
|
66
|
+
padding,
|
|
67
|
+
width,
|
|
68
|
+
...(barSeries.length === 0 ?
|
|
69
|
+
{} :
|
|
70
|
+
{
|
|
71
|
+
xDomain: { max: 1, min: 0 },
|
|
72
|
+
xScale: 'linear' as const
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const lines = lineSeries.map(item => createLumenLineGeometry(
|
|
77
|
+
item.data.map(alignComboLineDatum),
|
|
78
|
+
lineGeometryOptions
|
|
79
|
+
))
|
|
80
|
+
|
|
81
|
+
const hasData = hasLumenChartData(series)
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
<Chart class:list={['ui-combo-chart', classProp, className]} {caption} {description} {heading} {summary} {...rest}>
|
|
85
|
+
{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>}
|
|
86
|
+
{!hasData && <p class="ui-chart__empty" role="status">No chart data available.</p>}
|
|
87
|
+
<div class="ui-chart__plot" hidden={!hasData}><svg aria-hidden="true" viewBox={`0 0 ${width} ${height}`}><g class="
|
|
88
|
+
ui-bar-chart__marks
|
|
89
|
+
"
|
|
90
|
+
>{bars.marks.map(mark => <rect class={getLumenChartToneClassName(mark.tone)} height={mark.height} rx="4" width={mark.width} x={mark.x} y={mark.y}><title>{`${mark.seriesLabel}: ${formatValue(mark.value)}`}</title></rect>)}</g>{lines.map((geometry, index) => {
|
|
91
|
+
const item = lineSeries[index]
|
|
92
|
+
|
|
93
|
+
if (!item) return null
|
|
94
|
+
|
|
95
|
+
const tone = resolveLumenChartTone(item.tone, index + barSeries.length)
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<g class:list={['ui-line-chart__series', getLumenChartToneClassName(tone)]}>{item.mark === 'area' && geometry.areaPaths.map(path => (
|
|
99
|
+
<path
|
|
100
|
+
class="ui-line-chart__area"
|
|
101
|
+
d={path}
|
|
102
|
+
/>
|
|
103
|
+
)) }<path
|
|
104
|
+
class="ui-line-chart__line"
|
|
105
|
+
d={geometry.path}
|
|
106
|
+
/></g>
|
|
107
|
+
)
|
|
108
|
+
})}</svg></div>
|
|
109
|
+
{showTable && hasData && (
|
|
110
|
+
<details class="ui-chart__data"><summary>View chart data</summary><div><table><thead><tr><th scope="col">Category</th>{series.map(item => <th scope="col">{item.label}</th>)}</tr></thead><tbody>{categories.map(category => (
|
|
111
|
+
<tr><th scope="row">{String(category)}</th>{series.map(item => {
|
|
112
|
+
const datum = item.data.find(point => point.x === category)
|
|
113
|
+
|
|
114
|
+
return <td>{datum?.label ?? (datum?.y === undefined || datum.y === null || !Number.isFinite(datum.y) ? 'Not available' : formatValue(datum.y))}</td>
|
|
115
|
+
})}</tr>
|
|
116
|
+
))}</tbody></table></div></details>
|
|
117
|
+
)}
|
|
118
|
+
</Chart>
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
type LumenErrorStateAnnouncement,
|
|
6
|
+
type LumenErrorStateKind,
|
|
7
|
+
type LumenErrorStateLayout,
|
|
8
|
+
resolveLumenAstroProps
|
|
9
|
+
} from '@santi020k/lumen-core'
|
|
10
|
+
|
|
11
|
+
import Illustration from './Illustration.astro'
|
|
12
|
+
|
|
13
|
+
type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6
|
|
14
|
+
|
|
15
|
+
const headingTags = {
|
|
16
|
+
1: 'h1',
|
|
17
|
+
2: 'h2',
|
|
18
|
+
3: 'h3',
|
|
19
|
+
4: 'h4',
|
|
20
|
+
5: 'h5',
|
|
21
|
+
6: 'h6'
|
|
22
|
+
} as const
|
|
23
|
+
|
|
24
|
+
const resolveAnnouncementRole = (
|
|
25
|
+
announce: LumenErrorStateAnnouncement,
|
|
26
|
+
role: Props['role']
|
|
27
|
+
): Props['role'] => {
|
|
28
|
+
if (role) return role
|
|
29
|
+
|
|
30
|
+
if (announce === 'assertive') return 'alert'
|
|
31
|
+
|
|
32
|
+
if (announce === 'polite') return 'status'
|
|
33
|
+
|
|
34
|
+
return undefined
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface Props extends HTMLAttributes<'section'> {
|
|
38
|
+
announce?: LumenErrorStateAnnouncement
|
|
39
|
+
class?: string | null
|
|
40
|
+
className?: string
|
|
41
|
+
description?: string
|
|
42
|
+
headingLevel?: HeadingLevel
|
|
43
|
+
kind?: LumenErrorStateKind
|
|
44
|
+
layout?: LumenErrorStateLayout
|
|
45
|
+
reference?: string
|
|
46
|
+
referenceLabel?: string
|
|
47
|
+
showGraphic?: boolean
|
|
48
|
+
title: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const {
|
|
52
|
+
'aria-live': ariaLive,
|
|
53
|
+
announce = 'off',
|
|
54
|
+
class: classProp = '',
|
|
55
|
+
className = '',
|
|
56
|
+
description,
|
|
57
|
+
headingLevel = 2,
|
|
58
|
+
id,
|
|
59
|
+
kind = 'error',
|
|
60
|
+
layout = 'default',
|
|
61
|
+
reference,
|
|
62
|
+
referenceLabel = 'Reference',
|
|
63
|
+
role,
|
|
64
|
+
showGraphic = true,
|
|
65
|
+
title,
|
|
66
|
+
...rest
|
|
67
|
+
} = Astro.props
|
|
68
|
+
|
|
69
|
+
const Heading = headingTags[headingLevel]
|
|
70
|
+
const titleId = id ? `${id}-title` : undefined
|
|
71
|
+
const resolvedLive = ariaLive ?? (announce === 'off' ? undefined : announce)
|
|
72
|
+
const resolvedRole = resolveAnnouncementRole(announce, role)
|
|
73
|
+
|
|
74
|
+
const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
75
|
+
'ui-error-state',
|
|
76
|
+
`ui-error-state--${kind}`,
|
|
77
|
+
`ui-error-state--${layout}`
|
|
78
|
+
], classProp, className)
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
<section
|
|
82
|
+
aria-label={titleId ? undefined : title}
|
|
83
|
+
aria-labelledby={titleId}
|
|
84
|
+
aria-live={resolvedLive}
|
|
85
|
+
class:list={classList}
|
|
86
|
+
data-kind={kind}
|
|
87
|
+
data-layout={layout}
|
|
88
|
+
data-ui-error-state
|
|
89
|
+
id={id}
|
|
90
|
+
role={resolvedRole}
|
|
91
|
+
{...passthrough}
|
|
92
|
+
>
|
|
93
|
+
{showGraphic && (
|
|
94
|
+
<div class="ui-error-state__graphic" data-slot="error-state-graphic" aria-hidden="true">
|
|
95
|
+
{Astro.slots.has('graphic') ?
|
|
96
|
+
(<slot name="graphic" />) :
|
|
97
|
+
(
|
|
98
|
+
<Illustration
|
|
99
|
+
size={layout === 'compact' ? 'sm' : 'md'}
|
|
100
|
+
variant={kind}
|
|
101
|
+
/>
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
)}
|
|
105
|
+
<div class="ui-error-state__content" data-slot="error-state-content">
|
|
106
|
+
<Heading class="ui-error-state__title" data-slot="error-state-title" id={titleId}>{title}</Heading>
|
|
107
|
+
{description && <p class="ui-error-state__description" data-slot="error-state-description">{description}</p>}
|
|
108
|
+
{Astro.slots.has('default') && <div class="ui-error-state__details" data-slot="error-state-details"><slot /></div>}
|
|
109
|
+
{reference && (
|
|
110
|
+
<p class="ui-error-state__reference" data-slot="error-state-reference">
|
|
111
|
+
<span>{referenceLabel}:</span> <code>{reference}</code>
|
|
112
|
+
</p>
|
|
113
|
+
)}
|
|
114
|
+
</div>
|
|
115
|
+
{Astro.slots.has('actions') && (
|
|
116
|
+
<div class="ui-error-state__actions" data-slot="error-state-actions"><slot name="actions" /></div>
|
|
117
|
+
)}
|
|
118
|
+
</section>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
import { createLumenHeatmapGeometry, type LumenHeatmapDatum } 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 LumenHeatmapDatum[]
|
|
13
|
+
description?: string
|
|
14
|
+
formatValue?: (value: number) => string
|
|
15
|
+
heading?: string
|
|
16
|
+
showTable?: boolean
|
|
17
|
+
summary?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { caption, class: classProp = '', className = '', data, description, formatValue = String, heading, showTable = true, summary = `${data.filter(item => item.value !== null && Number.isFinite(item.value)).length} available heatmap cells.`, ...rest } = Astro.props
|
|
21
|
+
const geometry = createLumenHeatmapGeometry(data)
|
|
22
|
+
const hasData = geometry.cells.some(cell => cell.value !== null && Number.isFinite(cell.value))
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
<Chart class:list={['ui-heatmap', classProp, className]} {caption} {description} {heading} {summary} {...rest}>
|
|
26
|
+
{!hasData && <p class="ui-chart__empty" role="status">No chart data available.</p>}
|
|
27
|
+
<div class="ui-chart__plot" hidden={!hasData}><svg aria-hidden="true" viewBox={`0 0 ${geometry.width} ${geometry.height}`}><g class="
|
|
28
|
+
ui-heatmap__cells
|
|
29
|
+
"
|
|
30
|
+
>{geometry.cells.filter(cell => cell.value !== null && Number.isFinite(cell.value)).map(cell => <rect height={Math.max(0, cell.height - 2)} opacity={Math.max(0.12, cell.ratio)} width={Math.max(0, cell.width - 2)} x={cell.xCoordinate + 1} y={cell.yCoordinate + 1}><title>{`${cell.xLabel ?? cell.x} · ${cell.yLabel ?? cell.y}: ${cell.label ?? formatValue(cell.value ?? 0)}`}</title></rect>)}</g></svg></div>
|
|
31
|
+
{showTable && <details class="ui-chart__data"><summary>View chart data</summary><div><table><thead><tr><th scope="col">Column</th><th scope="col">Row</th><th scope="col">Value</th></tr></thead><tbody>{data.map(cell => <tr><th scope="row">{cell.xLabel ?? cell.x}</th><td>{cell.yLabel ?? cell.y}</td><td>{cell.label ?? (cell.value === null || !Number.isFinite(cell.value) ? 'Not available' : formatValue(cell.value))}</td></tr>)}</tbody></table></div></details>}
|
|
32
|
+
</Chart>
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { HTMLAttributes } from 'astro/types'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
renderLumenIllustrationSvg,
|
|
6
|
+
resolveLumenAstroProps
|
|
7
|
+
} from '@santi020k/lumen-core'
|
|
5
8
|
|
|
6
9
|
interface Props extends HTMLAttributes<'span'> {
|
|
7
10
|
class?: string | null
|
|
@@ -28,6 +31,8 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
|
28
31
|
`ui-illustration--${tone}`,
|
|
29
32
|
`ui-illustration--${size}`
|
|
30
33
|
], classProp, className)
|
|
34
|
+
|
|
35
|
+
const artwork = renderLumenIllustrationSvg(variant)
|
|
31
36
|
---
|
|
32
37
|
|
|
33
38
|
<span
|
|
@@ -36,32 +41,5 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
|
36
41
|
class:list={classList}
|
|
37
42
|
role={label ? 'img' : undefined}
|
|
38
43
|
{...passthrough}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
<circle class="ui-illustration__wash" cx="60" cy="60" r="48" />
|
|
42
|
-
{variant === 'empty' && (
|
|
43
|
-
<g>
|
|
44
|
-
<path d="M32 48h56l-7 35H39l-7-35Z" />
|
|
45
|
-
<path d="M43 48 49 36h22l6 12M47 66h26" />
|
|
46
|
-
</g>
|
|
47
|
-
)}
|
|
48
|
-
{variant === 'success' && (
|
|
49
|
-
<g>
|
|
50
|
-
<circle cx="60" cy="60" r="28" />
|
|
51
|
-
<path d="m45 60 10 10 21-23" />
|
|
52
|
-
</g>
|
|
53
|
-
)}
|
|
54
|
-
{variant === 'error' && (
|
|
55
|
-
<g>
|
|
56
|
-
<circle cx="60" cy="60" r="28" />
|
|
57
|
-
<path d="m49 49 22 22m0-22L49 71" />
|
|
58
|
-
</g>
|
|
59
|
-
)}
|
|
60
|
-
{variant === 'offline' && (
|
|
61
|
-
<g>
|
|
62
|
-
<path d="M37 74h44a14 14 0 0 0 1-28 23 23 0 0 0-42-4 16 16 0 0 0-3 32Z" />
|
|
63
|
-
<path d="m38 34 44 52" />
|
|
64
|
-
</g>
|
|
65
|
-
)}
|
|
66
|
-
</svg>
|
|
67
|
-
</span>
|
|
44
|
+
set:html={artwork}
|
|
45
|
+
/>
|
package/components/Image.astro
CHANGED
|
@@ -10,25 +10,32 @@ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
|
|
|
10
10
|
type Props = (LocalImageProps | RemoteImageProps) & {
|
|
11
11
|
class?: string | null
|
|
12
12
|
className?: string
|
|
13
|
+
fit?: 'contain' | 'cover'
|
|
13
14
|
invertOnDark?: boolean
|
|
15
|
+
radius?: 'full' | 'lg' | 'md' | 'none' | 'sm'
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
const {
|
|
17
19
|
class: classProp = '',
|
|
18
20
|
className = '',
|
|
21
|
+
fit = 'cover',
|
|
19
22
|
invertOnDark = false,
|
|
20
23
|
loading = 'lazy',
|
|
24
|
+
radius = 'lg',
|
|
21
25
|
...rest
|
|
22
26
|
} = Astro.props
|
|
23
27
|
|
|
24
28
|
const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
25
29
|
'ui-image',
|
|
26
|
-
invertOnDark && 'ui-image--invert-dark'
|
|
30
|
+
invertOnDark && 'ui-image--invert-dark',
|
|
31
|
+
`ui-image--fit-${fit}`,
|
|
32
|
+
`ui-image--radius-${radius}`
|
|
27
33
|
], classProp, className)
|
|
28
34
|
---
|
|
29
35
|
|
|
30
36
|
<AstroImage
|
|
31
37
|
loading={loading}
|
|
32
38
|
class:list={classList}
|
|
39
|
+
fit={fit}
|
|
33
40
|
{...passthrough}
|
|
34
41
|
/>
|
|
@@ -4,6 +4,7 @@ import type { HTMLAttributes } from 'astro/types'
|
|
|
4
4
|
import {
|
|
5
5
|
alignLumenChartSeries,
|
|
6
6
|
createLumenLineGeometry,
|
|
7
|
+
formatLumenChartSummary,
|
|
7
8
|
getLumenChartCategories,
|
|
8
9
|
getLumenChartDomain,
|
|
9
10
|
getLumenChartTicks,
|
|
@@ -33,6 +34,7 @@ interface Props extends HTMLAttributes<'figure'> {
|
|
|
33
34
|
series: readonly LumenChartSeries[]
|
|
34
35
|
showLegend?: boolean
|
|
35
36
|
showTable?: boolean
|
|
37
|
+
summary?: string
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
const {
|
|
@@ -51,6 +53,7 @@ const {
|
|
|
51
53
|
series,
|
|
52
54
|
showLegend = series.length > 1,
|
|
53
55
|
showTable = true,
|
|
56
|
+
summary = formatLumenChartSummary(series, formatValue),
|
|
54
57
|
...rest
|
|
55
58
|
} = Astro.props
|
|
56
59
|
|
|
@@ -99,6 +102,7 @@ const referenceY =
|
|
|
99
102
|
{description}
|
|
100
103
|
{glass}
|
|
101
104
|
{heading}
|
|
105
|
+
{summary}
|
|
102
106
|
{...rest}
|
|
103
107
|
>
|
|
104
108
|
{
|
|
@@ -227,7 +231,7 @@ const referenceY =
|
|
|
227
231
|
return (
|
|
228
232
|
<td>
|
|
229
233
|
{datum?.label ??
|
|
230
|
-
(datum?.y ===
|
|
234
|
+
(datum?.y === undefined || datum.y === null || !Number.isFinite(datum.y) ?
|
|
231
235
|
'Not available' :
|
|
232
236
|
formatValue(datum.y))}
|
|
233
237
|
</td>
|