@adea-ai/ui 0.100.0 → 0.101.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/components/composites/about-dialog/about-dialog.d.ts.map +1 -1
- package/dist/components/composites/about-dialog/about-dialog.js +2 -1
- package/dist/components/composites/about-dialog/about-dialog.js.map +1 -1
- package/dist/components/composites/account-menu/account-menu.d.ts +7 -0
- package/dist/components/composites/account-menu/account-menu.d.ts.map +1 -1
- package/dist/components/composites/account-menu/account-menu.js +116 -89
- package/dist/components/composites/account-menu/account-menu.js.map +1 -1
- package/dist/components/composites/help-center/help-center.d.ts.map +1 -1
- package/dist/components/composites/help-center/help-center.js +26 -21
- package/dist/components/composites/help-center/help-center.js.map +1 -1
- package/dist/components/composites/settings/settings.d.ts.map +1 -1
- package/dist/components/composites/settings/settings.js +43 -36
- package/dist/components/composites/settings/settings.js.map +1 -1
- package/dist/components/composites/stat/stat.d.ts.map +1 -1
- package/dist/components/composites/stat/stat.js +46 -35
- package/dist/components/composites/stat/stat.js.map +1 -1
- package/dist/components/composites/update-dialog/update-dialog.d.ts.map +1 -1
- package/dist/components/composites/update-dialog/update-dialog.js +73 -58
- package/dist/components/composites/update-dialog/update-dialog.js.map +1 -1
- package/dist/components/layout/page/page.d.ts.map +1 -1
- package/dist/components/layout/page/page.js +19 -13
- package/dist/components/layout/page/page.js.map +1 -1
- package/dist/components/theme/appearance-panel.d.ts.map +1 -1
- package/dist/components/theme/appearance-panel.js +37 -18
- package/dist/components/theme/appearance-panel.js.map +1 -1
- package/dist/components/ui/alert-dialog/alert-dialog.d.ts.map +1 -1
- package/dist/components/ui/alert-dialog/alert-dialog.js +5 -1
- package/dist/components/ui/alert-dialog/alert-dialog.js.map +1 -1
- package/dist/components/ui/card/card.d.ts.map +1 -1
- package/dist/components/ui/card/card.js +5 -1
- package/dist/components/ui/card/card.js.map +1 -1
- package/dist/components/ui/chart/chart-options.d.ts +3 -2
- package/dist/components/ui/chart/chart-options.d.ts.map +1 -1
- package/dist/components/ui/chart/chart-options.js +30 -9
- package/dist/components/ui/chart/chart-options.js.map +1 -1
- package/dist/components/ui/chart/chart.d.ts +4 -9
- package/dist/components/ui/chart/chart.d.ts.map +1 -1
- package/dist/components/ui/chart/chart.js +65 -10
- package/dist/components/ui/chart/chart.js.map +1 -1
- package/dist/components/ui/dialog/dialog.d.ts.map +1 -1
- package/dist/components/ui/dialog/dialog.js +5 -1
- package/dist/components/ui/dialog/dialog.js.map +1 -1
- package/dist/components/ui/drawer/drawer.d.ts.map +1 -1
- package/dist/components/ui/drawer/drawer.js +5 -1
- package/dist/components/ui/drawer/drawer.js.map +1 -1
- package/dist/components/ui/field/field.d.ts.map +1 -1
- package/dist/components/ui/field/field.js +2 -1
- package/dist/components/ui/field/field.js.map +1 -1
- package/dist/components/ui/item/item.d.ts +1 -1
- package/dist/components/ui/sheet/sheet.d.ts.map +1 -1
- package/dist/components/ui/sheet/sheet.js +5 -1
- package/dist/components/ui/sheet/sheet.js.map +1 -1
- package/dist/components/ui/typography/index.d.ts +3 -0
- package/dist/components/ui/typography/index.d.ts.map +1 -0
- package/dist/components/ui/typography/index.js +2 -0
- package/dist/components/ui/typography/typography.d.ts +84 -0
- package/dist/components/ui/typography/typography.d.ts.map +1 -0
- package/dist/components/ui/typography/typography.js +179 -0
- package/dist/components/ui/typography/typography.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/r/account-menu.json +1 -0
- package/dist/r/alert-dialog.json +1 -0
- package/dist/r/card.json +1 -0
- package/dist/r/dialog.json +1 -0
- package/dist/r/drawer.json +1 -0
- package/dist/r/field.json +1 -0
- package/dist/r/help-center.json +1 -0
- package/dist/r/page.json +1 -0
- package/dist/r/registry.json +37 -0
- package/dist/r/settings.json +1 -0
- package/dist/r/sheet.json +1 -0
- package/dist/r/src/components/composites/about-dialog/about-dialog.tsx +3 -2
- package/dist/r/src/components/composites/account-menu/account-menu.tsx +105 -66
- package/dist/r/src/components/composites/help-center/help-center.tsx +2 -1
- package/dist/r/src/components/composites/settings/settings.tsx +4 -1
- package/dist/r/src/components/composites/stat/stat.tsx +7 -2
- package/dist/r/src/components/composites/update-dialog/update-dialog.tsx +36 -24
- package/dist/r/src/components/layout/page/page.tsx +3 -2
- package/dist/r/src/components/theme/appearance-panel.tsx +13 -4
- package/dist/r/src/components/ui/alert-dialog/alert-dialog.tsx +2 -1
- package/dist/r/src/components/ui/card/card.tsx +2 -1
- package/dist/r/src/components/ui/chart/chart-options.ts +35 -7
- package/dist/r/src/components/ui/chart/chart.tsx +65 -12
- package/dist/r/src/components/ui/dialog/dialog.tsx +2 -1
- package/dist/r/src/components/ui/drawer/drawer.tsx +2 -1
- package/dist/r/src/components/ui/field/field.tsx +2 -3
- package/dist/r/src/components/ui/sheet/sheet.tsx +5 -1
- package/dist/r/src/components/ui/typography/index.ts +9 -0
- package/dist/r/src/components/ui/typography/typography.tsx +205 -0
- package/dist/r/stat.json +1 -0
- package/dist/r/theming.json +1 -0
- package/dist/r/typography.json +24 -0
- package/dist/r/update-dialog.json +1 -0
- package/package.json +1 -1
- package/registry.json +37 -0
- package/src/components/composites/about-dialog/about-dialog.tsx +3 -2
- package/src/components/composites/account-menu/account-menu.tsx +105 -66
- package/src/components/composites/help-center/help-center.tsx +2 -1
- package/src/components/composites/settings/settings.tsx +4 -1
- package/src/components/composites/stat/stat.tsx +7 -2
- package/src/components/composites/update-dialog/update-dialog.tsx +36 -24
- package/src/components/layout/page/page.tsx +3 -2
- package/src/components/theme/appearance-panel.tsx +13 -4
- package/src/components/ui/alert-dialog/alert-dialog.tsx +2 -1
- package/src/components/ui/card/card.tsx +2 -1
- package/src/components/ui/chart/chart-options.ts +35 -7
- package/src/components/ui/chart/chart.tsx +65 -12
- package/src/components/ui/dialog/dialog.tsx +2 -1
- package/src/components/ui/drawer/drawer.tsx +2 -1
- package/src/components/ui/field/field.tsx +2 -3
- package/src/components/ui/sheet/sheet.tsx +5 -1
- package/src/components/ui/typography/index.ts +9 -0
- package/src/components/ui/typography/typography.tsx +205 -0
- package/src/index.ts +1 -0
|
@@ -45,7 +45,30 @@ const furniture = {
|
|
|
45
45
|
*/
|
|
46
46
|
export type ChartScaleShape = 'cartesian' | 'radial' | 'none'
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
/**
|
|
49
|
+
* The chart's type: the UI face from the font axis and a rung of the type scale,
|
|
50
|
+
* declared as tokens like the colours.
|
|
51
|
+
*
|
|
52
|
+
* Chart.js does not inherit the page's font — it paints in its own default
|
|
53
|
+
* family — so every text a chart draws names `--font-sans` explicitly, and the
|
|
54
|
+
* chart follows `data-font` the same way it follows `.dark`. Everything is
|
|
55
|
+
* `--text-2xs`, the rung `ChartLegend` and the frame's metadata already use, so
|
|
56
|
+
* a canvas legend and an HTML one beside it are the same size.
|
|
57
|
+
*
|
|
58
|
+
* Chart.js types `size` as a number of pixels. The token is a `rem` length, so
|
|
59
|
+
* it is written as `var(--text-2xs)` here and the component resolves it to
|
|
60
|
+
* pixels with the colours (see `resolveCssVariables` and `chart.tsx`); the cast
|
|
61
|
+
* is that declaration, not a number.
|
|
62
|
+
*/
|
|
63
|
+
function chartFont(weight?: number) {
|
|
64
|
+
return {
|
|
65
|
+
family: 'var(--font-sans)',
|
|
66
|
+
size: 'var(--text-2xs)' as unknown as number,
|
|
67
|
+
...(weight === undefined ? {} : { weight }),
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const tickFont = chartFont()
|
|
49
72
|
|
|
50
73
|
function scalesFor(shape: ChartScaleShape): ChartOptions['scales'] {
|
|
51
74
|
if (shape === 'cartesian') {
|
|
@@ -140,7 +163,7 @@ export function chartOptions(
|
|
|
140
163
|
boxHeight: 8,
|
|
141
164
|
usePointStyle: true,
|
|
142
165
|
pointStyle: 'circle',
|
|
143
|
-
font:
|
|
166
|
+
font: chartFont(),
|
|
144
167
|
},
|
|
145
168
|
},
|
|
146
169
|
tooltip: {
|
|
@@ -155,8 +178,9 @@ export function chartOptions(
|
|
|
155
178
|
boxWidth: 8,
|
|
156
179
|
boxHeight: 8,
|
|
157
180
|
usePointStyle: true,
|
|
158
|
-
titleFont:
|
|
159
|
-
bodyFont:
|
|
181
|
+
titleFont: chartFont(600),
|
|
182
|
+
bodyFont: chartFont(),
|
|
183
|
+
footerFont: chartFont(),
|
|
160
184
|
},
|
|
161
185
|
},
|
|
162
186
|
scales: scalesFor(shape),
|
|
@@ -169,17 +193,21 @@ const cssVariable = /^\s*var\(\s*(--[\w-]+)\s*(?:,\s*(.*?))?\s*\)\s*$/
|
|
|
169
193
|
|
|
170
194
|
/**
|
|
171
195
|
* Replace every `var(--token)` string in `value` — options, datasets, nested
|
|
172
|
-
* arrays — with what `read` returns for the token
|
|
196
|
+
* arrays — with what `read` returns for the token, which may be a number (a
|
|
197
|
+
* length token read as pixels). A token `read` cannot
|
|
173
198
|
* resolve falls back to the `var()`'s own fallback, then to the string as
|
|
174
199
|
* written. Functions and class instances pass through untouched, so scriptable
|
|
175
200
|
* options and gradients keep working.
|
|
176
201
|
*/
|
|
177
|
-
export function resolveCssVariables<T>(
|
|
202
|
+
export function resolveCssVariables<T>(
|
|
203
|
+
value: T,
|
|
204
|
+
read: (name: string) => string | number | undefined
|
|
205
|
+
): T {
|
|
178
206
|
if (typeof value === 'string') {
|
|
179
207
|
const match = cssVariable.exec(value)
|
|
180
208
|
if (!match) return value
|
|
181
209
|
const resolved = read(match[1]!)
|
|
182
|
-
if (resolved) return resolved as T
|
|
210
|
+
if (resolved !== undefined && resolved !== '') return resolved as T
|
|
183
211
|
return (match[2] ? resolveCssVariables(match[2], read) : value) as T
|
|
184
212
|
}
|
|
185
213
|
if (Array.isArray(value)) {
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
RadialLinearScale,
|
|
16
16
|
Tooltip as ChartTooltip,
|
|
17
17
|
} from 'chart.js'
|
|
18
|
-
import type { ChartOptions, ChartType } from 'chart.js'
|
|
18
|
+
import type { ChartOptions, ChartType, Plugin } from 'chart.js'
|
|
19
19
|
import { cn } from '#lib/utils'
|
|
20
20
|
import {
|
|
21
21
|
chartOptions,
|
|
@@ -62,7 +62,9 @@ import {
|
|
|
62
62
|
* A canvas cannot read a custom property, so the options and datasets carry
|
|
63
63
|
* `var(--token)` strings and each chart resolves them against its own element
|
|
64
64
|
* when it renders, and again whenever the root's attributes change — the
|
|
65
|
-
* `.dark` class, `data-accent`, an inline override.
|
|
65
|
+
* `.dark` class, `data-accent`, `data-font`, an inline override. The type is
|
|
66
|
+
* resolved the same way: `--font-sans` for the family and a type-scale rung for
|
|
67
|
+
* the size, so a chart follows the font axis too. See `createThemeResolver`.
|
|
66
68
|
*/
|
|
67
69
|
|
|
68
70
|
/**
|
|
@@ -119,6 +121,23 @@ function toCanvasColor(value: string): string {
|
|
|
119
121
|
return `rgba(${r}, ${g}, ${b}, ${Math.round(((a ?? 255) / 255) * 1000) / 1000})`
|
|
120
122
|
}
|
|
121
123
|
|
|
124
|
+
const length = /^(-?\d*\.?\d+)(px|rem)$/
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* A token's computed value as a canvas option: a colour as an sRGB colour, a
|
|
128
|
+
* `px`/`rem` length (a type-scale rung) as the number of pixels Chart.js wants,
|
|
129
|
+
* and anything else — a font stack — as written.
|
|
130
|
+
*/
|
|
131
|
+
function toCanvasValue(raw: string): string | number {
|
|
132
|
+
const match = length.exec(raw)
|
|
133
|
+
if (match) {
|
|
134
|
+
const value = Number(match[1])
|
|
135
|
+
if (match[2] === 'px') return value
|
|
136
|
+
return value * (Number.parseFloat(getComputedStyle(document.documentElement).fontSize) || 16)
|
|
137
|
+
}
|
|
138
|
+
return toCanvasColor(raw)
|
|
139
|
+
}
|
|
140
|
+
|
|
122
141
|
/**
|
|
123
142
|
* Resolves `var(--token)` strings against a chart's element, and re-resolves
|
|
124
143
|
* them when the theme changes.
|
|
@@ -127,16 +146,25 @@ function toCanvasColor(value: string): string {
|
|
|
127
146
|
* inline tokens takes those. The observer watches every attribute on `<html>`,
|
|
128
147
|
* because that is where both applications switch appearance, accent and theme
|
|
129
148
|
* (`.dark`, `data-accent`, `data-theme`, inline variables); a theme axis added
|
|
130
|
-
* later is covered without touching this. It
|
|
149
|
+
* later is covered without touching this. It also re-resolves when a web font
|
|
150
|
+
* finishes loading: a canvas draws text once, so a chart rendered before the
|
|
151
|
+
* face selected by `data-font` arrived would otherwise keep the fallback face
|
|
152
|
+
* until something else redrew it. Both are released with the chart.
|
|
131
153
|
*/
|
|
132
154
|
function createThemeResolver() {
|
|
133
155
|
let element: HTMLElement | undefined
|
|
134
156
|
const [revision, invalidate] = createSignal(undefined, { equals: false })
|
|
157
|
+
const onFontsLoaded = () => invalidate()
|
|
135
158
|
|
|
136
159
|
onMount(() => {
|
|
137
160
|
const observer = new MutationObserver(() => invalidate())
|
|
138
161
|
observer.observe(document.documentElement, { attributes: true })
|
|
139
|
-
|
|
162
|
+
const fonts = typeof document.fonts === 'undefined' ? undefined : document.fonts
|
|
163
|
+
fonts?.addEventListener('loadingdone', onFontsLoaded)
|
|
164
|
+
onCleanup(() => {
|
|
165
|
+
observer.disconnect()
|
|
166
|
+
fonts?.removeEventListener('loadingdone', onFontsLoaded)
|
|
167
|
+
})
|
|
140
168
|
})
|
|
141
169
|
|
|
142
170
|
return {
|
|
@@ -147,11 +175,11 @@ function createThemeResolver() {
|
|
|
147
175
|
revision()
|
|
148
176
|
if (!element || typeof getComputedStyle === 'undefined') return value
|
|
149
177
|
const style = getComputedStyle(element)
|
|
150
|
-
const resolved = new Map<string, string | undefined>()
|
|
178
|
+
const resolved = new Map<string, string | number | undefined>()
|
|
151
179
|
return resolveCssVariables(value, (name) => {
|
|
152
180
|
if (!resolved.has(name)) {
|
|
153
181
|
const raw = style.getPropertyValue(name).trim()
|
|
154
|
-
resolved.set(name, raw ?
|
|
182
|
+
resolved.set(name, raw ? toCanvasValue(raw) : undefined)
|
|
155
183
|
}
|
|
156
184
|
return resolved.get(name)
|
|
157
185
|
})
|
|
@@ -343,13 +371,36 @@ export function RadarChart(props: SeriesChartProps) {
|
|
|
343
371
|
}
|
|
344
372
|
|
|
345
373
|
/**
|
|
346
|
-
*
|
|
374
|
+
* Puts the themed radial scale back on a chart `solid-chartjs` has just created.
|
|
347
375
|
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
376
|
+
* `solid-chartjs` deletes `options.scales.r` for every chart type but radar when
|
|
377
|
+
* it first creates a chart. Today that delete lands on a throwaway copy — Solid
|
|
378
|
+
* compiles `options={…}` to a getter, so its next read rebuilds the object with
|
|
379
|
+
* `r` intact — but that is an accident of how the prop is passed: a memoised or
|
|
380
|
+
* static options object would lose the scale, and the polar area would fall back
|
|
381
|
+
* to Chart.js's defaults (grey ticks on a translucent white box, which is a white
|
|
382
|
+
* box on a dark card). Restoring it from this component's own options makes the
|
|
383
|
+
* themed scale independent of both. Later option updates (every theme change)
|
|
384
|
+
* pass `r` through untouched; only creation drops it.
|
|
385
|
+
*
|
|
386
|
+
* `afterInit` runs inside Chart.js's constructor, after the options are read and
|
|
387
|
+
* before the first update builds the scales and draws, so the themed scale is in
|
|
388
|
+
* place for the first frame — no default-styled frame, and no extra update. The
|
|
389
|
+
* plugin is per instance, passed through the chart's `plugins` prop, so nothing
|
|
390
|
+
* is registered globally.
|
|
352
391
|
*/
|
|
392
|
+
function restoreRadialScale(options: () => ChartOptions): Plugin {
|
|
393
|
+
return {
|
|
394
|
+
id: 'adea-radial-scale',
|
|
395
|
+
afterInit(chart) {
|
|
396
|
+
const scale = options().scales?.['r']
|
|
397
|
+
if (!scale) return
|
|
398
|
+
chart.options.scales = { ...chart.options.scales, r: scale }
|
|
399
|
+
},
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/** A polar area chart: radial bars, for a composition. */
|
|
353
404
|
export function PolarAreaChart(props: {
|
|
354
405
|
labels: readonly string[]
|
|
355
406
|
data: readonly number[]
|
|
@@ -357,9 +408,11 @@ export function PolarAreaChart(props: {
|
|
|
357
408
|
class?: string
|
|
358
409
|
}) {
|
|
359
410
|
const theme = createThemeResolver()
|
|
411
|
+
const options = () => theme.resolve(chartOptions(props.options, 'radial'))
|
|
360
412
|
return (
|
|
361
413
|
<div ref={theme.ref} class={cn('h-64', props.class)}>
|
|
362
414
|
<PolarArea
|
|
415
|
+
plugins={[restoreRadialScale(options)]}
|
|
363
416
|
data={theme.resolve({
|
|
364
417
|
labels: [...props.labels],
|
|
365
418
|
datasets: [
|
|
@@ -371,7 +424,7 @@ export function PolarAreaChart(props: {
|
|
|
371
424
|
},
|
|
372
425
|
],
|
|
373
426
|
})}
|
|
374
|
-
options={
|
|
427
|
+
options={options()}
|
|
375
428
|
/>
|
|
376
429
|
</div>
|
|
377
430
|
)
|
|
@@ -9,6 +9,7 @@ import type { Accessor, ComponentProps, JSX } from 'solid-js'
|
|
|
9
9
|
import { createRenderEffect, onCleanup, Show, splitProps } from 'solid-js'
|
|
10
10
|
import { overlayMotion, overlayPositioner, overlayScrim, overlaySurface } from '#lib/overlay'
|
|
11
11
|
import { cn } from '#lib/utils'
|
|
12
|
+
import { headingVariants } from '../typography'
|
|
12
13
|
|
|
13
14
|
type FocusCycle = {
|
|
14
15
|
content: HTMLElement
|
|
@@ -327,7 +328,7 @@ export function DialogTitle(props: ComponentProps<typeof KobalteDialog.Title>) {
|
|
|
327
328
|
const [local, rest] = splitProps(props, ['class'])
|
|
328
329
|
return (
|
|
329
330
|
<KobalteDialog.Title
|
|
330
|
-
class={cn('
|
|
331
|
+
class={cn(headingVariants({ size: 'card', leading: 'none' }), local.class)}
|
|
331
332
|
{...rest}
|
|
332
333
|
/>
|
|
333
334
|
)
|
|
@@ -2,6 +2,7 @@ import DrawerPrimitive from '@corvu/drawer'
|
|
|
2
2
|
import type { ComponentProps } from 'solid-js'
|
|
3
3
|
import { splitProps } from 'solid-js'
|
|
4
4
|
import { cn } from '#lib/utils'
|
|
5
|
+
import { headingVariants } from '../typography'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Drawer.
|
|
@@ -96,7 +97,7 @@ export function DrawerTitle(props: ComponentProps<typeof DrawerPrimitive.Label>)
|
|
|
96
97
|
const [local, rest] = splitProps(props, ['class'])
|
|
97
98
|
return (
|
|
98
99
|
<DrawerPrimitive.Label
|
|
99
|
-
class={cn('
|
|
100
|
+
class={cn(headingVariants({ size: 'card', leading: 'none' }), local.class)}
|
|
100
101
|
{...rest}
|
|
101
102
|
/>
|
|
102
103
|
)
|
|
@@ -4,6 +4,7 @@ import { createUniqueId, Show, splitProps } from 'solid-js'
|
|
|
4
4
|
import { Label } from '../label/label'
|
|
5
5
|
import { createFormFieldContext, FormFieldContext } from '#lib/form-field'
|
|
6
6
|
import { cn } from '#lib/utils'
|
|
7
|
+
import { headingVariants } from '../typography'
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Field.
|
|
@@ -199,9 +200,7 @@ export function FieldLegend(props: ComponentProps<'legend'> & { variant?: 'legen
|
|
|
199
200
|
<legend
|
|
200
201
|
class={cn(
|
|
201
202
|
'mb-1',
|
|
202
|
-
local.variant === 'label'
|
|
203
|
-
? 'text-sm font-medium'
|
|
204
|
-
: 'text-base font-semibold tracking-tight',
|
|
203
|
+
local.variant === 'label' ? 'text-sm font-medium' : headingVariants({ size: 'card' }),
|
|
205
204
|
local.class
|
|
206
205
|
)}
|
|
207
206
|
{...rest}
|
|
@@ -10,6 +10,7 @@ import { Show, splitProps } from 'solid-js'
|
|
|
10
10
|
import { cva, type VariantProps } from '#lib/variants'
|
|
11
11
|
import { cn } from '#lib/utils'
|
|
12
12
|
import { createDialogFocusRestoration, DialogOverlay } from '../dialog/dialog'
|
|
13
|
+
import { headingVariants } from '../typography'
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Sheet.
|
|
@@ -152,7 +153,10 @@ export function SheetFooter(props: ComponentProps<'div'>) {
|
|
|
152
153
|
export function SheetTitle(props: ComponentProps<typeof KobalteDialog.Title>) {
|
|
153
154
|
const [local, rest] = splitProps(props, ['class'])
|
|
154
155
|
return (
|
|
155
|
-
<KobalteDialog.Title
|
|
156
|
+
<KobalteDialog.Title
|
|
157
|
+
class={cn(headingVariants({ size: 'subsection', leading: 'none' }), local.class)}
|
|
158
|
+
{...rest}
|
|
159
|
+
/>
|
|
156
160
|
)
|
|
157
161
|
}
|
|
158
162
|
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { Polymorphic, type PolymorphicProps } from '@kobalte/core/polymorphic'
|
|
2
|
+
import { type ComponentProps, splitProps, type ValidComponent } from 'solid-js'
|
|
3
|
+
import { cva, type VariantProps } from '#lib/variants'
|
|
4
|
+
import { cn } from '#lib/utils'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Typography.
|
|
8
|
+
*
|
|
9
|
+
* The type ladder in `theme.css` has eight rungs, each with its own leading. These
|
|
10
|
+
* two components are where a rung is paired with its weight and tracking, so a call
|
|
11
|
+
* site names a *role* — a page title, a section heading, a caption — and never
|
|
12
|
+
* re-decides `text-xl font-semibold tracking-tight` for itself. Twenty call sites
|
|
13
|
+
* that each choose a size and a weight drift; twenty that name a role cannot.
|
|
14
|
+
*
|
|
15
|
+
* **Size is visual; the element is the document outline.** They are separate
|
|
16
|
+
* decisions and the API keeps them separate. Each size has a conventional default
|
|
17
|
+
* element (`display`, `title` and `page` render an `h1`, `section` an `h2`, `card`
|
|
18
|
+
* an `h3`, `subsection` an `h4`) so the common case needs no prop, but the default
|
|
19
|
+
* is a convenience, not a coupling: a card-sized title that is the first heading
|
|
20
|
+
* in a settings section is an `h2`, so it says `as="h2"`. Never pick a `size` to get
|
|
21
|
+
* a heading level, and never pick an element to get a size.
|
|
22
|
+
*
|
|
23
|
+
* Headings are 600, and weights stop at 600 — on a dark surface a bold word blooms.
|
|
24
|
+
* Leading comes from the rung; `leading="none"` is for a title whose surrounding
|
|
25
|
+
* stack already sets the rhythm (a card or dialog header), and is the only other
|
|
26
|
+
* option.
|
|
27
|
+
*/
|
|
28
|
+
export const headingVariants = cva('font-semibold', {
|
|
29
|
+
variants: {
|
|
30
|
+
size: {
|
|
31
|
+
/** 30px. An app-level headline — a first-run or empty-app screen. Once per app. */
|
|
32
|
+
display: 'text-3xl tracking-tight',
|
|
33
|
+
/** 24px. A landing title or a headline figure larger than a stat's. */
|
|
34
|
+
title: 'text-2xl tracking-tight',
|
|
35
|
+
/** 20px. The title of a page (`PageHeaderTitle`). */
|
|
36
|
+
page: 'text-xl tracking-tight',
|
|
37
|
+
/** 18px. A section heading inside a page (`PageSection`). */
|
|
38
|
+
section: 'text-lg tracking-tight',
|
|
39
|
+
/** 16px. A card, dialog, drawer or settings-section title. */
|
|
40
|
+
card: 'text-base tracking-tight',
|
|
41
|
+
/** 14px. A heading inside a compact surface: a sheet, a chart, a column. */
|
|
42
|
+
subsection: 'text-sm',
|
|
43
|
+
},
|
|
44
|
+
leading: {
|
|
45
|
+
/** The rung's own line-height from `theme.css`. */
|
|
46
|
+
rung: '',
|
|
47
|
+
/** Line-height 1, for a title whose container's gap sets the rhythm. */
|
|
48
|
+
none: 'leading-none',
|
|
49
|
+
},
|
|
50
|
+
/** Tabular figures, so a row of numbers can be compared without digits shifting. */
|
|
51
|
+
numeric: {
|
|
52
|
+
true: 'tabular-nums',
|
|
53
|
+
},
|
|
54
|
+
tone: {
|
|
55
|
+
/** Inherit the surrounding colour — right on every surface by construction. */
|
|
56
|
+
inherit: '',
|
|
57
|
+
foreground: 'text-foreground',
|
|
58
|
+
muted: 'text-muted-foreground',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
defaultVariants: {
|
|
62
|
+
size: 'section',
|
|
63
|
+
leading: 'rung',
|
|
64
|
+
tone: 'inherit',
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The conventional element for each heading size. Exported so a test (and a
|
|
70
|
+
* reader) can see the whole mapping in one place; pass `as` to depart from it.
|
|
71
|
+
*/
|
|
72
|
+
export const headingElement = {
|
|
73
|
+
display: 'h1',
|
|
74
|
+
title: 'h1',
|
|
75
|
+
page: 'h1',
|
|
76
|
+
section: 'h2',
|
|
77
|
+
card: 'h3',
|
|
78
|
+
subsection: 'h4',
|
|
79
|
+
} as const
|
|
80
|
+
|
|
81
|
+
type HeadingVariantProps = VariantProps<typeof headingVariants>
|
|
82
|
+
type HeadingSize = keyof typeof headingElement
|
|
83
|
+
|
|
84
|
+
export type HeadingProps<T extends ValidComponent = 'h2'> = PolymorphicProps<
|
|
85
|
+
T,
|
|
86
|
+
HeadingVariantProps & {
|
|
87
|
+
class?: string
|
|
88
|
+
}
|
|
89
|
+
>
|
|
90
|
+
|
|
91
|
+
export function Heading<T extends ValidComponent = 'h2'>(props: HeadingProps<T>) {
|
|
92
|
+
const [local, rest] = splitProps(props as HeadingProps & { as?: ValidComponent }, [
|
|
93
|
+
'as',
|
|
94
|
+
'size',
|
|
95
|
+
'leading',
|
|
96
|
+
'numeric',
|
|
97
|
+
'tone',
|
|
98
|
+
'class',
|
|
99
|
+
])
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<Polymorphic
|
|
103
|
+
as={local.as ?? headingElement[(local.size ?? 'section') as HeadingSize]}
|
|
104
|
+
data-slot="heading"
|
|
105
|
+
class={cn(
|
|
106
|
+
headingVariants({
|
|
107
|
+
size: local.size,
|
|
108
|
+
leading: local.leading,
|
|
109
|
+
numeric: local.numeric,
|
|
110
|
+
tone: local.tone,
|
|
111
|
+
}),
|
|
112
|
+
local.class
|
|
113
|
+
)}
|
|
114
|
+
{...(rest as ComponentProps<'h2'>)}
|
|
115
|
+
/>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Text.
|
|
121
|
+
*
|
|
122
|
+
* Everything that is not a heading. `text-sm` is the interface default, not 16px,
|
|
123
|
+
* and it carries three weights by role: 400 for body copy, 500 for a label or
|
|
124
|
+
* control text, 600 for emphasis. Below it, `caption` (12px) is metadata and
|
|
125
|
+
* `micro` (11px, 500) is the floor — keyboard keys and the status bar. `code` is
|
|
126
|
+
* the mono face at 12px, for anything a user compares character by character.
|
|
127
|
+
*
|
|
128
|
+
* `tone` is a separate axis from `variant` because "secondary" is a colour
|
|
129
|
+
* decision, not a size: a muted caption and a muted body line are both common.
|
|
130
|
+
* The default inherits, so text on a filled surface picks up that surface's
|
|
131
|
+
* foreground without the caller restating it.
|
|
132
|
+
*
|
|
133
|
+
* Defaults: `body` renders a `p`, `code` a `code`, every other variant a `span`.
|
|
134
|
+
*/
|
|
135
|
+
export const textVariants = cva('', {
|
|
136
|
+
variants: {
|
|
137
|
+
variant: {
|
|
138
|
+
/** 14px / 400. Body copy and descriptions. */
|
|
139
|
+
body: 'text-sm',
|
|
140
|
+
/** 14px / 500. Labels, control text, list-row titles. */
|
|
141
|
+
label: 'text-sm font-medium',
|
|
142
|
+
/** 14px / 600. Emphasis inside body copy. */
|
|
143
|
+
strong: 'text-sm font-semibold',
|
|
144
|
+
/** 12px / 400. Metadata, secondary rows, units. */
|
|
145
|
+
caption: 'text-xs',
|
|
146
|
+
/** 11px / 500. The floor: keyboard keys, the status bar. */
|
|
147
|
+
micro: 'text-2xs font-medium',
|
|
148
|
+
/** 12px mono. Ids, paths, hashes, inline code. */
|
|
149
|
+
code: 'font-mono text-xs',
|
|
150
|
+
},
|
|
151
|
+
tone: {
|
|
152
|
+
inherit: '',
|
|
153
|
+
foreground: 'text-foreground',
|
|
154
|
+
muted: 'text-muted-foreground',
|
|
155
|
+
},
|
|
156
|
+
numeric: {
|
|
157
|
+
true: 'tabular-nums',
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
defaultVariants: {
|
|
161
|
+
variant: 'body',
|
|
162
|
+
tone: 'inherit',
|
|
163
|
+
},
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
export const textElement = {
|
|
167
|
+
body: 'p',
|
|
168
|
+
label: 'span',
|
|
169
|
+
strong: 'span',
|
|
170
|
+
caption: 'span',
|
|
171
|
+
micro: 'span',
|
|
172
|
+
code: 'code',
|
|
173
|
+
} as const
|
|
174
|
+
|
|
175
|
+
type TextVariantProps = VariantProps<typeof textVariants>
|
|
176
|
+
type TextVariant = keyof typeof textElement
|
|
177
|
+
|
|
178
|
+
export type TextProps<T extends ValidComponent = 'p'> = PolymorphicProps<
|
|
179
|
+
T,
|
|
180
|
+
TextVariantProps & {
|
|
181
|
+
class?: string
|
|
182
|
+
}
|
|
183
|
+
>
|
|
184
|
+
|
|
185
|
+
export function Text<T extends ValidComponent = 'p'>(props: TextProps<T>) {
|
|
186
|
+
const [local, rest] = splitProps(props as TextProps & { as?: ValidComponent }, [
|
|
187
|
+
'as',
|
|
188
|
+
'variant',
|
|
189
|
+
'tone',
|
|
190
|
+
'numeric',
|
|
191
|
+
'class',
|
|
192
|
+
])
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<Polymorphic
|
|
196
|
+
as={local.as ?? textElement[(local.variant ?? 'body') as TextVariant]}
|
|
197
|
+
data-slot="text"
|
|
198
|
+
class={cn(
|
|
199
|
+
textVariants({ variant: local.variant, tone: local.tone, numeric: local.numeric }),
|
|
200
|
+
local.class
|
|
201
|
+
)}
|
|
202
|
+
{...(rest as ComponentProps<'p'>)}
|
|
203
|
+
/>
|
|
204
|
+
)
|
|
205
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -74,6 +74,7 @@ export * from './components/ui/toast'
|
|
|
74
74
|
export * from './components/ui/toggle'
|
|
75
75
|
export * from './components/ui/toggle-group'
|
|
76
76
|
export * from './components/ui/tooltip'
|
|
77
|
+
export * from './components/ui/typography'
|
|
77
78
|
|
|
78
79
|
/* --- Motion -------------------------------------------------------------- */
|
|
79
80
|
export * from './components/motion'
|