@santi020k/lumen-astro 3.0.1 → 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.
- package/README.md +403 -7
- package/components/AmountField.astro +21 -0
- package/components/AnimatedNumber.astro +1 -1
- package/components/ApprovalCard.astro +30 -0
- package/components/AttachmentList.astro +15 -0
- package/components/AttachmentPreview.astro +51 -0
- package/components/Avatar.astro +2 -0
- package/components/BarChart.astro +95 -31
- package/components/BoxPlot.astro +77 -0
- package/components/Breadcrumb.astro +2 -1
- package/components/BulletChart.astro +64 -0
- package/components/Button.astro +1 -1
- package/components/Calendar.astro +54 -65
- package/components/CalendarHeatmap.astro +76 -0
- package/components/Card.astro +4 -0
- package/components/ChangeSummary.astro +36 -0
- package/components/ChartDatumActions.astro +24 -0
- package/components/ChartMotion.astro +11 -0
- package/components/Code.astro +18 -3
- package/components/CodeTabs.astro +12 -0
- package/components/ComboChart.astro +93 -11
- package/components/CountryFlag.astro +23 -0
- package/components/DataTable.astro +15 -7
- package/components/DatePicker.astro +24 -17
- package/components/DescriptionDetail.astro +18 -0
- package/components/DescriptionItem.astro +18 -0
- package/components/DescriptionTerm.astro +18 -0
- package/components/DeviceFrame.astro +79 -0
- package/components/DialogBody.astro +20 -0
- package/components/DialogClose.astro +21 -0
- package/components/DialogFooter.astro +20 -0
- package/components/DialogHeader.astro +20 -0
- package/components/DialogTitle.astro +21 -0
- package/components/DumbbellChart.astro +8 -0
- package/components/FileUpload.astro +3 -1
- package/components/FilterBar.astro +25 -0
- package/components/FunnelChart.astro +50 -0
- package/components/Grid.astro +2 -2
- package/components/Heatmap.astro +61 -12
- package/components/Histogram.astro +30 -0
- package/components/Icon.astro +7 -3
- package/components/ImageComparison.astro +74 -0
- package/components/Input.astro +2 -2
- package/components/LineChart.astro +122 -66
- package/components/LollipopChart.astro +8 -0
- package/components/MediaFilmstrip.astro +19 -0
- package/components/MediaThumbnail.astro +29 -0
- package/components/MediaViewport.astro +38 -0
- package/components/Mentions.astro +2 -1
- package/components/MessageScroller.astro +6 -0
- package/components/MotionGroup.astro +19 -0
- package/components/NativeSelect.astro +2 -2
- package/components/Pagination.astro +2 -1
- package/components/PasswordField.astro +1 -1
- package/components/PhoneInput.astro +60 -9
- package/components/PhoneNumber.astro +19 -0
- package/components/PieChart.astro +26 -3
- package/components/PromptComposer.astro +36 -0
- package/components/RangeChart.astro +38 -8
- package/components/ScatterChart.astro +62 -14
- package/components/Segmented.astro +5 -5
- package/components/Select.astro +8 -8
- package/components/SourceCitation.astro +18 -0
- package/components/Sparkline.astro +9 -10
- package/components/Stack.astro +2 -2
- package/components/StreamMessage.astro +26 -0
- package/components/Tabs.astro +3 -0
- package/components/ToolActivity.astro +17 -0
- package/components/TreeSelect.astro +1 -0
- package/components/VirtualList.astro +5 -2
- package/components/VisualEffect.astro +21 -0
- package/components/WaterfallChart.astro +27 -0
- package/components/WorldMap.astro +213 -0
- package/forms.ts +17 -4
- package/index.ts +37 -0
- package/internal/ChartInspection.astro +27 -0
- package/internal/ComparisonChart.astro +69 -0
- package/internal/IntervalChart.astro +66 -0
- package/package.json +6 -4
- package/runtime/UIPrimitives.astro +670 -647
- package/runtime/chart-actions.ts +19 -0
- package/runtime/controllers/amount-field.ts +12 -0
- package/runtime/controllers/attachments.ts +13 -0
- package/runtime/controllers/chart-activation.ts +33 -0
- package/runtime/controllers/charts.ts +33 -0
- package/runtime/controllers/combobox.ts +39 -0
- package/runtime/controllers/comparison-reset.ts +20 -0
- package/runtime/controllers/data-table.ts +142 -0
- package/runtime/controllers/dialogs.ts +32 -32
- package/runtime/controllers/document-navigation.ts +54 -22
- package/runtime/controllers/file-upload.ts +89 -0
- package/runtime/controllers/image-comparison.ts +53 -0
- package/runtime/controllers/media-selection.ts +20 -0
- package/runtime/controllers/media-viewport.ts +54 -0
- package/runtime/controllers/media-workspace.ts +8 -0
- package/runtime/controllers/message-scroller.ts +13 -0
- package/runtime/controllers/motion.ts +15 -16
- package/runtime/controllers/optional-media.ts +29 -0
- package/runtime/controllers/phone-input.ts +128 -39
- package/runtime/controllers/visual-interactions.ts +75 -0
- package/runtime/controllers/world-map.ts +154 -0
- package/styles/critical.css +634 -61
- package/styles/lumen.css +101 -0
|
@@ -4,6 +4,7 @@ import type { HTMLAttributes } from 'astro/types'
|
|
|
4
4
|
import {
|
|
5
5
|
getLumenPhoneCountries,
|
|
6
6
|
getLumenPhoneCountry,
|
|
7
|
+
type LumenControlVisualSize,
|
|
7
8
|
type LumenPhoneCountry,
|
|
8
9
|
type LumenPhoneCountryOptions,
|
|
9
10
|
type LumenPhoneNumber,
|
|
@@ -11,6 +12,8 @@ import {
|
|
|
11
12
|
resolveLumenPhoneNumber
|
|
12
13
|
} from '@santi020k/lumen-core'
|
|
13
14
|
|
|
15
|
+
import CountryFlag from './CountryFlag.astro'
|
|
16
|
+
|
|
14
17
|
interface CountryOption {
|
|
15
18
|
disabled?: boolean
|
|
16
19
|
label: string
|
|
@@ -23,7 +26,7 @@ interface NormalizedCountryOption extends CountryOption {
|
|
|
23
26
|
regionCode?: string
|
|
24
27
|
}
|
|
25
28
|
|
|
26
|
-
interface Props extends
|
|
29
|
+
interface Props extends HTMLAttributes<'div'> {
|
|
27
30
|
class?: string | null
|
|
28
31
|
className?: string
|
|
29
32
|
countries?: Country[]
|
|
@@ -31,17 +34,30 @@ interface Props extends Omit<HTMLAttributes<'div'>, 'size'> {
|
|
|
31
34
|
countryLabel?: string
|
|
32
35
|
countryName?: string
|
|
33
36
|
countryValue?: string
|
|
37
|
+
disabled?: boolean
|
|
38
|
+
readOnly?: boolean
|
|
39
|
+
required?: boolean
|
|
40
|
+
errorMessage?: string
|
|
41
|
+
showValidationError?: boolean
|
|
42
|
+
inputProps?: Omit<HTMLAttributes<'input'>, 'value' | 'name' | 'type'>
|
|
34
43
|
invalidNumberMessage?: string
|
|
35
44
|
locale?: LumenPhoneCountryOptions['locale']
|
|
36
45
|
name?: string
|
|
37
46
|
placeholder?: string
|
|
38
|
-
|
|
47
|
+
visualSize?: LumenControlVisualSize
|
|
39
48
|
value?: string
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
const {
|
|
43
52
|
class: classProp = '',
|
|
44
53
|
className = '',
|
|
54
|
+
id,
|
|
55
|
+
disabled = false,
|
|
56
|
+
readOnly = false,
|
|
57
|
+
required = false,
|
|
58
|
+
errorMessage,
|
|
59
|
+
showValidationError = true,
|
|
60
|
+
inputProps = {},
|
|
45
61
|
countries = [],
|
|
46
62
|
countryOptions,
|
|
47
63
|
countryLabel = 'Country code',
|
|
@@ -51,18 +67,20 @@ const {
|
|
|
51
67
|
locale,
|
|
52
68
|
name = 'phone',
|
|
53
69
|
placeholder = 'Phone number',
|
|
54
|
-
|
|
70
|
+
visualSize = 'default',
|
|
55
71
|
value,
|
|
56
72
|
...rest
|
|
57
73
|
} = Astro.props
|
|
58
74
|
|
|
75
|
+
const isDisabled = disabled || Boolean(inputProps.disabled)
|
|
76
|
+
const isReadOnly = readOnly || Boolean(inputProps.readonly)
|
|
59
77
|
const phoneOptions = locale === undefined ? {} : { locale }
|
|
60
78
|
const metadataCountries = countryOptions ?? getLumenPhoneCountries(phoneOptions)
|
|
61
79
|
const usesMetadata = countries.length === 0
|
|
62
80
|
|
|
63
81
|
const normalizedCountries: NormalizedCountryOption[] = usesMetadata ?
|
|
64
82
|
metadataCountries.map(country => ({
|
|
65
|
-
label: country.
|
|
83
|
+
label: `${country.displayName} (${country.callingCode})`,
|
|
66
84
|
regionCode: country.regionCode,
|
|
67
85
|
value: country.regionCode
|
|
68
86
|
})) :
|
|
@@ -95,6 +113,9 @@ const normalizePhoneValue = (detectedValue: LumenPhoneNumber | null): LumenPhone
|
|
|
95
113
|
const phoneValue = normalizePhoneValue(detectedPhoneValue)
|
|
96
114
|
const selectedCountryValue = usesMetadata ? phoneValue?.country.regionCode ?? selectedCountry?.regionCode : countryValue
|
|
97
115
|
const renderedValue = usesMetadata ? phoneValue?.nationalNumber ?? value : value
|
|
116
|
+
const inputId = id ?? inputProps.id ?? `phone-${crypto.randomUUID()}`
|
|
117
|
+
const errorId = `phone-error-${crypto.randomUUID()}`
|
|
118
|
+
const effectiveError = errorMessage || (showValidationError && phoneValue && phoneValue.nationalNumber.length > 0 && !phoneValue.isValid ? invalidNumberMessage : '')
|
|
98
119
|
|
|
99
120
|
const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
100
121
|
'ui-phone-input',
|
|
@@ -104,21 +125,39 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
|
104
125
|
|
|
105
126
|
<div
|
|
106
127
|
class:list={classList}
|
|
128
|
+
data-disabled={isDisabled || undefined}
|
|
129
|
+
data-readonly={isReadOnly || undefined}
|
|
130
|
+
data-size={visualSize}
|
|
131
|
+
data-slot="phone-input"
|
|
132
|
+
data-error-message={errorMessage}
|
|
133
|
+
data-error-id={errorId}
|
|
134
|
+
data-show-validation-error={String(showValidationError)}
|
|
135
|
+
data-invalid={effectiveError ? 'true' : undefined}
|
|
107
136
|
data-e164={phoneValue?.e164}
|
|
108
137
|
data-invalid-number-message={invalidNumberMessage}
|
|
109
138
|
data-ui-phone-input={usesMetadata ? '' : undefined}
|
|
110
139
|
data-valid={phoneValue ? String(phoneValue.isValid) : undefined}
|
|
111
140
|
{...passthrough}
|
|
112
141
|
>
|
|
142
|
+
<span class="ui-phone-input__picker" data-slot="phone-country">
|
|
143
|
+
{usesMetadata && (
|
|
144
|
+
<span aria-hidden="true" class="ui-phone-input__selection">
|
|
145
|
+
<CountryFlag decorative regionCode={phoneValue?.country.regionCode ?? selectedCountry?.regionCode ?? countryValue} />
|
|
146
|
+
<span data-ui-phone-code>{phoneValue?.country.callingCode ?? selectedCountry?.callingCode}</span>
|
|
147
|
+
<span class="ui-phone-input__chevron" />
|
|
148
|
+
</span>
|
|
149
|
+
)}
|
|
113
150
|
<select
|
|
114
151
|
aria-label={countryLabel}
|
|
115
152
|
class:list={[
|
|
116
153
|
'ui-select',
|
|
117
154
|
'ui-phone-input__country',
|
|
118
|
-
|
|
119
|
-
|
|
155
|
+
visualSize === 'sm' && 'ui-select--sm',
|
|
156
|
+
visualSize === 'lg' && 'ui-select--lg'
|
|
120
157
|
]}
|
|
158
|
+
disabled={isDisabled || isReadOnly || normalizedCountries.length === 0}
|
|
121
159
|
name={countryName}
|
|
160
|
+
form={inputProps.form}
|
|
122
161
|
>
|
|
123
162
|
{normalizedCountries.map(country => (
|
|
124
163
|
<option
|
|
@@ -130,13 +169,22 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
|
130
169
|
</option>
|
|
131
170
|
))}
|
|
132
171
|
</select>
|
|
172
|
+
</span>
|
|
133
173
|
<input
|
|
134
|
-
|
|
174
|
+
{...inputProps}
|
|
175
|
+
id={inputId}
|
|
176
|
+
autocomplete={inputProps.autocomplete ?? 'tel-national'}
|
|
177
|
+
disabled={disabled || inputProps.disabled}
|
|
178
|
+
readonly={readOnly || inputProps.readonly}
|
|
179
|
+
required={required || inputProps.required}
|
|
180
|
+
aria-invalid={effectiveError ? 'true' : inputProps['aria-invalid']}
|
|
181
|
+
aria-errormessage={effectiveError ? errorId : inputProps['aria-errormessage']}
|
|
182
|
+
aria-describedby={[inputProps['aria-describedby'], effectiveError ? errorId : undefined].filter(Boolean).join(' ') || undefined}
|
|
135
183
|
class:list={[
|
|
136
184
|
'ui-input',
|
|
137
185
|
'ui-phone-input__number',
|
|
138
|
-
|
|
139
|
-
|
|
186
|
+
visualSize === 'sm' && 'ui-input--sm',
|
|
187
|
+
visualSize === 'lg' && 'ui-input--lg'
|
|
140
188
|
]}
|
|
141
189
|
inputmode="tel"
|
|
142
190
|
name={name}
|
|
@@ -144,4 +192,7 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
|
144
192
|
type="tel"
|
|
145
193
|
value={renderedValue}
|
|
146
194
|
/>
|
|
195
|
+
{isReadOnly && !isDisabled && <input name={countryName} form={inputProps.form} type="hidden" value={selectedCountryValue} />}
|
|
147
196
|
</div>
|
|
197
|
+
|
|
198
|
+
<span class="ui-phone-input__error" id={errorId} role="alert" hidden={!effectiveError}>{effectiveError}</span>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
import { formatLumenPhoneNumber, type LumenPhoneNumber } from '@santi020k/lumen-core'
|
|
5
|
+
|
|
6
|
+
import CountryFlag from './CountryFlag.astro'
|
|
7
|
+
|
|
8
|
+
interface Props extends HTMLAttributes<'span'> {
|
|
9
|
+
value: LumenPhoneNumber
|
|
10
|
+
link?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { value, link = false, class: className, ...props } = Astro.props
|
|
14
|
+
const Tag = link && value.isValid && value.e164 ? 'a' : 'span'
|
|
15
|
+
---
|
|
16
|
+
<Tag {...props} class:list={['ui-phone-number', className]} href={link && value.isValid && value.e164 ? `tel:${value.e164}` : undefined} title={value.country.displayName}>
|
|
17
|
+
<CountryFlag decorative regionCode={value.country.regionCode} />
|
|
18
|
+
<span>{formatLumenPhoneNumber(value) || '—'}</span>
|
|
19
|
+
</Tag>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { HTMLAttributes } from 'astro/types'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { createLumenChartDatumActivation,
|
|
5
5
|
createLumenPieGeometry,
|
|
6
6
|
formatLumenChartSummary,
|
|
7
7
|
getLumenChartToneClassName,
|
|
@@ -12,7 +12,10 @@ import {
|
|
|
12
12
|
type LumenPieChartVariant,
|
|
13
13
|
resolveLumenChartLabels } from '@santi020k/lumen-core'
|
|
14
14
|
|
|
15
|
+
import { createChartDatumAction } from '../runtime/chart-actions'
|
|
16
|
+
|
|
15
17
|
import Chart from './Chart.astro'
|
|
18
|
+
import ChartDatumActions from './ChartDatumActions.astro'
|
|
16
19
|
|
|
17
20
|
interface Props extends HTMLAttributes<'figure'> {
|
|
18
21
|
caption?: string
|
|
@@ -21,6 +24,7 @@ interface Props extends HTMLAttributes<'figure'> {
|
|
|
21
24
|
class?: string | null
|
|
22
25
|
className?: string
|
|
23
26
|
description?: string
|
|
27
|
+
drilldown?: boolean
|
|
24
28
|
glass?: boolean | 'subtle' | 'strong'
|
|
25
29
|
heading?: string
|
|
26
30
|
labels?: Partial<LumenChartLabels>
|
|
@@ -39,6 +43,7 @@ const {
|
|
|
39
43
|
class: classProp = '',
|
|
40
44
|
className = '',
|
|
41
45
|
description,
|
|
46
|
+
drilldown = false,
|
|
42
47
|
glass = false,
|
|
43
48
|
heading,
|
|
44
49
|
labels,
|
|
@@ -66,6 +71,21 @@ const percentageFor = (percentage: number) => new Intl.NumberFormat(undefined, {
|
|
|
66
71
|
maximumFractionDigits: percentage < 0.01 ? 1 : 0,
|
|
67
72
|
style: 'percent'
|
|
68
73
|
}).format(percentage)
|
|
74
|
+
|
|
75
|
+
const datumActions = drilldown ?
|
|
76
|
+
geometry.slices.map((slice, index) => {
|
|
77
|
+
const datum = renderedSeries.data[index]
|
|
78
|
+
|
|
79
|
+
return datum ?
|
|
80
|
+
createChartDatumAction(
|
|
81
|
+
createLumenChartDatumActivation(series.id, datum),
|
|
82
|
+
`${slice.label} · ${series.label}: ${valueFormatter(slice.value)}`,
|
|
83
|
+
resolvedLabels
|
|
84
|
+
) :
|
|
85
|
+
null
|
|
86
|
+
}) :
|
|
87
|
+
[]
|
|
88
|
+
|
|
69
89
|
---
|
|
70
90
|
|
|
71
91
|
<Chart
|
|
@@ -76,6 +96,7 @@ const percentageFor = (percentage: number) => new Intl.NumberFormat(undefined, {
|
|
|
76
96
|
{heading}
|
|
77
97
|
{summary}
|
|
78
98
|
{...rest}
|
|
99
|
+
data-ui-chart-activation={drilldown || undefined}
|
|
79
100
|
>
|
|
80
101
|
{
|
|
81
102
|
showLegend && hasData && (
|
|
@@ -98,9 +119,10 @@ const percentageFor = (percentage: number) => new Intl.NumberFormat(undefined, {
|
|
|
98
119
|
>
|
|
99
120
|
<g class="ui-pie-chart__slices">
|
|
100
121
|
{
|
|
101
|
-
geometry.slices.map(slice => (
|
|
122
|
+
geometry.slices.map((slice, index) => (
|
|
102
123
|
<path
|
|
103
124
|
class={getLumenChartToneClassName(slice.tone)}
|
|
125
|
+
data-ui-chart-datum={datumActions[index]?.serialized}
|
|
104
126
|
d={slice.path}
|
|
105
127
|
fill-rule="evenodd"
|
|
106
128
|
>
|
|
@@ -130,7 +152,7 @@ const percentageFor = (percentage: number) => new Intl.NumberFormat(undefined, {
|
|
|
130
152
|
showTable && hasData && (
|
|
131
153
|
<details class="ui-chart__data">
|
|
132
154
|
<summary>{resolvedLabels.viewData}</summary>
|
|
133
|
-
<div>
|
|
155
|
+
<div role="group" tabindex="0" aria-label={resolvedLabels.chartData}>
|
|
134
156
|
<table>
|
|
135
157
|
<thead>
|
|
136
158
|
<tr>
|
|
@@ -153,4 +175,5 @@ const percentageFor = (percentage: number) => new Intl.NumberFormat(undefined, {
|
|
|
153
175
|
</details>
|
|
154
176
|
)
|
|
155
177
|
}
|
|
178
|
+
{drilldown && <ChartDatumActions actions={datumActions} label={resolvedLabels.exploreData} />}
|
|
156
179
|
</Chart>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
import { canSubmitLumenPrompt, normalizeLumenPromptLimit } from '@santi020k/lumen-core'
|
|
5
|
+
|
|
6
|
+
import Button from './Button.astro'
|
|
7
|
+
import Form from './Form.astro'
|
|
8
|
+
import Label from './Label.astro'
|
|
9
|
+
import Stack from './Stack.astro'
|
|
10
|
+
import Textarea from './Textarea.astro'
|
|
11
|
+
|
|
12
|
+
interface Props extends HTMLAttributes<'form'> {
|
|
13
|
+
disabled?: boolean
|
|
14
|
+
label?: string
|
|
15
|
+
maxLength?: number
|
|
16
|
+
pending?: boolean
|
|
17
|
+
sendLabel?: string
|
|
18
|
+
stopLabel?: string
|
|
19
|
+
submitOnEnter?: boolean
|
|
20
|
+
value?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { class: className, disabled = false, id = `prompt-${crypto.randomUUID()}`, label = 'Message', maxLength = 4000, pending = false, sendLabel = 'Send', stopLabel = 'Stop', submitOnEnter = false, value = '', ...rest } = Astro.props
|
|
24
|
+
const limit = normalizeLumenPromptLimit(maxLength)
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
<Form {...rest} class:list={['ui-prompt-composer', className]} data-ui-prompt-composer data-disabled={String(disabled)} data-pending={String(pending)} data-submit-on-enter={String(submitOnEnter)} enhance={false} method="post" status={pending ? 'submitting' : 'idle'}>
|
|
28
|
+
<Label for={id}>{label}</Label>
|
|
29
|
+
<Textarea id={id} data-ui-prompt-input disabled={disabled} maxlength={limit} name="prompt" required rows={3}>{value}</Textarea>
|
|
30
|
+
<Stack direction="horizontal" gap="related" wrap>
|
|
31
|
+
<slot name="actions" />
|
|
32
|
+
<Button data-ui-prompt-send disabled={disabled || pending || (!rest.action && !canSubmitLumenPrompt(value, limit))} type="submit">{sendLabel}</Button>
|
|
33
|
+
<Button data-ui-prompt-stop disabled={disabled} hidden={!pending} type="button" variant="secondary">{stopLabel}</Button>
|
|
34
|
+
</Stack>
|
|
35
|
+
<slot />
|
|
36
|
+
</Form>
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { HTMLAttributes } from 'astro/types'
|
|
3
3
|
|
|
4
|
-
import { createLumenRangeGeometry, getLumenChartTicks, getLumenChartToneClassName, type LumenChartLabels, type LumenChartTone, type LumenRangeDatum, resolveLumenChartLabels, scaleLumenChartValue } from '@santi020k/lumen-core'
|
|
4
|
+
import { createLumenRangeDatumActivation, createLumenRangeGeometry, getLumenChartCategoryTicks, getLumenChartTicks, getLumenChartToneClassName, type LumenChartLabels, type LumenChartTone, type LumenRangeDatum, resolveLumenChartLabels, scaleLumenChartValue } from '@santi020k/lumen-core'
|
|
5
|
+
|
|
6
|
+
import { createChartDatumAction } from '../runtime/chart-actions'
|
|
5
7
|
|
|
6
8
|
import Chart from './Chart.astro'
|
|
9
|
+
import ChartDatumActions from './ChartDatumActions.astro'
|
|
7
10
|
|
|
8
11
|
interface Props extends HTMLAttributes<'figure'> {
|
|
9
12
|
caption?: string
|
|
@@ -11,6 +14,7 @@ interface Props extends HTMLAttributes<'figure'> {
|
|
|
11
14
|
className?: string
|
|
12
15
|
data: readonly LumenRangeDatum[]
|
|
13
16
|
description?: string
|
|
17
|
+
drilldown?: boolean
|
|
14
18
|
formatValue?: (value: number) => string
|
|
15
19
|
heading?: string
|
|
16
20
|
labels?: Partial<LumenChartLabels>
|
|
@@ -19,7 +23,7 @@ interface Props extends HTMLAttributes<'figure'> {
|
|
|
19
23
|
tone?: LumenChartTone
|
|
20
24
|
}
|
|
21
25
|
|
|
22
|
-
const { caption, class: classProp = '', className = '', data, description, formatValue = String, heading, labels, showTable = true, summary: summaryProp, tone = 'series-1', ...rest } = Astro.props
|
|
26
|
+
const { caption, class: classProp = '', className = '', data, description, drilldown = false, formatValue = String, heading, labels, showTable = true, summary: summaryProp, tone = 'series-1', ...rest } = Astro.props
|
|
23
27
|
const width = 640
|
|
24
28
|
const height = 320
|
|
25
29
|
const padding = 44
|
|
@@ -32,29 +36,55 @@ const summary = summaryProp ?? (
|
|
|
32
36
|
resolvedLabels.formatRangeSummary(geometry.points.length)
|
|
33
37
|
)
|
|
34
38
|
|
|
39
|
+
const categoryTicks = getLumenChartCategoryTicks(geometry.points.map(point => String(point.xLabel ?? point.x)), {
|
|
40
|
+
start: padding,
|
|
41
|
+
end: width - padding,
|
|
42
|
+
positions: geometry.points.map(point => point.xCoordinate)
|
|
43
|
+
})
|
|
44
|
+
|
|
35
45
|
const ticks = getLumenChartTicks(geometry.domain)
|
|
46
|
+
|
|
47
|
+
const datumActions = drilldown ?
|
|
48
|
+
geometry.points.map(point => createChartDatumAction(
|
|
49
|
+
createLumenRangeDatumActivation(point),
|
|
50
|
+
`${point.xLabel ?? point.x}: ${point.label ?? `${formatValue(point.low ?? 0)}–${formatValue(point.high ?? 0)}`}`,
|
|
51
|
+
resolvedLabels
|
|
52
|
+
)) :
|
|
53
|
+
[]
|
|
54
|
+
|
|
36
55
|
---
|
|
37
56
|
|
|
38
|
-
<Chart class:list={['ui-range-chart', getLumenChartToneClassName(tone), classProp, className]} {caption} {description} {heading} {summary} {...rest}>
|
|
57
|
+
<Chart class:list={['ui-range-chart', getLumenChartToneClassName(tone), classProp, className]} {caption} {description} {heading} {summary} {...rest} data-ui-chart-activation={drilldown || undefined}>
|
|
39
58
|
{geometry.points.length === 0 && <p class="ui-chart__empty" role="status">{resolvedLabels.empty}</p>}
|
|
40
|
-
<div class="ui-chart__plot" hidden={geometry.points.length === 0}><svg aria-hidden="true" viewBox={`0 0 ${width} ${height}`}><g class="
|
|
59
|
+
<div aria-label={resolvedLabels.chartData} class="ui-chart__plot" role="region" tabindex="0" hidden={geometry.points.length === 0}><svg aria-hidden="true" viewBox={`0 0 ${width} ${height}`}><g class="
|
|
41
60
|
ui-chart__grid
|
|
42
61
|
"
|
|
43
|
-
|
|
62
|
+
>{ticks.map(tick => {
|
|
44
63
|
const y = scaleLumenChartValue(tick, geometry.domain, height - padding, padding)
|
|
45
64
|
|
|
46
65
|
return <><line x1={padding} x2={width - padding} y1={y} y2={y}/><text x={padding - 8} y={y}>{formatValue(tick)}</text></>
|
|
47
|
-
})}</g><path
|
|
66
|
+
})}</g><g class="ui-chart__axis-labels">{categoryTicks.map(tick => <text x={tick.position} y={height - 12} text-anchor={tick.textAnchor}>{tick.label}</text>)}</g><path
|
|
48
67
|
class="ui-range-chart__area"
|
|
49
68
|
d={geometry.areaPath}
|
|
50
|
-
|
|
69
|
+
/>{geometry.points.map((point, index) => (
|
|
51
70
|
<line
|
|
71
|
+
data-ui-chart-datum={datumActions[index]?.serialized}
|
|
52
72
|
class="ui-range-chart__interval"
|
|
53
73
|
x1={point.xCoordinate}
|
|
54
74
|
x2={point.xCoordinate}
|
|
55
75
|
y1={point.highCoordinate}
|
|
56
76
|
y2={point.lowCoordinate}
|
|
57
77
|
><title>{`${point.xLabel ?? point.x}: ${point.label ?? `${formatValue(point.low ?? 0)}–${formatValue(point.high ?? 0)}`}`}</title></line>
|
|
78
|
+
))}{drilldown && geometry.points.map((point, index) => (
|
|
79
|
+
<rect
|
|
80
|
+
class="ui-chart__datum-hit"
|
|
81
|
+
x={point.xCoordinate - 10}
|
|
82
|
+
y={Math.min(point.highCoordinate, point.lowCoordinate) - Math.max(0, 20 - Math.abs(point.highCoordinate - point.lowCoordinate)) / 2}
|
|
83
|
+
width="20"
|
|
84
|
+
height={Math.max(20, Math.abs(point.highCoordinate - point.lowCoordinate))}
|
|
85
|
+
data-ui-chart-datum={datumActions[index]?.serialized}
|
|
86
|
+
/>
|
|
58
87
|
))}</svg></div>
|
|
59
|
-
{showTable && <details class="ui-chart__data"><summary>{resolvedLabels.viewData}</summary><div><table><thead><tr><th scope="col">{resolvedLabels.category}</th><th scope="col">{resolvedLabels.low}</th><th scope="col">{resolvedLabels.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) ? resolvedLabels.notAvailable : formatValue(item.low)}</td><td>{item.high === null || !Number.isFinite(item.high) ? resolvedLabels.notAvailable : formatValue(item.high)}</td></tr>)}</tbody></table></div></details>}
|
|
88
|
+
{showTable && <details class="ui-chart__data"><summary>{resolvedLabels.viewData}</summary><div role="group" tabindex="0" aria-label={resolvedLabels.chartData}><table><thead><tr><th scope="col">{resolvedLabels.category}</th><th scope="col">{resolvedLabels.low}</th><th scope="col">{resolvedLabels.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) ? resolvedLabels.notAvailable : formatValue(item.low)}</td><td>{item.high === null || !Number.isFinite(item.high) ? resolvedLabels.notAvailable : formatValue(item.high)}</td></tr>)}</tbody></table></div></details>}
|
|
89
|
+
{drilldown && <ChartDatumActions actions={datumActions} label={resolvedLabels.exploreData} />}
|
|
60
90
|
</Chart>
|
|
@@ -1,33 +1,46 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { HTMLAttributes } from 'astro/types'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { createLumenChartDatumActivation,
|
|
5
5
|
createLumenScatterGeometry,
|
|
6
|
+
createLumenScatterReferences,
|
|
6
7
|
formatLumenChartSummary,
|
|
7
8
|
getLumenChartTicks,
|
|
8
9
|
getLumenChartToneClassName,
|
|
10
|
+
getLumenScatterXTicks,
|
|
11
|
+
type LumenChartDomain,
|
|
9
12
|
type LumenChartLabels,
|
|
10
|
-
type LumenChartScaleType,
|
|
11
13
|
type LumenChartSeries,
|
|
14
|
+
type LumenScatterReference,
|
|
15
|
+
type LumenScatterScaleType,
|
|
12
16
|
resolveLumenChartLabels,
|
|
13
|
-
scaleLumenChartValue } from '@santi020k/lumen-core'
|
|
17
|
+
scaleLumenChartValue, scaleLumenScatterX } from '@santi020k/lumen-core'
|
|
18
|
+
|
|
19
|
+
import { createChartDatumAction } from '../runtime/chart-actions'
|
|
14
20
|
|
|
15
21
|
import Chart from './Chart.astro'
|
|
22
|
+
import ChartDatumActions from './ChartDatumActions.astro'
|
|
16
23
|
|
|
17
24
|
interface Props extends HTMLAttributes<'figure'> {
|
|
18
25
|
caption?: string
|
|
19
26
|
class?: string | null
|
|
20
27
|
className?: string
|
|
21
28
|
description?: string
|
|
29
|
+
drilldown?: boolean
|
|
22
30
|
emptyLabel?: string
|
|
23
31
|
formatValue?: (value: number) => string
|
|
32
|
+
formatX?: (value: number | string) => string
|
|
33
|
+
formatY?: (value: number) => string
|
|
34
|
+
xDomain?: Partial<LumenChartDomain>
|
|
35
|
+
domain?: Partial<LumenChartDomain>
|
|
36
|
+
references?: readonly LumenScatterReference[]
|
|
24
37
|
heading?: string
|
|
25
38
|
labels?: Partial<LumenChartLabels>
|
|
26
39
|
series: readonly LumenChartSeries[]
|
|
27
40
|
showLegend?: boolean
|
|
28
41
|
showTable?: boolean
|
|
29
42
|
summary?: string
|
|
30
|
-
xScale?:
|
|
43
|
+
xScale?: LumenScatterScaleType
|
|
31
44
|
}
|
|
32
45
|
|
|
33
46
|
const {
|
|
@@ -35,8 +48,14 @@ const {
|
|
|
35
48
|
class: classProp = '',
|
|
36
49
|
className = '',
|
|
37
50
|
description,
|
|
51
|
+
drilldown = false,
|
|
38
52
|
emptyLabel,
|
|
39
53
|
formatValue = String,
|
|
54
|
+
formatX = String,
|
|
55
|
+
formatY = formatValue,
|
|
56
|
+
xDomain,
|
|
57
|
+
domain,
|
|
58
|
+
references = [],
|
|
40
59
|
heading,
|
|
41
60
|
labels,
|
|
42
61
|
series,
|
|
@@ -47,7 +66,9 @@ const {
|
|
|
47
66
|
...rest
|
|
48
67
|
} = Astro.props
|
|
49
68
|
|
|
50
|
-
const
|
|
69
|
+
const plotId = `ui-scatter-${crypto.randomUUID()}`
|
|
70
|
+
const geometry = createLumenScatterGeometry(series, { xScale, ...(xDomain ? { xDomain } : {}), ...(domain ? { domain } : {}) })
|
|
71
|
+
const referenceGeometry = createLumenScatterReferences(references, geometry, xScale)
|
|
51
72
|
const resolvedLabels = resolveLumenChartLabels(labels)
|
|
52
73
|
|
|
53
74
|
const projectedSeries = series.map(item => ({
|
|
@@ -56,28 +77,55 @@ const projectedSeries = series.map(item => ({
|
|
|
56
77
|
})).filter(item => item.data.length > 0)
|
|
57
78
|
|
|
58
79
|
const hasData = geometry.points.length > 0
|
|
59
|
-
const summary = summaryProp ?? formatLumenChartSummary(projectedSeries,
|
|
60
|
-
const xTicks =
|
|
80
|
+
const summary = summaryProp ?? formatLumenChartSummary(projectedSeries, formatY, resolvedLabels)
|
|
81
|
+
const xTicks = getLumenScatterXTicks(geometry.xDomain, xScale)
|
|
61
82
|
const yTicks = getLumenChartTicks(geometry.domain)
|
|
83
|
+
|
|
84
|
+
const datumActions = drilldown ?
|
|
85
|
+
geometry.points.map(point => createChartDatumAction(
|
|
86
|
+
createLumenChartDatumActivation(point.seriesId, point),
|
|
87
|
+
`${point.xLabel ?? formatX(point.x)} · ${point.seriesLabel}: ${point.label ?? formatY(point.y ?? 0)}`,
|
|
88
|
+
resolvedLabels
|
|
89
|
+
)) :
|
|
90
|
+
[]
|
|
91
|
+
|
|
62
92
|
---
|
|
63
93
|
|
|
64
|
-
<Chart class:list={['ui-scatter-chart', classProp, className]} {caption} {description} {heading} {summary} {...rest}>
|
|
94
|
+
<Chart class:list={['ui-scatter-chart', classProp, className]} {caption} {description} {heading} {summary} {...rest} data-ui-chart-activation={drilldown || undefined}>
|
|
65
95
|
{showLegend && hasData && <ul class="ui-chart__legend" aria-label={resolvedLabels.chartLegend}>{series.map((item, index) => <li class={getLumenChartToneClassName(item.tone, index)}><span aria-hidden="true" />{item.label}</li>)}</ul>}
|
|
66
96
|
{!hasData && <p class="ui-chart__empty" role="status">{emptyLabel ?? resolvedLabels.empty}</p>}
|
|
67
|
-
<div class="ui-chart__plot" hidden={!hasData}>
|
|
97
|
+
<div aria-label={resolvedLabels.chartData} class="ui-chart__plot" role="region" tabindex="0" hidden={!hasData}>
|
|
68
98
|
<svg aria-hidden="true" viewBox={`0 0 ${geometry.width} ${geometry.height}`}>
|
|
99
|
+
<defs><clipPath id={plotId}><rect x="44" y="44" width={geometry.width - 88} height={geometry.height - 88} /></clipPath></defs>
|
|
69
100
|
<g class="ui-chart__grid">{yTicks.map(tick => {
|
|
70
101
|
const y = scaleLumenChartValue(tick, geometry.domain, geometry.height - 44, 44)
|
|
71
102
|
|
|
72
|
-
return <><line x1="44" x2={geometry.width - 44} y1={y} y2={y}/><text x="36" y={y}>{
|
|
103
|
+
return <><line x1="44" x2={geometry.width - 44} y1={y} y2={y}/><text x="36" y={y}>{formatY(tick)}</text></>
|
|
73
104
|
})}</g>
|
|
74
105
|
<g class="ui-chart__axis-labels">{xTicks.map(tick => {
|
|
75
|
-
const x =
|
|
106
|
+
const x = scaleLumenScatterX(tick, geometry.xDomain, 44, geometry.width - 44, xScale)
|
|
76
107
|
|
|
77
|
-
return <text text-anchor="middle" x={x} y={geometry.height - 14}>{
|
|
108
|
+
return <text text-anchor="middle" x={x} y={geometry.height - 14}>{formatX(tick)}</text>
|
|
78
109
|
})}</g>
|
|
79
|
-
<g class="ui-scatter-
|
|
110
|
+
<g class="ui-scatter-chart__references" clip-path={`url(#${plotId})`}>{referenceGeometry.map(reference => reference.region ?
|
|
111
|
+
<rect x={Math.min(reference.x1, reference.x2)} y={Math.min(reference.y1, reference.y2)} width={Math.abs(reference.x2 - reference.x1)} height={Math.abs(reference.y2 - reference.y1)}><title>{reference.label}</title></rect> :
|
|
112
|
+
<line x1={reference.x1} x2={reference.x2} y1={reference.y1} y2={reference.y2}><title>{reference.label}</title></line>)}</g>
|
|
113
|
+
<g class="ui-scatter-chart__marks" clip-path={`url(#${plotId})`}>{geometry.points.map((point, index) => <circle data-ui-chart-datum={datumActions[index]?.serialized} class={getLumenChartToneClassName(point.tone)} cx={point.xCoordinate} cy={point.yCoordinate} r={point.radius}><title>{`${point.xLabel ?? formatX(point.x)} · ${point.seriesLabel}: ${point.label ?? formatY(point.y ?? 0)}`}</title></circle>)}</g>
|
|
114
|
+
{drilldown && geometry.points.map((point, index) => point.xCoordinate >= 44 && point.xCoordinate <= geometry.width - 44 && point.yCoordinate >= 44 && point.yCoordinate <= geometry.height - 44 && (
|
|
115
|
+
<circle
|
|
116
|
+
class="ui-chart__datum-hit"
|
|
117
|
+
cx={point.xCoordinate}
|
|
118
|
+
cy={point.yCoordinate}
|
|
119
|
+
r={Math.max(10, point.radius)}
|
|
120
|
+
data-ui-chart-datum={datumActions[index]?.serialized}
|
|
121
|
+
/>
|
|
122
|
+
))}
|
|
123
|
+
|
|
80
124
|
</svg>
|
|
81
125
|
</div>
|
|
82
|
-
{
|
|
126
|
+
{referenceGeometry.length > 0 && (
|
|
127
|
+
<ul class="ui-scatter-chart__reference-labels">{referenceGeometry.map(reference => <li>{reference.label}</li>)}</ul>
|
|
128
|
+
)}
|
|
129
|
+
{showTable && hasData && <details class="ui-chart__data"><summary>{resolvedLabels.viewData}</summary><div role="group" tabindex="0" aria-label={resolvedLabels.chartData}><table><thead><tr><th scope="col">{resolvedLabels.x}</th><th scope="col">{resolvedLabels.series}</th><th scope="col">{resolvedLabels.value}</th><th scope="col">{resolvedLabels.size}</th></tr></thead><tbody>{geometry.points.map(point => <tr><th scope="row">{point.xLabel ?? formatX(point.x)}</th><td>{point.seriesLabel}</td><td>{point.label ?? formatY(point.y ?? 0)}</td><td>{point.size === undefined || point.size === null || !Number.isFinite(point.size) ? resolvedLabels.notAvailable : formatValue(point.size)}</td></tr>)}</tbody></table></div></details>}
|
|
130
|
+
{drilldown && <ChartDatumActions actions={datumActions} label={resolvedLabels.exploreData} />}
|
|
83
131
|
</Chart>
|
|
@@ -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
|
type SegmentedOption = string | { disabled?: boolean, label: string, value: string }
|
|
7
7
|
|
|
@@ -11,7 +11,7 @@ interface Props extends HTMLAttributes<'div'> {
|
|
|
11
11
|
defaultValue?: string
|
|
12
12
|
name?: string
|
|
13
13
|
options?: SegmentedOption[]
|
|
14
|
-
|
|
14
|
+
visualSize?: LumenControlVisualSize
|
|
15
15
|
value?: string
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -22,7 +22,7 @@ const {
|
|
|
22
22
|
defaultValue,
|
|
23
23
|
name = 'segmented',
|
|
24
24
|
options = [],
|
|
25
|
-
|
|
25
|
+
visualSize = 'default',
|
|
26
26
|
value = defaultValue,
|
|
27
27
|
...rest
|
|
28
28
|
} = Astro.props
|
|
@@ -31,8 +31,8 @@ const normalized = options.map(option => typeof option === 'string' ? { disabled
|
|
|
31
31
|
|
|
32
32
|
const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
33
33
|
'ui-segmented',
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
visualSize === 'sm' && 'ui-segmented--sm',
|
|
35
|
+
visualSize === 'lg' && 'ui-segmented--lg'
|
|
36
36
|
], classProp, className)
|
|
37
37
|
---
|
|
38
38
|
|
package/components/Select.astro
CHANGED
|
@@ -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
|
|
@@ -11,13 +11,13 @@ interface Option {
|
|
|
11
11
|
|
|
12
12
|
type SelectOption = Option | string
|
|
13
13
|
|
|
14
|
-
interface Props extends
|
|
14
|
+
interface Props extends HTMLAttributes<'select'> {
|
|
15
15
|
class?: string | null
|
|
16
16
|
className?: string
|
|
17
17
|
glass?: boolean | 'subtle' | 'strong'
|
|
18
18
|
options?: SelectOption[]
|
|
19
19
|
placeholder?: string
|
|
20
|
-
|
|
20
|
+
visualSize?: LumenControlVisualSize
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
const {
|
|
@@ -30,7 +30,7 @@ const {
|
|
|
30
30
|
options = [],
|
|
31
31
|
placeholder,
|
|
32
32
|
required = false,
|
|
33
|
-
|
|
33
|
+
visualSize = 'default',
|
|
34
34
|
value,
|
|
35
35
|
...rest
|
|
36
36
|
} = Astro.props
|
|
@@ -63,8 +63,8 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
|
63
63
|
class:list={[
|
|
64
64
|
'ui-select',
|
|
65
65
|
'ui-select__native',
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
visualSize === 'sm' && 'ui-select--sm',
|
|
67
|
+
visualSize === 'lg' && 'ui-select--lg'
|
|
68
68
|
]}
|
|
69
69
|
data-ui-select-native
|
|
70
70
|
disabled={disabled}
|
|
@@ -98,8 +98,8 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
|
98
98
|
class:list={[
|
|
99
99
|
'ui-select',
|
|
100
100
|
'ui-select__trigger',
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
visualSize === 'sm' && 'ui-select--sm',
|
|
102
|
+
visualSize === 'lg' && 'ui-select--lg'
|
|
103
103
|
]}
|
|
104
104
|
data-ui-select-trigger
|
|
105
105
|
disabled={disabled}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
import { resolveLumenCitationHref } from '@santi020k/lumen-core'
|
|
5
|
+
|
|
6
|
+
import Link from './Link.astro'
|
|
7
|
+
|
|
8
|
+
interface Props extends Omit<HTMLAttributes<'a'>, 'href'> { href: string, label: string }
|
|
9
|
+
|
|
10
|
+
const { class: className, href, label, ...rest } = Astro.props
|
|
11
|
+
const safeHref = resolveLumenCitationHref(href)
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
{safeHref ?
|
|
15
|
+
<Link {...rest} class:list={['ui-source-citation', className]} href={safeHref} rel="noopener noreferrer">{label}<slot /></Link> :
|
|
16
|
+
(
|
|
17
|
+
<span class:list={['ui-source-citation', className]}>{label}<slot /></span>
|
|
18
|
+
)}
|