@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,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { tableHeadVariants, type TableAlign, type TableDensity, type TableHeadTone } from '../table/index.ts'
|
|
3
|
+
import { cn } from '../shared/index.ts'
|
|
4
|
+
|
|
5
|
+
interface Props extends Omit<astroHTML.JSX.ThHTMLAttributes, 'align'> {
|
|
6
|
+
align?: TableAlign
|
|
7
|
+
density?: TableDensity
|
|
8
|
+
tone?: TableHeadTone
|
|
9
|
+
}
|
|
10
|
+
const { align, density, tone, scope = 'col', class: className, ...rest } = Astro.props
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<th scope={scope} class={cn(tableHeadVariants({ align, density, tone }), className)} data-slot="table-head" {...rest}><slot /></th>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { tableRowClass } from '../table/index.ts'
|
|
3
|
+
import { cn } from '../shared/index.ts'
|
|
4
|
+
|
|
5
|
+
interface Props extends astroHTML.JSX.HTMLAttributes {}
|
|
6
|
+
const { class: className, ...rest } = Astro.props
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<tr class={cn(tableRowClass, className)} data-slot="table-row" {...rest}><slot /></tr>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -11,8 +11,12 @@ export const avatarTokens: TokenContract = {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export const avatarVariants = cva({
|
|
14
|
-
base: 'relative flex shrink-0 overflow-hidden
|
|
14
|
+
base: 'relative flex shrink-0 overflow-hidden',
|
|
15
15
|
variants: {
|
|
16
|
+
shape: {
|
|
17
|
+
circle: 'rounded-full',
|
|
18
|
+
square: 'rounded-md',
|
|
19
|
+
},
|
|
16
20
|
size: {
|
|
17
21
|
xs: 'h-6 w-6',
|
|
18
22
|
sm: 'h-8 w-8',
|
|
@@ -22,6 +26,7 @@ export const avatarVariants = cva({
|
|
|
22
26
|
},
|
|
23
27
|
},
|
|
24
28
|
defaultVariants: {
|
|
29
|
+
shape: 'circle',
|
|
25
30
|
size: 'md',
|
|
26
31
|
},
|
|
27
32
|
})
|
|
@@ -31,7 +36,7 @@ export const avatarImageVariants = cva({
|
|
|
31
36
|
})
|
|
32
37
|
|
|
33
38
|
export const avatarFallbackVariants = cva({
|
|
34
|
-
base: 'flex h-full w-full items-center justify-center rounded-
|
|
39
|
+
base: 'flex h-full w-full items-center justify-center rounded-[inherit] bg-muted text-muted-foreground',
|
|
35
40
|
variants: {
|
|
36
41
|
size: {
|
|
37
42
|
xs: 'text-xs',
|
package/dist/avatar/avatar.ts
CHANGED
|
@@ -2,6 +2,9 @@ import type { AccessibilityProps } from '../shared/index.ts'
|
|
|
2
2
|
|
|
3
3
|
export type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
4
4
|
|
|
5
|
+
/** Outline of the avatar. */
|
|
6
|
+
export type AvatarShape = 'circle' | 'square'
|
|
7
|
+
|
|
5
8
|
export type AvatarLoadingStatus = 'idle' | 'loading' | 'loaded' | 'error'
|
|
6
9
|
|
|
7
10
|
export interface AvatarProps {
|
|
@@ -44,10 +47,12 @@ export function getInitials(name: string): string {
|
|
|
44
47
|
export function createAvatar(props: AvatarProps = {}): AvatarAPI {
|
|
45
48
|
const { src, alt = '', fallback = '', size = 'md' } = props
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
// role="img" makes its children presentational, so it is only applied when
|
|
51
|
+
// the avatar itself has a name; otherwise the inner <img alt> / fallback
|
|
52
|
+
// text stays exposed.
|
|
53
|
+
const ariaProps: Partial<AccessibilityProps> = {}
|
|
50
54
|
if (alt) {
|
|
55
|
+
ariaProps.role = 'img'
|
|
51
56
|
ariaProps['aria-label'] = alt
|
|
52
57
|
}
|
|
53
58
|
|
package/dist/avatar/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cva } from '../shared/index.ts'
|
|
2
2
|
|
|
3
3
|
export const badgeVariants = cva({
|
|
4
|
-
base: 'inline-flex items-center rounded-full border
|
|
4
|
+
base: 'inline-flex items-center rounded-full border font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
|
5
5
|
variants: {
|
|
6
6
|
variant: {
|
|
7
7
|
default: 'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80',
|
|
@@ -11,6 +11,11 @@ export const badgeVariants = cva({
|
|
|
11
11
|
outline: 'text-foreground',
|
|
12
12
|
success: 'border-transparent bg-success text-success-foreground shadow hover:bg-success/80 font-semibold',
|
|
13
13
|
warning: 'border-transparent bg-warning text-warning-foreground shadow hover:bg-warning/80 font-semibold',
|
|
14
|
+
// Second brand accent + extended status roles (issue #485).
|
|
15
|
+
tertiary: 'border-transparent bg-tertiary text-tertiary-foreground shadow hover:bg-tertiary/80 font-semibold',
|
|
16
|
+
positive: 'border-transparent bg-positive text-positive-foreground shadow hover:bg-positive/80 font-semibold',
|
|
17
|
+
caution: 'border-transparent bg-caution text-caution-foreground shadow hover:bg-caution/80 font-semibold',
|
|
18
|
+
done: 'border-transparent bg-done text-info-foreground shadow hover:bg-done/80 font-semibold',
|
|
14
19
|
},
|
|
15
20
|
size: {
|
|
16
21
|
sm: 'px-2 py-0 text-[10px]',
|
package/dist/badge/badge.ts
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import type { AccessibilityProps } from '../shared/index.ts'
|
|
2
2
|
|
|
3
|
-
export type BadgeVariant =
|
|
3
|
+
export type BadgeVariant =
|
|
4
|
+
| 'default'
|
|
5
|
+
| 'primary'
|
|
6
|
+
| 'secondary'
|
|
7
|
+
| 'destructive'
|
|
8
|
+
| 'outline'
|
|
9
|
+
| 'success'
|
|
10
|
+
| 'warning'
|
|
11
|
+
// Issue #485 — second brand accent + extended status roles.
|
|
12
|
+
| 'tertiary'
|
|
13
|
+
| 'positive'
|
|
14
|
+
| 'caution'
|
|
15
|
+
| 'done'
|
|
4
16
|
export type BadgeSize = 'sm' | 'md'
|
|
5
17
|
|
|
6
18
|
export interface BadgeProps {
|
|
@@ -15,13 +27,23 @@ export interface BadgeAPI {
|
|
|
15
27
|
dataAttributes: Record<string, string>
|
|
16
28
|
}
|
|
17
29
|
|
|
18
|
-
const statusVariants: BadgeVariant[] = [
|
|
30
|
+
const statusVariants: BadgeVariant[] = [
|
|
31
|
+
'success',
|
|
32
|
+
'warning',
|
|
33
|
+
'destructive',
|
|
34
|
+
'positive',
|
|
35
|
+
'caution',
|
|
36
|
+
'done',
|
|
37
|
+
]
|
|
19
38
|
|
|
20
39
|
/** Icon name for status badge variants — non-color indicator for colorblind safety */
|
|
21
40
|
const BADGE_ICONS: Partial<Record<BadgeVariant, string>> = {
|
|
22
41
|
success: 'check',
|
|
23
42
|
warning: 'alert',
|
|
24
43
|
destructive: 'x',
|
|
44
|
+
positive: 'check',
|
|
45
|
+
caution: 'alert',
|
|
46
|
+
done: 'check',
|
|
25
47
|
}
|
|
26
48
|
|
|
27
49
|
/** Get the icon name for a badge variant (undefined for non-status variants) */
|
|
@@ -13,7 +13,7 @@ export const buttonTokens: TokenContract = {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export const buttonVariants = cva({
|
|
16
|
-
base: 'inline-flex items-center justify-center gap-2 whitespace-nowrap
|
|
16
|
+
base: 'inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0',
|
|
17
17
|
variants: {
|
|
18
18
|
variant: {
|
|
19
19
|
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
|
|
@@ -24,15 +24,21 @@ export const buttonVariants = cva({
|
|
|
24
24
|
destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
|
|
25
25
|
outline: 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
|
|
26
26
|
secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
|
|
27
|
+
// Soft tinted brand fill — lower-emphasis primary action (issue #485).
|
|
28
|
+
soft: 'bg-primary-soft text-primary-soft-foreground shadow-sm hover:bg-primary-soft/80',
|
|
29
|
+
// Second brand accent (issue #485).
|
|
30
|
+
tertiary: 'bg-tertiary text-tertiary-foreground shadow-sm hover:bg-tertiary/90',
|
|
27
31
|
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
|
28
32
|
link: 'text-primary underline-offset-4 hover:underline',
|
|
29
33
|
},
|
|
30
34
|
size: {
|
|
31
35
|
xs: 'h-7 rounded px-2 text-xs',
|
|
32
36
|
sm: 'h-8 rounded-md px-3 text-xs',
|
|
33
|
-
default: 'h-9 px-4 py-2',
|
|
34
|
-
lg: 'h-10 rounded-md px-8',
|
|
35
|
-
icon: 'h-9 w-9',
|
|
37
|
+
default: 'h-9 rounded-md px-4 py-2 text-sm',
|
|
38
|
+
lg: 'h-10 rounded-md px-8 text-sm',
|
|
39
|
+
icon: 'h-9 w-9 rounded-md text-sm',
|
|
40
|
+
'icon-sm': 'h-8 w-8 rounded-md text-xs',
|
|
41
|
+
'icon-xs': 'h-7 w-7 rounded text-xs',
|
|
36
42
|
},
|
|
37
43
|
},
|
|
38
44
|
defaultVariants: {
|
package/dist/button/button.ts
CHANGED
|
@@ -14,6 +14,9 @@ export type ButtonVariant =
|
|
|
14
14
|
| 'secondary'
|
|
15
15
|
| 'ghost'
|
|
16
16
|
| 'link'
|
|
17
|
+
// Issue #485 — soft tinted brand fill + a real second brand accent.
|
|
18
|
+
| 'soft'
|
|
19
|
+
| 'tertiary'
|
|
17
20
|
|
|
18
21
|
/** Variants that map to the same visual style as `default`. */
|
|
19
22
|
export const BUTTON_VARIANT_ALIASES: Partial<Record<ButtonVariant, ButtonVariant>> = {
|
|
@@ -25,7 +28,7 @@ export function resolveButtonVariant(variant: ButtonVariant | undefined): Button
|
|
|
25
28
|
if (!variant) return variant
|
|
26
29
|
return BUTTON_VARIANT_ALIASES[variant] ?? variant
|
|
27
30
|
}
|
|
28
|
-
export type ButtonSize = 'xs' | 'sm' | 'default' | 'lg' | 'icon'
|
|
31
|
+
export type ButtonSize = 'xs' | 'sm' | 'default' | 'lg' | 'icon' | 'icon-sm' | 'icon-xs'
|
|
29
32
|
|
|
30
33
|
export interface ButtonProps {
|
|
31
34
|
variant?: ButtonVariant
|
package/dist/callout/callout.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import type { AccessibilityProps } from '../shared/index.ts'
|
|
2
2
|
|
|
3
3
|
export interface CalloutProps {
|
|
4
|
-
/**
|
|
4
|
+
/** Explicit role; always wins. */
|
|
5
5
|
role?: string
|
|
6
|
+
/** Destructive callouts are announced (`role="alert"`). */
|
|
7
|
+
destructive?: boolean
|
|
8
|
+
/**
|
|
9
|
+
* Whether the callout has an accessible name (`aria-label` /
|
|
10
|
+
* `aria-labelledby`). Only a named callout becomes a `region` landmark — an
|
|
11
|
+
* unnamed region is an axe violation and landmark noise.
|
|
12
|
+
*/
|
|
13
|
+
labelled?: boolean
|
|
6
14
|
}
|
|
7
15
|
|
|
8
16
|
export interface CalloutAPI {
|
|
@@ -13,10 +21,11 @@ export interface CalloutAPI {
|
|
|
13
21
|
export function createCallout(props: CalloutProps = {}): CalloutAPI {
|
|
14
22
|
const ariaProps: Partial<AccessibilityProps> = {}
|
|
15
23
|
|
|
16
|
-
// Callouts are usually alert or region depending on severity
|
|
17
24
|
if (props.role) {
|
|
18
25
|
ariaProps.role = props.role
|
|
19
|
-
} else {
|
|
26
|
+
} else if (props.destructive) {
|
|
27
|
+
ariaProps.role = 'alert'
|
|
28
|
+
} else if (props.labelled) {
|
|
20
29
|
ariaProps.role = 'region'
|
|
21
30
|
}
|
|
22
31
|
|
package/dist/card/card.styles.ts
CHANGED
|
@@ -11,8 +11,16 @@ export const cardTokens: TokenContract = {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export const cardVariants = cva({
|
|
14
|
-
base: 'rounded-lg border
|
|
14
|
+
base: 'rounded-lg border shadow',
|
|
15
15
|
variants: {
|
|
16
|
+
variant: {
|
|
17
|
+
// Default — unchanged surface (bg-card + border via the global token).
|
|
18
|
+
default: 'bg-card text-card-foreground',
|
|
19
|
+
// Lower-emphasis container on the subtle surface token (issue #485).
|
|
20
|
+
subtle: 'bg-surface-subtle text-card-foreground',
|
|
21
|
+
// Tinted second-accent card (issue #485).
|
|
22
|
+
tertiary: 'border-transparent bg-tertiary-soft text-tertiary-soft-foreground',
|
|
23
|
+
},
|
|
16
24
|
padding: {
|
|
17
25
|
none: '',
|
|
18
26
|
default: 'p-6',
|
|
@@ -20,6 +28,7 @@ export const cardVariants = cva({
|
|
|
20
28
|
},
|
|
21
29
|
},
|
|
22
30
|
defaultVariants: {
|
|
31
|
+
variant: 'default',
|
|
23
32
|
padding: 'none',
|
|
24
33
|
},
|
|
25
34
|
})
|
package/dist/card/card.ts
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import type { AccessibilityProps } from '../shared/index.ts'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Visual tone of a card (issue #485).
|
|
5
|
+
* - `default` — the standard surface (activates the `--rfr-card-*` contract).
|
|
6
|
+
* - `subtle` — a lower-emphasis container on the subtle surface token.
|
|
7
|
+
* - `tertiary` — a tinted second-brand-accent card.
|
|
8
|
+
*/
|
|
9
|
+
export type CardVariant = 'default' | 'subtle' | 'tertiary'
|
|
10
|
+
|
|
11
|
+
/** Built-in inner padding of the Card container. */
|
|
12
|
+
export type CardPadding = 'none' | 'default' | 'compact'
|
|
13
|
+
|
|
14
|
+
/** Heading element for CardTitle. */
|
|
15
|
+
export type CardTitleLevel = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
|
|
16
|
+
|
|
3
17
|
export interface CardProps {
|
|
4
18
|
/** Optional accessible role override */
|
|
5
19
|
role?: string
|
package/dist/card/index.ts
CHANGED
|
@@ -53,3 +53,21 @@ export const rowVariants = cva({
|
|
|
53
53
|
selected: 'false',
|
|
54
54
|
},
|
|
55
55
|
})
|
|
56
|
+
|
|
57
|
+
/** Logical text alignment per column alignment. */
|
|
58
|
+
export const dataTableAlignClass = {
|
|
59
|
+
start: 'text-start',
|
|
60
|
+
center: 'text-center',
|
|
61
|
+
end: 'text-end',
|
|
62
|
+
} as const
|
|
63
|
+
|
|
64
|
+
/** Numeric cells: tabular figures, no wrapping. */
|
|
65
|
+
export const dataTableNumericClass = 'tabular-nums whitespace-nowrap'
|
|
66
|
+
|
|
67
|
+
/** Button inside a sortable header: the keyboard-reachable sort control. */
|
|
68
|
+
export const dataTableSortButtonClass =
|
|
69
|
+
'inline-flex items-center gap-1 rounded-sm font-inherit text-inherit focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring'
|
|
70
|
+
|
|
71
|
+
/** Rows with a row action: pointer + keyboard focus ring. */
|
|
72
|
+
export const dataTableActionRowClass =
|
|
73
|
+
'cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring'
|
|
@@ -5,8 +5,25 @@ export interface ColumnDef<T = Record<string, unknown>> {
|
|
|
5
5
|
id: string
|
|
6
6
|
/** Column header text */
|
|
7
7
|
header: string
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Value of this column for a row. Drives sorting, filtering and — when no
|
|
10
|
+
* `cell` renderer is given — the rendered text. Optional for display-only
|
|
11
|
+
* columns that set `cell`.
|
|
12
|
+
*/
|
|
13
|
+
accessor?: (row: T) => unknown
|
|
14
|
+
/**
|
|
15
|
+
* Custom cell renderer. Framework adapters narrow the return type (React:
|
|
16
|
+
* `ReactNode`). When omitted the cell renders `String(accessor(row))`.
|
|
17
|
+
*/
|
|
18
|
+
cell?: (row: T, index: number) => unknown
|
|
19
|
+
/** Horizontal alignment of header and cells. Defaults to `start`. */
|
|
20
|
+
align?: DataTableAlign
|
|
21
|
+
/** Numeric column: tabular figures, no wrapping, aligned to `end` unless `align` says otherwise. */
|
|
22
|
+
numeric?: boolean
|
|
23
|
+
/** Extra class for this column's data cells. */
|
|
24
|
+
className?: string
|
|
25
|
+
/** Extra class for this column's header cell. */
|
|
26
|
+
headerClassName?: string
|
|
10
27
|
/** Whether this column is sortable */
|
|
11
28
|
sortable?: boolean
|
|
12
29
|
/** Whether this column is filterable */
|
|
@@ -15,6 +32,31 @@ export interface ColumnDef<T = Record<string, unknown>> {
|
|
|
15
32
|
|
|
16
33
|
export type SortDirection = 'asc' | 'desc'
|
|
17
34
|
|
|
35
|
+
/** Horizontal alignment of a column (logical, RTL-safe). */
|
|
36
|
+
export type DataTableAlign = 'start' | 'center' | 'end'
|
|
37
|
+
|
|
38
|
+
/** A column's effective alignment: explicit `align`, else `end` for numeric columns. */
|
|
39
|
+
export function resolveColumnAlign(col: { align?: DataTableAlign; numeric?: boolean }): DataTableAlign {
|
|
40
|
+
return col.align ?? (col.numeric ? 'end' : 'start')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The raw value of a column for a row (`undefined` without an accessor). */
|
|
44
|
+
export function getColumnValue<T>(col: { accessor?: (row: T) => unknown }, row: T): unknown {
|
|
45
|
+
return col.accessor ? col.accessor(row) : undefined
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Stable React/DOM key for a row: `getRowKey` when given, else the row's
|
|
50
|
+
* index. Prefer a `getRowKey` — index keys break row state on sort/filter.
|
|
51
|
+
*/
|
|
52
|
+
export function resolveRowKey<T>(
|
|
53
|
+
row: T,
|
|
54
|
+
index: number,
|
|
55
|
+
getRowKey?: (row: T, index: number) => string | number,
|
|
56
|
+
): string | number {
|
|
57
|
+
return getRowKey ? getRowKey(row, index) : index
|
|
58
|
+
}
|
|
59
|
+
|
|
18
60
|
export interface DataTableProps<T = Record<string, unknown>> {
|
|
19
61
|
/** Column definitions */
|
|
20
62
|
columns: ColumnDef<T>[]
|
|
@@ -74,10 +116,10 @@ export function createDataTable<T = Record<string, unknown>>(
|
|
|
74
116
|
for (const [columnId, filterValue] of Object.entries(filters)) {
|
|
75
117
|
if (!filterValue) continue
|
|
76
118
|
const col = columns.find((c) => c.id === columnId)
|
|
77
|
-
if (!col) continue
|
|
119
|
+
if (!col?.accessor) continue
|
|
78
120
|
const lowerFilter = filterValue.toLowerCase()
|
|
79
121
|
result = result.filter((row) => {
|
|
80
|
-
const cellValue = col
|
|
122
|
+
const cellValue = getColumnValue(col, row)
|
|
81
123
|
return String(cellValue ?? '').toLowerCase().includes(lowerFilter)
|
|
82
124
|
})
|
|
83
125
|
}
|
|
@@ -91,11 +133,11 @@ export function createDataTable<T = Record<string, unknown>>(
|
|
|
91
133
|
if (!sortBy) return filtered
|
|
92
134
|
|
|
93
135
|
const col = columns.find((c) => c.id === sortBy)
|
|
94
|
-
if (!col) return filtered
|
|
136
|
+
if (!col?.accessor) return filtered
|
|
95
137
|
|
|
96
138
|
return [...filtered].sort((a, b) => {
|
|
97
|
-
const aVal = col
|
|
98
|
-
const bVal = col
|
|
139
|
+
const aVal = getColumnValue(col, a)
|
|
140
|
+
const bVal = getColumnValue(col, b)
|
|
99
141
|
const aStr = String(aVal ?? '')
|
|
100
142
|
const bStr = String(bVal ?? '')
|
|
101
143
|
|
package/dist/data-table/index.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export {
|
|
2
2
|
createDataTable,
|
|
3
|
+
resolveColumnAlign,
|
|
4
|
+
getColumnValue,
|
|
5
|
+
resolveRowKey,
|
|
3
6
|
type ColumnDef,
|
|
7
|
+
type DataTableAlign,
|
|
4
8
|
type SortDirection,
|
|
5
9
|
type DataTableProps,
|
|
6
10
|
type DataTableState,
|
|
@@ -12,4 +16,8 @@ export {
|
|
|
12
16
|
headerVariants,
|
|
13
17
|
cellVariants,
|
|
14
18
|
rowVariants,
|
|
19
|
+
dataTableAlignClass,
|
|
20
|
+
dataTableNumericClass,
|
|
21
|
+
dataTableSortButtonClass,
|
|
22
|
+
dataTableActionRowClass,
|
|
15
23
|
} from './data-table.styles.js'
|
|
@@ -9,6 +9,9 @@ export const diffViewerTokens: TokenContract = {
|
|
|
9
9
|
sidebarBg: { variable: '--rfr-diff-sidebar-bg', fallback: 'hsl(var(--muted))' },
|
|
10
10
|
headerBg: { variable: '--rfr-diff-header-bg', fallback: 'hsl(var(--muted))' },
|
|
11
11
|
border: { variable: '--rfr-diff-border', fallback: 'hsl(var(--border))' },
|
|
12
|
+
// Diff add/del colors are syntax semantics (like code highlighting), not
|
|
13
|
+
// theme status tones — deliberately NOT routed through the success/
|
|
14
|
+
// destructive tokens. The --rfr-diff-* variables are the theming seam.
|
|
12
15
|
addBg: { variable: '--rfr-diff-add-bg', fallback: 'rgba(46, 160, 67, 0.15)' },
|
|
13
16
|
delBg: { variable: '--rfr-diff-del-bg', fallback: 'rgba(248, 81, 73, 0.15)' },
|
|
14
17
|
addFg: { variable: '--rfr-diff-add-fg', fallback: '#3fb950' },
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Token-class styles for FunnelChart; no colour literals. */
|
|
2
|
+
export const funnelChartClasses = {
|
|
3
|
+
root: 'm-0 grid list-none p-0',
|
|
4
|
+
bar: 'relative flex items-center gap-3 overflow-hidden rounded-lg border border-border bg-background px-3.5 py-3',
|
|
5
|
+
fill: 'absolute inset-y-0 left-0 border-r-2 border-primary bg-accent',
|
|
6
|
+
chevron: 'relative shrink-0 text-primary',
|
|
7
|
+
text: 'relative grid min-w-0 flex-1',
|
|
8
|
+
label: 'text-[13px] font-bold text-foreground',
|
|
9
|
+
description: 'text-[11px] text-muted-foreground',
|
|
10
|
+
figure: 'relative grid text-right',
|
|
11
|
+
value: 'text-[15px] font-bold tabular-nums text-foreground',
|
|
12
|
+
share: 'text-[10.5px] text-muted-foreground',
|
|
13
|
+
transition: 'flex items-center gap-2 py-1.5 pl-[18px] text-[11px] text-muted-foreground',
|
|
14
|
+
conversion: 'font-semibold text-success',
|
|
15
|
+
dropped: 'font-semibold text-destructive',
|
|
16
|
+
} as const
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export interface FunnelChartStep {
|
|
2
|
+
/** Stable key. */
|
|
3
|
+
id: string
|
|
4
|
+
/** Step name. */
|
|
5
|
+
label: string
|
|
6
|
+
/** Secondary line under the label. */
|
|
7
|
+
description?: string
|
|
8
|
+
/** Count at this step. */
|
|
9
|
+
value: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface FunnelChartTransition {
|
|
13
|
+
/** Percent (0–100) of this step that reached the next one. */
|
|
14
|
+
conversion: number
|
|
15
|
+
/** Absolute count lost between this step and the next. */
|
|
16
|
+
dropped: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface FunnelChartRow {
|
|
20
|
+
step: FunnelChartStep
|
|
21
|
+
/** Percent (0–100) of the first step's value. */
|
|
22
|
+
shareOfTop: number
|
|
23
|
+
/** Width the bar draws at, in percent — never below `minBarPercent`. */
|
|
24
|
+
barPercent: number
|
|
25
|
+
/** Transition to the following step; absent on the last step. */
|
|
26
|
+
next?: FunnelChartTransition
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Smallest bar width in percent, so a tiny step stays visible. */
|
|
30
|
+
export const DEFAULT_FUNNEL_MIN_BAR_PERCENT = 6
|
|
31
|
+
|
|
32
|
+
const percentOf = (part: number, whole: number) => (whole ? (part / whole) * 100 : 0)
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Derive every row of a stepped conversion funnel: each step's share of the top
|
|
36
|
+
* step, the bar width it draws at, and between two steps the continue-rate and
|
|
37
|
+
* how many were lost. Pure and deterministic.
|
|
38
|
+
*/
|
|
39
|
+
export function computeFunnel(
|
|
40
|
+
steps: FunnelChartStep[],
|
|
41
|
+
minBarPercent: number = DEFAULT_FUNNEL_MIN_BAR_PERCENT,
|
|
42
|
+
): FunnelChartRow[] {
|
|
43
|
+
const top = steps.length ? steps[0].value : 0
|
|
44
|
+
return steps.map((step, i) => {
|
|
45
|
+
const shareOfTop = percentOf(step.value, top)
|
|
46
|
+
const following = steps[i + 1]
|
|
47
|
+
return {
|
|
48
|
+
step,
|
|
49
|
+
shareOfTop,
|
|
50
|
+
barPercent: Math.min(100, Math.max(shareOfTop, minBarPercent)),
|
|
51
|
+
next: following
|
|
52
|
+
? {
|
|
53
|
+
conversion: percentOf(following.value, step.value),
|
|
54
|
+
dropped: step.value - following.value,
|
|
55
|
+
}
|
|
56
|
+
: undefined,
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Default copy + number formatting; every one is overridable by a prop. */
|
|
62
|
+
export const funnelChartDefaults = {
|
|
63
|
+
formatValue: (value: number) => value.toLocaleString('en-US'),
|
|
64
|
+
formatPercent: (percent: number) => `${percent.toFixed(1)}%`,
|
|
65
|
+
shareLabel: (share: string) => `${share} of top`,
|
|
66
|
+
conversionLabel: (percent: string) => `${percent} continue`,
|
|
67
|
+
droppedLabel: (count: string) => `−${count} dropped`,
|
|
68
|
+
}
|
package/dist/index.ts
CHANGED
|
@@ -237,3 +237,9 @@ export * from './astro-audience-feature-card/index.ts'
|
|
|
237
237
|
export * from './astro-sortable-list/index.ts'
|
|
238
238
|
export * from './astro-kanban-board/index.ts'
|
|
239
239
|
export * from './astro-slot-picker/index.ts'
|
|
240
|
+
export * from './astro-action-list/index.ts'
|
|
241
|
+
export * from './astro-funnel-chart/index.ts'
|
|
242
|
+
export * from './astro-line-chart/index.ts'
|
|
243
|
+
export * from './astro-page-header/index.ts'
|
|
244
|
+
export * from './astro-table/index.ts'
|
|
245
|
+
export * from './astro-native-select/index.ts'
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { cva } from '../shared/index.ts'
|
|
2
2
|
|
|
3
3
|
export const inputVariants = cva({
|
|
4
|
-
base: 'flex
|
|
4
|
+
base: 'flex w-full rounded-md border border-input bg-transparent px-3 py-1 shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
|
|
5
5
|
variants: {
|
|
6
6
|
size: {
|
|
7
7
|
sm: 'h-8 text-xs',
|
|
8
|
-
default: 'h-9',
|
|
8
|
+
default: 'h-9 text-sm',
|
|
9
9
|
lg: 'h-10 text-base',
|
|
10
10
|
},
|
|
11
11
|
validationState: {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export {
|
|
2
|
+
computeLineChart,
|
|
3
|
+
niceScale,
|
|
4
|
+
tooltipLeftPercent,
|
|
5
|
+
nextLineChartIndex,
|
|
6
|
+
lineChartColor,
|
|
7
|
+
DEFAULT_LINE_CHART_PADDING,
|
|
8
|
+
DEFAULT_LINE_CHART_WIDTH,
|
|
9
|
+
DEFAULT_LINE_CHART_HEIGHT,
|
|
10
|
+
type LineChartSeries,
|
|
11
|
+
type LineChartPadding,
|
|
12
|
+
type LineChartOptions,
|
|
13
|
+
type LineChartTick,
|
|
14
|
+
type LineChartXLabel,
|
|
15
|
+
type LineChartPath,
|
|
16
|
+
type LineChartColumn,
|
|
17
|
+
type LineChartGeometry,
|
|
18
|
+
} from './line-chart.js'
|
|
19
|
+
|
|
20
|
+
export { lineChartClasses } from './line-chart.styles.js'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Token-class styles for LineChart; no colour literals. */
|
|
2
|
+
export const lineChartClasses = {
|
|
3
|
+
root: 'relative',
|
|
4
|
+
svg: 'block w-full rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
5
|
+
gridline: 'stroke-border',
|
|
6
|
+
axis: 'fill-muted-foreground text-[9px]',
|
|
7
|
+
crosshair: 'stroke-border',
|
|
8
|
+
marker: 'stroke-card',
|
|
9
|
+
tooltip:
|
|
10
|
+
'pointer-events-none absolute z-10 -translate-x-1/2 whitespace-nowrap rounded-md bg-tertiary px-2.5 py-2 text-[11px] text-tertiary-foreground shadow-lg',
|
|
11
|
+
tooltipLabel: 'mb-1 opacity-60',
|
|
12
|
+
tooltipRow: 'flex justify-between gap-2',
|
|
13
|
+
legend: 'mt-2.5 flex flex-wrap gap-3.5',
|
|
14
|
+
legendItem: 'inline-flex items-center gap-1.5 text-[11px] text-muted-foreground',
|
|
15
|
+
legendSwatch: 'h-[3px] w-2.5 rounded-sm',
|
|
16
|
+
} as const
|