@refraction-ui/astro 0.18.1 → 0.20.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/dist/action-list/action-list.styles.ts +33 -0
- package/dist/action-list/action-list.ts +31 -0
- package/dist/action-list/index.ts +16 -0
- package/dist/analytics-sink-app-insights/app-insights-sink.ts +1 -1
- package/dist/analytics-sink-posthog/client-sdk-sink.ts +1 -1
- package/dist/analytics-sink-posthog/replay.ts +1 -1
- package/dist/astro-action-list/ActionList.astro +12 -0
- package/dist/astro-action-list/ActionListItem.astro +62 -0
- package/dist/astro-action-list/index.ts +1 -0
- package/dist/astro-avatar/Avatar.astro +5 -4
- package/dist/astro-callout/Callout.astro +4 -1
- package/dist/astro-card/Card.astro +3 -2
- package/dist/astro-card/CardTitle.astro +6 -4
- package/dist/astro-data-table/DataTable.astro +7 -3
- package/dist/astro-funnel-chart/FunnelChart.astro +64 -0
- package/dist/astro-funnel-chart/index.ts +1 -0
- package/dist/astro-line-chart/LineChart.astro +82 -0
- package/dist/astro-line-chart/index.ts +1 -0
- package/dist/astro-native-select/NativeSelect.astro +26 -0
- package/dist/astro-native-select/index.ts +1 -0
- package/dist/astro-page-header/PageHeader.astro +46 -0
- package/dist/astro-page-header/index.ts +1 -0
- package/dist/astro-stat-grid/StatGrid.astro +23 -19
- package/dist/astro-table/Table.astro +21 -0
- package/dist/astro-table/TableCaption.astro +9 -0
- package/dist/astro-table/TableCell.astro +13 -0
- package/dist/astro-table/TableHead.astro +13 -0
- package/dist/astro-table/TableRow.astro +9 -0
- package/dist/astro-table/index.ts +1 -0
- package/dist/avatar/avatar.styles.ts +7 -2
- package/dist/avatar/avatar.ts +8 -3
- package/dist/avatar/index.ts +1 -0
- package/dist/badge/badge.styles.ts +6 -1
- package/dist/badge/badge.ts +24 -2
- package/dist/button/button.styles.ts +10 -4
- package/dist/button/button.ts +4 -1
- package/dist/callout/callout.ts +12 -3
- package/dist/card/card.styles.ts +10 -1
- package/dist/card/card.ts +14 -0
- package/dist/card/index.ts +3 -0
- package/dist/data-table/data-table.styles.ts +18 -0
- package/dist/data-table/data-table.ts +49 -7
- package/dist/data-table/index.ts +8 -0
- package/dist/diff-viewer/diff-viewer.styles.ts +3 -0
- package/dist/funnel-chart/funnel-chart.styles.ts +16 -0
- package/dist/funnel-chart/funnel-chart.ts +68 -0
- package/dist/funnel-chart/index.ts +10 -0
- package/dist/index.ts +6 -0
- package/dist/input/input.styles.ts +2 -2
- package/dist/line-chart/index.ts +20 -0
- package/dist/line-chart/line-chart.styles.ts +16 -0
- package/dist/line-chart/line-chart.ts +215 -0
- package/dist/logger/faro-engine.ts +2 -2
- package/dist/native-select/index.ts +6 -0
- package/dist/native-select/native-select.styles.ts +30 -0
- package/dist/native-select/native-select.ts +5 -0
- package/dist/page-header/index.ts +15 -0
- package/dist/page-header/page-header.styles.ts +21 -0
- package/dist/page-header/page-header.ts +27 -0
- package/dist/progress-display/progress-display.styles.ts +1 -1
- package/dist/select/index.ts +2 -0
- package/dist/select/select.styles.ts +9 -3
- package/dist/select/select.ts +11 -3
- package/dist/separator/index.ts +2 -0
- package/dist/separator/separator.styles.ts +17 -2
- package/dist/shared/cn.ts +6 -3
- package/dist/slide-viewer/slide-viewer.styles.ts +2 -0
- package/dist/stat-grid/index.ts +9 -0
- package/dist/stat-grid/stat-grid.styles.ts +73 -6
- package/dist/stat-grid/stat-grid.ts +25 -5
- package/dist/sticky-note/sticky-note.styles.ts +2 -0
- package/dist/table/index.ts +16 -0
- package/dist/table/table.styles.ts +72 -0
- package/dist/table/table.ts +24 -0
- package/dist/tabs/index.ts +1 -0
- package/dist/tabs/tabs.ts +29 -0
- package/dist/textarea/textarea.styles.ts +2 -2
- package/package.json +7 -1
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
export interface LineChartSeries {
|
|
2
|
+
/** Stable key for the series. */
|
|
3
|
+
id: string
|
|
4
|
+
/** Legend / tooltip name. */
|
|
5
|
+
name: string
|
|
6
|
+
/**
|
|
7
|
+
* Any CSS colour (a token `hsl(var(--…))` or a data colour). Defaults to the
|
|
8
|
+
* theme's chart palette (`--chart-1` … `--chart-5`) by series position.
|
|
9
|
+
*/
|
|
10
|
+
color?: string
|
|
11
|
+
/** One value per label, in label order. */
|
|
12
|
+
data: number[]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface LineChartPadding {
|
|
16
|
+
top: number
|
|
17
|
+
right: number
|
|
18
|
+
bottom: number
|
|
19
|
+
left: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface LineChartOptions {
|
|
23
|
+
series: LineChartSeries[]
|
|
24
|
+
labels: string[]
|
|
25
|
+
/** viewBox width — the SVG scales to its container's width. */
|
|
26
|
+
width: number
|
|
27
|
+
/** viewBox height. */
|
|
28
|
+
height: number
|
|
29
|
+
padding?: Partial<LineChartPadding>
|
|
30
|
+
/** Target number of gridline intervals on the y-axis (the nice scale may use fewer). */
|
|
31
|
+
tickCount?: number
|
|
32
|
+
/** Headroom multiplier above the largest value. */
|
|
33
|
+
headroom?: number
|
|
34
|
+
/**
|
|
35
|
+
* Show every n-th x label (the last label is always shown). Defaults to
|
|
36
|
+
* thinning so labels are at least ~48 viewBox units apart.
|
|
37
|
+
*/
|
|
38
|
+
labelEvery?: number
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface LineChartTick {
|
|
42
|
+
value: number
|
|
43
|
+
y: number
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface LineChartXLabel {
|
|
47
|
+
index: number
|
|
48
|
+
label: string
|
|
49
|
+
x: number
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface LineChartPath {
|
|
53
|
+
id: string
|
|
54
|
+
/** Resolved stroke colour. */
|
|
55
|
+
color: string
|
|
56
|
+
/** The stroke path. */
|
|
57
|
+
line: string
|
|
58
|
+
/** The line closed down to the zero baseline, for an area fill. */
|
|
59
|
+
area: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface LineChartColumn {
|
|
63
|
+
index: number
|
|
64
|
+
x: number
|
|
65
|
+
width: number
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface LineChartGeometry {
|
|
69
|
+
width: number
|
|
70
|
+
height: number
|
|
71
|
+
padding: LineChartPadding
|
|
72
|
+
/** Top of the shared y-scale. */
|
|
73
|
+
max: number
|
|
74
|
+
ticks: LineChartTick[]
|
|
75
|
+
xLabels: LineChartXLabel[]
|
|
76
|
+
paths: LineChartPath[]
|
|
77
|
+
/** One hover/focus column per category. */
|
|
78
|
+
columns: LineChartColumn[]
|
|
79
|
+
x: (index: number) => number
|
|
80
|
+
y: (value: number) => number
|
|
81
|
+
/** Top and bottom of the plot area, for a crosshair. */
|
|
82
|
+
plotTop: number
|
|
83
|
+
plotBottom: number
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const DEFAULT_LINE_CHART_PADDING: LineChartPadding = {
|
|
87
|
+
top: 14,
|
|
88
|
+
right: 14,
|
|
89
|
+
bottom: 24,
|
|
90
|
+
left: 40,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const DEFAULT_LINE_CHART_WIDTH = 760
|
|
94
|
+
export const DEFAULT_LINE_CHART_HEIGHT = 230
|
|
95
|
+
const DEFAULT_TICK_COUNT = 4
|
|
96
|
+
const DEFAULT_HEADROOM = 1.08
|
|
97
|
+
/** Minimum horizontal room per x label when thinning automatically. */
|
|
98
|
+
const MIN_LABEL_SPACING = 48
|
|
99
|
+
/** Number of `--chart-N` tokens the theme defines. */
|
|
100
|
+
const CHART_TOKEN_COUNT = 5
|
|
101
|
+
|
|
102
|
+
/** The theme chart colour for the series at `index` (wraps around). */
|
|
103
|
+
export function lineChartColor(index: number): string {
|
|
104
|
+
return `hsl(var(--chart-${(index % CHART_TOKEN_COUNT) + 1}))`
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const NICE_STEPS = [1, 2, 5, 10]
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* A "nice" y-scale: a 1-2-5 × 10^k step so there are at most `tickCount`
|
|
111
|
+
* intervals up to `rawMax`, and the max rounded up to a whole step. Tick
|
|
112
|
+
* values are therefore round numbers by construction (0, 2000, 4000, …) —
|
|
113
|
+
* never 5999.9999 — so any tick formatter prints clean labels.
|
|
114
|
+
*/
|
|
115
|
+
export function niceScale(rawMax: number, tickCount: number): { max: number; step: number; ticks: number[] } {
|
|
116
|
+
const intervals = Math.max(1, Math.floor(tickCount))
|
|
117
|
+
if (!(rawMax > 0)) return { max: 1, step: 1 / intervals, ticks: Array.from({ length: intervals + 1 }, (_, i) => i / intervals) }
|
|
118
|
+
const target = rawMax / intervals
|
|
119
|
+
const magnitude = 10 ** Math.floor(Math.log10(target))
|
|
120
|
+
const step = NICE_STEPS.map((m) => m * magnitude).find((candidate) => candidate >= target) ?? 10 * magnitude
|
|
121
|
+
const count = Math.ceil(rawMax / step - 1e-9)
|
|
122
|
+
// Strip binary floating-point noise (e.g. 0.1 * 3) to the step's precision.
|
|
123
|
+
const decimals = Math.max(0, -Math.floor(Math.log10(step)))
|
|
124
|
+
const clean = (value: number) => Number(value.toFixed(decimals))
|
|
125
|
+
const ticks = Array.from({ length: count + 1 }, (_, i) => clean(i * step))
|
|
126
|
+
return { max: ticks[ticks.length - 1], step: clean(step), ticks }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function pathFrom(points: Array<[number, number]>): string {
|
|
130
|
+
return points.map(([px, py], i) => (i ? 'L' : 'M') + px.toFixed(1) + ' ' + py.toFixed(1)).join(' ')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Pure geometry for a responsive multi-series line chart. Every series shares
|
|
135
|
+
* ONE y-scale (zero baseline to the largest value plus headroom), so series are
|
|
136
|
+
* comparable. Coordinates are in viewBox units; the SVG scales to its container.
|
|
137
|
+
*/
|
|
138
|
+
export function computeLineChart(options: LineChartOptions): LineChartGeometry {
|
|
139
|
+
const {
|
|
140
|
+
series,
|
|
141
|
+
labels,
|
|
142
|
+
width,
|
|
143
|
+
height,
|
|
144
|
+
tickCount = DEFAULT_TICK_COUNT,
|
|
145
|
+
headroom = DEFAULT_HEADROOM,
|
|
146
|
+
} = options
|
|
147
|
+
const padding = { ...DEFAULT_LINE_CHART_PADDING, ...options.padding }
|
|
148
|
+
const n = labels.length
|
|
149
|
+
const values = series.flatMap((s) => s.data)
|
|
150
|
+
const peak = values.length ? Math.max(...values) : 0
|
|
151
|
+
const scale = niceScale(peak > 0 ? peak * headroom : 0, tickCount)
|
|
152
|
+
const max = scale.max
|
|
153
|
+
const plotW = width - padding.left - padding.right
|
|
154
|
+
const plotTop = padding.top
|
|
155
|
+
const plotBottom = height - padding.bottom
|
|
156
|
+
const plotH = plotBottom - plotTop
|
|
157
|
+
|
|
158
|
+
const labelEvery =
|
|
159
|
+
options.labelEvery ??
|
|
160
|
+
Math.max(1, Math.ceil(n / Math.max(1, Math.floor(plotW / MIN_LABEL_SPACING))))
|
|
161
|
+
|
|
162
|
+
const x = (index: number) => padding.left + (n > 1 ? (index / (n - 1)) * plotW : plotW / 2)
|
|
163
|
+
const y = (value: number) => plotTop + (1 - value / max) * plotH
|
|
164
|
+
|
|
165
|
+
const ticks: LineChartTick[] = scale.ticks.map((value) => ({ value, y: y(value) }))
|
|
166
|
+
|
|
167
|
+
const xLabels: LineChartXLabel[] = labels
|
|
168
|
+
.map((label, index) => ({ index, label, x: x(index) }))
|
|
169
|
+
.filter(({ index }) => index % labelEvery === 0 || index === n - 1)
|
|
170
|
+
|
|
171
|
+
const paths: LineChartPath[] = series.map((s, si) => {
|
|
172
|
+
const points = s.data.map((v, i): [number, number] => [x(i), y(v)])
|
|
173
|
+
const line = pathFrom(points)
|
|
174
|
+
const first = points.length ? points[0][0] : x(0)
|
|
175
|
+
const last = points.length ? points[points.length - 1][0] : x(0)
|
|
176
|
+
const base = y(0).toFixed(1)
|
|
177
|
+
const area = `${line} L ${last.toFixed(1)} ${base} L ${first.toFixed(1)} ${base} Z`
|
|
178
|
+
return { id: s.id, color: s.color ?? lineChartColor(si), line, area }
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
const colW = n > 1 ? plotW / (n - 1) : plotW
|
|
182
|
+
const columns: LineChartColumn[] = labels.map((_, index) => ({
|
|
183
|
+
index,
|
|
184
|
+
x: x(index) - colW / 2,
|
|
185
|
+
width: colW,
|
|
186
|
+
}))
|
|
187
|
+
|
|
188
|
+
return { width, height, padding, max, ticks, xLabels, paths, columns, x, y, plotTop, plotBottom }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Where a tooltip for `index` sits horizontally, as a percent of the chart width. */
|
|
192
|
+
export function tooltipLeftPercent(geometry: LineChartGeometry, index: number): number {
|
|
193
|
+
return (geometry.x(index) / geometry.width) * 100
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* The category index a keyboard key moves to from `current` (null = nothing
|
|
198
|
+
* active yet). Returns `current` unchanged for keys the chart doesn't handle.
|
|
199
|
+
*/
|
|
200
|
+
export function nextLineChartIndex(current: number | null, key: string, count: number): number | null {
|
|
201
|
+
if (count === 0) return null
|
|
202
|
+
const last = count - 1
|
|
203
|
+
switch (key) {
|
|
204
|
+
case 'ArrowRight':
|
|
205
|
+
return current == null ? 0 : Math.min(current + 1, last)
|
|
206
|
+
case 'ArrowLeft':
|
|
207
|
+
return current == null ? last : Math.max(current - 1, 0)
|
|
208
|
+
case 'Home':
|
|
209
|
+
return 0
|
|
210
|
+
case 'End':
|
|
211
|
+
return last
|
|
212
|
+
default:
|
|
213
|
+
return current
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -74,11 +74,11 @@ async function loadFaroTransport(
|
|
|
74
74
|
// Indirected so bundlers keep these as runtime-optional dynamic imports.
|
|
75
75
|
const sdkName = '@grafana/faro-web-sdk'
|
|
76
76
|
const tracingName = '@grafana/faro-web-tracing'
|
|
77
|
-
const sdk = (await import(/* @vite-ignore */ sdkName)) as {
|
|
77
|
+
const sdk = (await import(/* webpackIgnore: true */ /* @vite-ignore */ sdkName)) as {
|
|
78
78
|
initializeFaro: (cfg: unknown) => unknown
|
|
79
79
|
getWebInstrumentations: () => unknown[]
|
|
80
80
|
}
|
|
81
|
-
const tracing = (await import(/* @vite-ignore */ tracingName)) as {
|
|
81
|
+
const tracing = (await import(/* webpackIgnore: true */ /* @vite-ignore */ tracingName)) as {
|
|
82
82
|
TracingInstrumentation: new () => unknown
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { cva } from '../shared/index.ts'
|
|
2
|
+
|
|
3
|
+
/** Wrapper that positions the chevron over the select. */
|
|
4
|
+
export const nativeSelectWrapperClass = 'relative inline-flex min-w-0 items-center'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The `<select>`: the platform's own listbox (mobile pickers, type-to-select,
|
|
8
|
+
* form submission, `change` events) with the input look. Size-owned utilities
|
|
9
|
+
* live only in the size variants so `size` always wins.
|
|
10
|
+
*/
|
|
11
|
+
export const nativeSelectVariants = cva({
|
|
12
|
+
base:
|
|
13
|
+
'w-full cursor-pointer appearance-none rounded-md border border-input bg-background text-foreground shadow-sm ' +
|
|
14
|
+
'ring-offset-background focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring ' +
|
|
15
|
+
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
16
|
+
variants: {
|
|
17
|
+
size: {
|
|
18
|
+
sm: 'h-8 pl-2.5 pr-8 text-xs',
|
|
19
|
+
default: 'h-9 pl-3 pr-9 text-sm',
|
|
20
|
+
lg: 'h-10 pl-3 pr-10 text-base',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
defaultVariants: {
|
|
24
|
+
size: 'default',
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
/** The chevron: decorative, and never eats the click meant for the select. */
|
|
29
|
+
export const nativeSelectIconClass =
|
|
30
|
+
'pointer-events-none absolute right-2.5 h-4 w-4 shrink-0 text-muted-foreground'
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createPageHeader,
|
|
3
|
+
type PageHeaderTitleLevel,
|
|
4
|
+
type PageHeaderProps,
|
|
5
|
+
type PageHeaderAPI,
|
|
6
|
+
} from './page-header.js'
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
pageHeaderClass,
|
|
10
|
+
pageHeaderTextClass,
|
|
11
|
+
pageHeaderKickerClass,
|
|
12
|
+
pageHeaderTitleClass,
|
|
13
|
+
pageHeaderDescriptionClass,
|
|
14
|
+
pageHeaderActionsClass,
|
|
15
|
+
} from './page-header.styles.js'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Root: text cluster on the leading edge, actions on the trailing edge; wraps
|
|
3
|
+
* on narrow screens. Carries no outer margin — the page layout spaces it.
|
|
4
|
+
*/
|
|
5
|
+
export const pageHeaderClass = 'flex flex-wrap items-start justify-between gap-4'
|
|
6
|
+
|
|
7
|
+
/** Text cluster (kicker, title, description). */
|
|
8
|
+
export const pageHeaderTextClass = 'flex min-w-0 flex-1 flex-col gap-1'
|
|
9
|
+
|
|
10
|
+
/** Small uppercase eyebrow above the title. */
|
|
11
|
+
export const pageHeaderKickerClass =
|
|
12
|
+
'break-words text-[11px] uppercase tracking-wider text-muted-foreground'
|
|
13
|
+
|
|
14
|
+
/** Title — app-sized, not a marketing display size. */
|
|
15
|
+
export const pageHeaderTitleClass = 'break-words text-xl font-semibold text-foreground'
|
|
16
|
+
|
|
17
|
+
/** Supporting copy under the title, capped at a readable measure. */
|
|
18
|
+
export const pageHeaderDescriptionClass = 'max-w-prose text-sm text-muted-foreground'
|
|
19
|
+
|
|
20
|
+
/** Trailing actions row. */
|
|
21
|
+
export const pageHeaderActionsClass = 'flex flex-none flex-wrap items-center gap-2'
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Heading element used for the page title. */
|
|
2
|
+
export type PageHeaderTitleLevel = 'h1' | 'h2' | 'h3'
|
|
3
|
+
|
|
4
|
+
export interface PageHeaderProps {
|
|
5
|
+
/** Whether an actions slot will be rendered. */
|
|
6
|
+
hasActions?: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface PageHeaderAPI {
|
|
10
|
+
/** Data attributes for styling hooks. */
|
|
11
|
+
dataAttributes: Record<string, string>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Build the framework-agnostic data attributes for a PageHeader — the heading
|
|
16
|
+
* cluster at the top of an application page (kicker, title, description and a
|
|
17
|
+
* trailing actions row). Unlike SectionHead (a centred marketing heading) it is
|
|
18
|
+
* sized for dense app chrome and never centres.
|
|
19
|
+
*/
|
|
20
|
+
export function createPageHeader(props: PageHeaderProps = {}): PageHeaderAPI {
|
|
21
|
+
return {
|
|
22
|
+
dataAttributes: {
|
|
23
|
+
'data-slot': 'page-header',
|
|
24
|
+
'data-has-actions': props.hasActions ? 'true' : 'false',
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
}
|
package/dist/select/index.ts
CHANGED
|
@@ -12,7 +12,7 @@ export const selectTokens: TokenContract = {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export const selectTriggerVariants = cva({
|
|
15
|
-
base: 'flex
|
|
15
|
+
base: 'flex w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
|
|
16
16
|
variants: {
|
|
17
17
|
size: {
|
|
18
18
|
sm: 'h-8 text-xs',
|
|
@@ -26,10 +26,10 @@ export const selectTriggerVariants = cva({
|
|
|
26
26
|
})
|
|
27
27
|
|
|
28
28
|
export const selectContentVariants = cva({
|
|
29
|
-
base: '
|
|
29
|
+
base: 'absolute left-0 top-full z-50 mt-1 max-h-96 w-full min-w-[8rem] overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95',
|
|
30
30
|
variants: {
|
|
31
31
|
position: {
|
|
32
|
-
popper: '
|
|
32
|
+
popper: '',
|
|
33
33
|
'item-aligned': '',
|
|
34
34
|
},
|
|
35
35
|
},
|
|
@@ -50,3 +50,9 @@ export const selectItemVariants = cva({
|
|
|
50
50
|
selected: 'false',
|
|
51
51
|
},
|
|
52
52
|
})
|
|
53
|
+
|
|
54
|
+
/** Positioning context: the listbox floats under the trigger. */
|
|
55
|
+
export const selectRootClass = 'relative'
|
|
56
|
+
|
|
57
|
+
/** The selected label inside the trigger; truncates, dims the placeholder. */
|
|
58
|
+
export const selectValueClass = 'truncate text-left data-[placeholder]:text-muted-foreground'
|
package/dist/select/select.ts
CHANGED
|
@@ -14,6 +14,12 @@ export interface SelectProps {
|
|
|
14
14
|
disabled?: boolean
|
|
15
15
|
open?: boolean
|
|
16
16
|
name?: string
|
|
17
|
+
/**
|
|
18
|
+
* Element ids to link trigger and listbox. Adapters should pass ids that
|
|
19
|
+
* are stable across renders (e.g. React `useId`); when omitted, fresh ids
|
|
20
|
+
* are generated on every call.
|
|
21
|
+
*/
|
|
22
|
+
ids?: { trigger: string; content: string }
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
export interface SelectAPI {
|
|
@@ -63,14 +69,16 @@ export function createSelect(props: SelectProps = {}): SelectAPI {
|
|
|
63
69
|
const isInteractive = !disabled
|
|
64
70
|
const selectedOption = options.find((o) => o.value === value)
|
|
65
71
|
|
|
66
|
-
const triggerId = generateId('select-trigger')
|
|
67
|
-
const contentId = generateId('select-content')
|
|
72
|
+
const triggerId = props.ids?.trigger ?? generateId('select-trigger')
|
|
73
|
+
const contentId = props.ids?.content ?? generateId('select-content')
|
|
68
74
|
|
|
75
|
+
// No aria-labelledby on the trigger: pointing a combobox at itself would
|
|
76
|
+
// override a caller's aria-label / <label for> and leave the accessible
|
|
77
|
+
// name as the displayed value.
|
|
69
78
|
const triggerAriaProps: Partial<AccessibilityProps> = {
|
|
70
79
|
role: 'combobox',
|
|
71
80
|
'aria-expanded': open,
|
|
72
81
|
'aria-controls': contentId,
|
|
73
|
-
'aria-labelledby': triggerId,
|
|
74
82
|
}
|
|
75
83
|
if (disabled) {
|
|
76
84
|
triggerAriaProps['aria-disabled'] = true
|
package/dist/separator/index.ts
CHANGED
|
@@ -12,8 +12,16 @@ export const separatorVariants = cva({
|
|
|
12
12
|
base: '',
|
|
13
13
|
variants: {
|
|
14
14
|
orientation: {
|
|
15
|
-
horizontal: 'h-px w-full
|
|
16
|
-
vertical: 'w-px h-full
|
|
15
|
+
horizontal: 'h-px w-full',
|
|
16
|
+
vertical: 'w-px h-full',
|
|
17
|
+
},
|
|
18
|
+
// Line color (issue #485). `default` is the standard border token;
|
|
19
|
+
// `subtle` uses the hairline `border-subtle` token for a lighter rule.
|
|
20
|
+
// Keeping the color here (not in `orientation`) keeps every existing call
|
|
21
|
+
// pixel-identical while adding the subtle option.
|
|
22
|
+
tone: {
|
|
23
|
+
default: 'bg-border',
|
|
24
|
+
subtle: 'bg-border-subtle',
|
|
17
25
|
},
|
|
18
26
|
labeled: {
|
|
19
27
|
true: 'flex items-center',
|
|
@@ -22,13 +30,20 @@ export const separatorVariants = cva({
|
|
|
22
30
|
},
|
|
23
31
|
defaultVariants: {
|
|
24
32
|
orientation: 'horizontal',
|
|
33
|
+
tone: 'default',
|
|
25
34
|
labeled: 'false',
|
|
26
35
|
},
|
|
27
36
|
})
|
|
28
37
|
|
|
38
|
+
/** Line tone for the separator rule and labeled-divider flanks. */
|
|
39
|
+
export type SeparatorTone = 'default' | 'subtle'
|
|
40
|
+
|
|
29
41
|
/** Class for each flanking line in the labeled-divider variant. */
|
|
30
42
|
export const separatorLineClass = 'h-px flex-1 bg-border'
|
|
31
43
|
|
|
44
|
+
/** Hairline variant of {@link separatorLineClass} (issue #485). */
|
|
45
|
+
export const separatorSubtleLineClass = 'h-px flex-1 bg-border-subtle'
|
|
46
|
+
|
|
32
47
|
/** Class for the centered label in the labeled-divider variant. */
|
|
33
48
|
export const separatorLabelClass =
|
|
34
49
|
'px-3 text-xs uppercase tracking-wide text-muted-foreground'
|
package/dist/shared/cn.ts
CHANGED
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
* Handles conditional classes, arrays, and falsy values.
|
|
4
4
|
* No external dependencies (no clsx, no tailwind-merge).
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* It does NOT resolve Tailwind conflicts: `cn('p-2', 'p-4')` keeps both and
|
|
7
|
+
* the stylesheet order decides. Components therefore keep size-owned
|
|
8
|
+
* utilities (height, text size, padding) out of their cva `base` and only in
|
|
9
|
+
* the matching variant, so a variant prop always wins; a consumer `className`
|
|
10
|
+
* that must beat a component utility can use Tailwind's `!` important
|
|
11
|
+
* modifier (e.g. `!h-7`).
|
|
9
12
|
*/
|
|
10
13
|
|
|
11
14
|
type ClassValue = string | number | boolean | undefined | null | ClassValue[]
|
|
@@ -45,6 +45,8 @@ export { slideViewerProgressBarVariants as progressBarVariants }
|
|
|
45
45
|
export const slideTypeBadgeVariants = cva({
|
|
46
46
|
base: 'inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium',
|
|
47
47
|
variants: {
|
|
48
|
+
// Slide-type accents are a content-category palette (lesson/quiz/exercise…),
|
|
49
|
+
// not status tones — deliberately not routed through the theme tokens.
|
|
48
50
|
type: {
|
|
49
51
|
lesson: 'bg-blue-100 text-blue-800',
|
|
50
52
|
quiz: 'bg-purple-100 text-purple-800',
|
package/dist/stat-grid/index.ts
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
export {
|
|
2
2
|
createStatGrid,
|
|
3
3
|
statColumns,
|
|
4
|
+
statColumnsKey,
|
|
4
5
|
type StatItem,
|
|
6
|
+
type StatTone,
|
|
7
|
+
type StatGridVariant,
|
|
8
|
+
type StatGridLayout,
|
|
9
|
+
type StatGridColumns,
|
|
5
10
|
type StatGridAPI,
|
|
6
11
|
} from './stat-grid.js'
|
|
7
12
|
|
|
8
13
|
export {
|
|
9
14
|
statGridVariants,
|
|
15
|
+
statItemVariants,
|
|
16
|
+
statValueVariants,
|
|
17
|
+
statLabelVariants,
|
|
18
|
+
statDescriptionClass,
|
|
10
19
|
statItemClass,
|
|
11
20
|
statValueClass,
|
|
12
21
|
statLabelClass,
|
|
@@ -1,25 +1,92 @@
|
|
|
1
1
|
import { cva } from '../shared/index.ts'
|
|
2
2
|
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Grid container. Column counts are responsive (one column on a phone, more
|
|
5
|
+
* as the width allows) — never a fixed inline template that cannot reflow.
|
|
6
|
+
* `auto` fits as many ~12rem items per row as the width allows.
|
|
7
|
+
*/
|
|
4
8
|
export const statGridVariants = cva({
|
|
5
|
-
base: 'grid
|
|
9
|
+
base: 'grid',
|
|
6
10
|
variants: {
|
|
7
11
|
columns: {
|
|
8
12
|
'1': 'grid-cols-1',
|
|
9
|
-
'2': 'grid-cols-2',
|
|
10
|
-
'3': 'grid-cols-3',
|
|
13
|
+
'2': 'grid-cols-1 sm:grid-cols-2',
|
|
14
|
+
'3': 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3',
|
|
15
|
+
'4': 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4',
|
|
16
|
+
auto: 'grid-cols-[repeat(auto-fill,minmax(12rem,1fr))]',
|
|
17
|
+
},
|
|
18
|
+
variant: {
|
|
19
|
+
plain: 'gap-8',
|
|
20
|
+
card: 'gap-3',
|
|
11
21
|
},
|
|
12
22
|
},
|
|
13
23
|
defaultVariants: {
|
|
14
24
|
columns: '3',
|
|
25
|
+
variant: 'plain',
|
|
15
26
|
},
|
|
16
27
|
})
|
|
17
28
|
|
|
18
29
|
/** Individual stat item container (role="listitem"). */
|
|
30
|
+
export const statItemVariants = cva({
|
|
31
|
+
base: 'flex min-w-0 flex-col',
|
|
32
|
+
variants: {
|
|
33
|
+
variant: {
|
|
34
|
+
plain: '',
|
|
35
|
+
card: 'gap-1 rounded-lg border border-border bg-card p-4 text-card-foreground',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
defaultVariants: {
|
|
39
|
+
variant: 'plain',
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
/** @deprecated Use `statItemVariants()`; kept for existing imports. */
|
|
19
44
|
export const statItemClass = 'flex flex-col'
|
|
20
45
|
|
|
21
|
-
/** The
|
|
46
|
+
/** The value of a stat, by variant and tone. */
|
|
47
|
+
export const statValueVariants = cva({
|
|
48
|
+
base: 'font-bold tabular-nums',
|
|
49
|
+
variants: {
|
|
50
|
+
variant: {
|
|
51
|
+
plain: 'text-xl sm:text-2xl',
|
|
52
|
+
card: 'text-2xl leading-tight',
|
|
53
|
+
},
|
|
54
|
+
tone: {
|
|
55
|
+
default: '',
|
|
56
|
+
positive: 'text-success',
|
|
57
|
+
negative: 'text-destructive',
|
|
58
|
+
caution: 'text-warning',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
compoundVariants: [
|
|
62
|
+
{ variant: 'plain', tone: 'default', class: 'text-primary' },
|
|
63
|
+
{ variant: 'card', tone: 'default', class: 'text-foreground' },
|
|
64
|
+
],
|
|
65
|
+
defaultVariants: {
|
|
66
|
+
variant: 'plain',
|
|
67
|
+
tone: 'default',
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
/** @deprecated Use `statValueVariants()`; kept for existing imports. */
|
|
22
72
|
export const statValueClass = 'text-xl sm:text-2xl font-bold text-primary'
|
|
23
73
|
|
|
24
|
-
/** The descriptive label
|
|
74
|
+
/** The descriptive label. Above the value it reads as a small caption. */
|
|
75
|
+
export const statLabelVariants = cva({
|
|
76
|
+
base: 'text-muted-foreground',
|
|
77
|
+
variants: {
|
|
78
|
+
layout: {
|
|
79
|
+
'value-first': 'mt-1 text-sm leading-snug',
|
|
80
|
+
'label-first': 'text-xs uppercase tracking-wide',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
defaultVariants: {
|
|
84
|
+
layout: 'value-first',
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
/** @deprecated Use `statLabelVariants()`; kept for existing imports. */
|
|
25
89
|
export const statLabelClass = 'mt-1 text-sm text-muted-foreground leading-snug'
|
|
90
|
+
|
|
91
|
+
/** Optional line under a stat explaining how it was computed. */
|
|
92
|
+
export const statDescriptionClass = 'text-xs leading-snug text-muted-foreground'
|
|
@@ -1,9 +1,24 @@
|
|
|
1
|
+
/** Semantic colour of a stat's value. */
|
|
2
|
+
export type StatTone = 'default' | 'positive' | 'negative' | 'caution'
|
|
3
|
+
/** `plain` = marketing callouts; `card` = bordered dashboard KPI cards. */
|
|
4
|
+
export type StatGridVariant = 'plain' | 'card'
|
|
5
|
+
/** Whether the label sits under the value (callout) or above it (KPI). */
|
|
6
|
+
export type StatGridLayout = 'value-first' | 'label-first'
|
|
7
|
+
/** Column count: a fixed maximum that reflows down on narrow screens, or `auto`. */
|
|
8
|
+
export type StatGridColumns = 1 | 2 | 3 | 4 | 'auto'
|
|
9
|
+
|
|
1
10
|
/** A single stat callout displayed in the grid. */
|
|
2
11
|
export type StatItem = {
|
|
3
12
|
/** The primary value displayed prominently (e.g. "10k+", "$4.2M"). */
|
|
4
13
|
value: string
|
|
5
|
-
/** The descriptive label
|
|
14
|
+
/** The descriptive label. */
|
|
6
15
|
label: string
|
|
16
|
+
/** Stable key; defaults to the index. */
|
|
17
|
+
id?: string
|
|
18
|
+
/** Optional line explaining the figure (how it was computed, a delta). */
|
|
19
|
+
description?: string
|
|
20
|
+
/** Colour of the value. Defaults to `default`. */
|
|
21
|
+
tone?: StatTone
|
|
7
22
|
}
|
|
8
23
|
|
|
9
24
|
export interface StatGridAPI {
|
|
@@ -20,11 +35,16 @@ export interface StatGridAPI {
|
|
|
20
35
|
* - 2 items → 2 columns
|
|
21
36
|
* - 3 or more items → capped at `max` (default 3)
|
|
22
37
|
*/
|
|
23
|
-
export function statColumns(count: number, max = 3):
|
|
24
|
-
if (count <=
|
|
25
|
-
if (count === 1) return 1
|
|
38
|
+
export function statColumns(count: number, max = 3): 1 | 2 | 3 | 4 {
|
|
39
|
+
if (count <= 1) return 1
|
|
26
40
|
if (count === 2) return 2
|
|
27
|
-
return Math.min(count, max)
|
|
41
|
+
return Math.min(count, max, 4) as 3 | 4
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The cva key for a column setting (clamped to the supported 1–4). */
|
|
45
|
+
export function statColumnsKey(columns: StatGridColumns): '1' | '2' | '3' | '4' | 'auto' {
|
|
46
|
+
if (columns === 'auto') return 'auto'
|
|
47
|
+
return String(Math.min(Math.max(Math.round(columns), 1), 4)) as '1' | '2' | '3' | '4'
|
|
28
48
|
}
|
|
29
49
|
|
|
30
50
|
/**
|