cortena-ui 0.1.0 → 1.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.
Files changed (68) hide show
  1. package/dist/index.d.ts +2004 -4
  2. package/dist/index.js +5949 -2
  3. package/dist/index.js.map +1 -1
  4. package/package.json +41 -5
  5. package/src/components/accordion.tsx +86 -0
  6. package/src/components/alert.tsx +81 -0
  7. package/src/components/avatar.tsx +53 -0
  8. package/src/components/badge.tsx +51 -0
  9. package/src/components/calendar.tsx +193 -0
  10. package/src/components/card.tsx +85 -0
  11. package/src/components/chart/chart.tsx +193 -0
  12. package/src/components/chart/container.tsx +324 -0
  13. package/src/components/chart/data.ts +101 -0
  14. package/src/components/chart/index.tsx +84 -0
  15. package/src/components/chart/nivo-charts.tsx +227 -0
  16. package/src/components/chart/nivo-theme.ts +136 -0
  17. package/src/components/chart/recharts-charts.tsx +232 -0
  18. package/src/components/chart/tokens.ts +210 -0
  19. package/src/components/chart/types.ts +142 -0
  20. package/src/components/checkbox.tsx +49 -0
  21. package/src/components/chip.tsx +49 -0
  22. package/src/components/combobox.tsx +405 -0
  23. package/src/components/command.tsx +225 -0
  24. package/src/components/data-table/data-table.tsx +731 -0
  25. package/src/components/data-table/export.ts +121 -0
  26. package/src/components/data-table/features.ts +123 -0
  27. package/src/components/data-table/index.tsx +45 -0
  28. package/src/components/data-table/parts.tsx +568 -0
  29. package/src/components/data-table/system-columns.tsx +91 -0
  30. package/src/components/data-table/types.ts +182 -0
  31. package/src/components/data-table/use-data-table.ts +224 -0
  32. package/src/components/data-table/use-server-source.ts +232 -0
  33. package/src/components/date-field.tsx +327 -0
  34. package/src/components/date-picker.tsx +230 -0
  35. package/src/components/dialog.tsx +146 -0
  36. package/src/components/dropdown-menu.tsx +314 -0
  37. package/src/components/dropzone.tsx +260 -0
  38. package/src/components/empty-state.tsx +91 -0
  39. package/src/components/error-banner.tsx +65 -0
  40. package/src/components/field.tsx +250 -0
  41. package/src/components/form.tsx +212 -0
  42. package/src/components/input.tsx +32 -0
  43. package/src/components/kbd.tsx +26 -0
  44. package/src/components/label.tsx +26 -0
  45. package/src/components/loading-skeleton.tsx +153 -0
  46. package/src/components/markdown.tsx +377 -0
  47. package/src/components/page-header.tsx +99 -0
  48. package/src/components/popover.tsx +133 -0
  49. package/src/components/progress.tsx +83 -0
  50. package/src/components/radio-group.tsx +48 -0
  51. package/src/components/scroll-area.tsx +74 -0
  52. package/src/components/section-card.tsx +75 -0
  53. package/src/components/segmented.tsx +81 -0
  54. package/src/components/select.tsx +222 -0
  55. package/src/components/separator.tsx +41 -0
  56. package/src/components/sheet.tsx +161 -0
  57. package/src/components/skeleton.tsx +22 -0
  58. package/src/components/sortable-list.tsx +275 -0
  59. package/src/components/spinner.tsx +32 -0
  60. package/src/components/status-dot.tsx +78 -0
  61. package/src/components/switch.tsx +48 -0
  62. package/src/components/tabs.tsx +109 -0
  63. package/src/components/textarea.tsx +75 -0
  64. package/src/components/toast.tsx +150 -0
  65. package/src/components/toolbar.tsx +63 -0
  66. package/src/components/tooltip.tsx +103 -0
  67. package/src/hooks/use-cortena-theme.ts +268 -0
  68. package/src/index.ts +91 -3
@@ -0,0 +1,210 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import { vizSeries } from "cortena-design/tokens";
5
+
6
+ /**
7
+ * Token plumbing for the chart engines.
8
+ *
9
+ * Recharts draws SVG, so it can take `var(--ds-viz-1)` verbatim and the
10
+ * browser resolves it. Nivo cannot: its canvas renderer hands colours to a
11
+ * 2D context, and its SVG renderer runs colours through d3-color for label and
12
+ * border derivation, and neither understands `var()`. So every Nivo value is
13
+ * read from the `--ds-*` custom property at runtime and handed over as a
14
+ * concrete hex. Nothing in this file is a literal; on the server (no DOM) the
15
+ * `var()` form is returned so SSR markup still resolves in the browser.
16
+ */
17
+
18
+ /** Number of categorical series colours the design system defines (`--ds-viz-1..n`). */
19
+ export const VIZ_SERIES_COUNT = vizSeries.length;
20
+
21
+ /** `var(--ds-viz-N)` for a zero-based series index, wrapping past the palette. */
22
+ export function vizVar(index: number): string {
23
+ const n = VIZ_SERIES_COUNT;
24
+ return `var(--ds-viz-${(((index % n) + n) % n) + 1})`;
25
+ }
26
+
27
+ /** The categorical palette as `var()` references, in series order. */
28
+ export const vizSeriesVars: readonly string[] = Array.from({ length: VIZ_SERIES_COUNT }, (_, i) =>
29
+ vizVar(i),
30
+ );
31
+
32
+ function hasDom(): boolean {
33
+ return typeof document !== "undefined" && typeof getComputedStyle === "function";
34
+ }
35
+
36
+ /** Read a custom property as the browser computes it on `el` (default: `<html>`). */
37
+ export function readToken(name: string, el?: Element | null): string {
38
+ if (!hasDom()) return "";
39
+ return getComputedStyle(el ?? document.documentElement).getPropertyValue(name).trim();
40
+ }
41
+
42
+ /** A `--ds-text-*` size (rem or px) as a pixel number, for engines that want numbers. */
43
+ export function tokenPx(name: string, el?: Element | null): number | undefined {
44
+ const raw = readToken(name, el);
45
+ if (!raw) return undefined;
46
+ const n = Number.parseFloat(raw);
47
+ if (Number.isNaN(n)) return undefined;
48
+ if (raw.endsWith("rem")) {
49
+ const root = Number.parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
50
+ return n * root;
51
+ }
52
+ return n;
53
+ }
54
+
55
+ let probe: CanvasRenderingContext2D | null | undefined;
56
+
57
+ function hex2(n: number): string {
58
+ return n.toString(16).padStart(2, "0");
59
+ }
60
+
61
+ /**
62
+ * Convert any CSS colour the browser understands (oklch, color-mix, rgba…) to
63
+ * `#rrggbb`, by painting it into an offscreen 2D context. Alpha is composited
64
+ * against transparent, so translucent tokens come back opaque; chart fills
65
+ * never use those.
66
+ */
67
+ export function toHex(css: string): string | undefined {
68
+ if (!hasDom() || !css) return undefined;
69
+ if (probe === undefined) {
70
+ probe = document.createElement("canvas").getContext("2d", { willReadFrequently: true });
71
+ }
72
+ if (!probe) return undefined;
73
+ probe.clearRect(0, 0, 1, 1);
74
+ probe.fillStyle = css;
75
+ probe.fillRect(0, 0, 1, 1);
76
+ const d = probe.getImageData(0, 0, 1, 1).data;
77
+ return `#${hex2(d[0] ?? 0)}${hex2(d[1] ?? 0)}${hex2(d[2] ?? 0)}`;
78
+ }
79
+
80
+ /**
81
+ * A token as a concrete colour. Returns hex in the browser; on the server the
82
+ * `var()` form, which is fine for SVG markup and irrelevant for canvas.
83
+ */
84
+ export function resolveColor(token: string, el?: Element | null): string {
85
+ return toHex(readToken(token, el)) ?? `var(${token})`;
86
+ }
87
+
88
+ /** The categorical palette resolved to concrete colours, in series order. */
89
+ export function resolveVizSeries(el?: Element | null): string[] {
90
+ return vizSeriesVars.map((_, i) => resolveColor(`--ds-viz-${i + 1}`, el));
91
+ }
92
+
93
+ /** Relative luminance of a `#rrggbb`, for choosing a scale direction. */
94
+ function luminance(hex: string): number {
95
+ const c = (i: number) => {
96
+ const v = Number.parseInt(hex.slice(i, i + 2), 16) / 255;
97
+ return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
98
+ };
99
+ return 0.2126 * c(1) + 0.7152 * c(3) + 0.0722 * c(5);
100
+ }
101
+
102
+ /**
103
+ * A label colour that reads on a given fill: the design system's ink on light
104
+ * fills, its white on dark ones. Nivo cells and arcs paint their labels with
105
+ * this so a value on a deep sequential cell stays legible.
106
+ */
107
+ export function contrastLabel(el?: Element | null): (fill: string) => string {
108
+ const ink = resolveColor("--ds-base-text-ink", el);
109
+ const white = resolveColor("--ds-base-white", el);
110
+ return (fill: string) => {
111
+ const hex = fill.startsWith("#") && fill.length === 7 ? fill : toHex(fill);
112
+ return hex && luminance(hex) < 0.3 ? white : ink;
113
+ };
114
+ }
115
+
116
+ /** True when the resolved `--ds-background` is dark, whatever set the theme (attribute or OS). */
117
+ export function isDarkSurface(el?: Element | null): boolean {
118
+ const bg = toHex(readToken("--ds-background", el));
119
+ return bg ? luminance(bg) < 0.4 : false;
120
+ }
121
+
122
+ /** Linear interpolation through a list of hex stops; `t` in [0, 1]. */
123
+ export function piecewiseHex(stops: string[]): (t: number) => string {
124
+ const rgb = stops.map((h) => [1, 3, 5].map((i) => Number.parseInt(h.slice(i, i + 2), 16)));
125
+ return (t: number) => {
126
+ const clamped = Math.min(1, Math.max(0, Number.isNaN(t) ? 0 : t));
127
+ const pos = clamped * (rgb.length - 1);
128
+ const i = Math.max(0, Math.min(rgb.length - 2, Math.floor(pos)));
129
+ const f = pos - i;
130
+ const a = rgb[i] ?? [0, 0, 0];
131
+ const b = rgb[i + 1] ?? a;
132
+ const mix = (k: number) => Math.round((a[k] ?? 0) + ((b[k] ?? 0) - (a[k] ?? 0)) * f);
133
+ return `#${hex2(mix(0))}${hex2(mix(1))}${hex2(mix(2))}`;
134
+ };
135
+ }
136
+
137
+ export interface ChartScales {
138
+ /** Low → high, as concrete hex stops. */
139
+ sequential: string[];
140
+ /** Negative → neutral → positive, as concrete hex stops. */
141
+ diverging: string[];
142
+ /** Interpolators over the stops, `t` in [0, 1], for Nivo's `interpolator` configs. */
143
+ sequentialInterpolator: (t: number) => string;
144
+ divergingInterpolator: (t: number) => string;
145
+ /** `n` evenly spaced sequential swatches, for quantised fills like the calendar. */
146
+ sequentialSteps: (n: number) => string[];
147
+ /** Fill for cells with no value. */
148
+ empty: string;
149
+ /** The chart's surface (`--ds-background`), for the gaps between cells and arcs. */
150
+ surface: string;
151
+ }
152
+
153
+ /**
154
+ * Sequential and diverging scales from the tokens.
155
+ *
156
+ * The token set has only the eight categorical `--ds-viz-*` colours and no
157
+ * sequential ramp, so the sequential scale is derived from the
158
+ * `--ds-primary-100..900` ladder: light surfaces run 100 → 700 (pale to deep),
159
+ * dark surfaces run 900 → 400 (deep to bright), so low values sit near the
160
+ * background in both themes. The diverging scale runs `--ds-viz-5` (rose)
161
+ * through the theme's neutral (`--ds-neutral-200` light, `--ds-neutral-700`
162
+ * dark) to `--ds-viz-1` (blue).
163
+ */
164
+ export function chartScales(el?: Element | null): ChartScales {
165
+ const dark = isDarkSurface(el);
166
+ const seqTokens = dark
167
+ ? ["--ds-primary-900", "--ds-primary-800", "--ds-primary-600", "--ds-primary-400"]
168
+ : ["--ds-primary-100", "--ds-primary-300", "--ds-primary-500", "--ds-primary-700"];
169
+ const sequential = seqTokens.map((t) => resolveColor(t, el));
170
+ const diverging = ["--ds-viz-5", dark ? "--ds-neutral-700" : "--ds-neutral-200", "--ds-viz-1"].map(
171
+ (t) => resolveColor(t, el),
172
+ );
173
+ const sequentialInterpolator = piecewiseHex(sequential);
174
+ const divergingInterpolator = piecewiseHex(diverging);
175
+ return {
176
+ sequential,
177
+ diverging,
178
+ sequentialInterpolator,
179
+ divergingInterpolator,
180
+ sequentialSteps: (n) =>
181
+ Array.from({ length: n }, (_, i) => sequentialInterpolator(n === 1 ? 1 : i / (n - 1))),
182
+ empty: resolveColor("--ds-muted", el),
183
+ surface: resolveColor("--ds-background", el),
184
+ };
185
+ }
186
+
187
+ /**
188
+ * A counter that bumps whenever the theme may have changed: the `data-theme`
189
+ * or `class` attribute on `<html>`, or the OS colour scheme. Resolved token
190
+ * values are memoised on it so Nivo re-reads them on a flip.
191
+ */
192
+ export function useThemeVersion(): number {
193
+ const [version, setVersion] = useState(0);
194
+ useEffect(() => {
195
+ if (!hasDom()) return;
196
+ const bump = () => setVersion((v) => v + 1);
197
+ const observer = new MutationObserver(bump);
198
+ observer.observe(document.documentElement, {
199
+ attributes: true,
200
+ attributeFilter: ["data-theme", "class", "style"],
201
+ });
202
+ const media = window.matchMedia?.("(prefers-color-scheme: dark)");
203
+ media?.addEventListener?.("change", bump);
204
+ return () => {
205
+ observer.disconnect();
206
+ media?.removeEventListener?.("change", bump);
207
+ };
208
+ }, []);
209
+ return version;
210
+ }
@@ -0,0 +1,142 @@
1
+ import type * as React from "react";
2
+
3
+ /**
4
+ * Chart types and the data shape each one expects.
5
+ *
6
+ * Engine per type (see cortena-docs/cortenaUI&Design/03-charting.md):
7
+ *
8
+ * | type | engine |
9
+ * | -------------------------------------------------------- | ------------------------- |
10
+ * | bar, line, area, pie, donut, scatter, radar, funnel | Recharts (static import) |
11
+ * | heatmap, calendar, treemap, sunburst | Nivo (lazy, per package) |
12
+ *
13
+ * Data shapes (the webchat renderer's legacy shapes are all accepted):
14
+ *
15
+ * - **bar**, **radar**: `ChartRow[]` — one object per category with a string
16
+ * category key (`indexBy`, inferred as the first string field) and one
17
+ * numeric field per series (`keys`, inferred as every numeric field).
18
+ * `[{ month: "Jan", revenue: 12, cost: 8 }, …]`. The series shape below is
19
+ * also accepted and pivoted.
20
+ * - **line**, **area**: `ChartSeries[]` — `[{ id: "revenue", data: [{ x, y }] }]`
21
+ * (the Nivo line shape the webchat emits). `ChartRow[]` is also accepted.
22
+ * - **pie**, **donut**, **funnel**: `ChartSlice[]` — `[{ id, label?, value }]`.
23
+ * - **scatter** (alias `scatterplot`): `ChartSeries[]` — one series per group.
24
+ * - **heatmap**: `ChartSeries[]` — Nivo heatmap shape, `[{ id: row, data: [{ x: column, y: value }] }]`.
25
+ * - **calendar**: `ChartDay[]` — `[{ day: "2026-01-31", value }]`; `from`/`to`
26
+ * default to the data's range.
27
+ * - **treemap**, **sunburst**: `ChartTree` — Nivo tree, `{ id, children?: [...], value? }`
28
+ * (`name` is accepted in place of `id`).
29
+ */
30
+ export type ChartType =
31
+ | "bar"
32
+ | "line"
33
+ | "area"
34
+ | "pie"
35
+ | "donut"
36
+ | "scatter"
37
+ | "scatterplot"
38
+ | "radar"
39
+ | "funnel"
40
+ | "heatmap"
41
+ | "calendar"
42
+ | "treemap"
43
+ | "sunburst";
44
+
45
+ /** The engine that draws a given type. */
46
+ export type ChartEngine = "recharts" | "nivo";
47
+
48
+ /** Nivo renderer; only the Nivo types honour it (Recharts is SVG only). */
49
+ export type ChartRenderer = "svg" | "canvas";
50
+
51
+ /** One category with a numeric value per series. */
52
+ export type ChartRow = Record<string, string | number | null | undefined>;
53
+
54
+ /** One series of points; x may be a category or a number, y a number. */
55
+ export interface ChartPoint {
56
+ x: string | number;
57
+ y: number | null;
58
+ }
59
+ export interface ChartSeries {
60
+ id: string | number;
61
+ data: ChartPoint[];
62
+ }
63
+
64
+ /** One slice of a pie, donut or funnel. */
65
+ export interface ChartSlice {
66
+ id: string | number;
67
+ label?: string;
68
+ value: number;
69
+ }
70
+
71
+ /** One day of a calendar heatmap. */
72
+ export interface ChartDay {
73
+ day: string;
74
+ value: number;
75
+ }
76
+
77
+ /** A Nivo tree node for treemap and sunburst. */
78
+ export interface ChartTree {
79
+ id?: string | number;
80
+ name?: string;
81
+ value?: number;
82
+ children?: ChartTree[];
83
+ [key: string]: unknown;
84
+ }
85
+
86
+ /** The data type accepted for each chart type. */
87
+ export type ChartDataFor<T extends ChartType> = T extends "bar" | "radar"
88
+ ? ChartRow[] | ChartSeries[]
89
+ : T extends "line" | "area"
90
+ ? ChartSeries[] | ChartRow[]
91
+ : T extends "pie" | "donut" | "funnel"
92
+ ? ChartSlice[]
93
+ : T extends "scatter" | "scatterplot" | "heatmap"
94
+ ? ChartSeries[]
95
+ : T extends "calendar"
96
+ ? ChartDay[]
97
+ : T extends "treemap" | "sunburst"
98
+ ? ChartTree
99
+ : never;
100
+
101
+ /**
102
+ * Per-series presentation, keyed by series id (the `keys` of a row chart, the
103
+ * `id` of a series or slice). Colours default to `var(--ds-viz-1..n)` in key
104
+ * order, so the same category index gets the same colour in every app; set
105
+ * `color` only to pin a category to a semantic colour, and then only to a
106
+ * token (`var(--ds-success)`), never a literal.
107
+ */
108
+ export type ChartConfig = Record<
109
+ string,
110
+ {
111
+ label?: React.ReactNode;
112
+ icon?: React.ComponentType;
113
+ color?: string;
114
+ }
115
+ >;
116
+
117
+ /** Which continuous scale a heatmap or calendar uses. */
118
+ export type ChartScale = "sequential" | "diverging";
119
+
120
+ export const RECHARTS_TYPES: ReadonlySet<ChartType> = new Set<ChartType>([
121
+ "bar",
122
+ "line",
123
+ "area",
124
+ "pie",
125
+ "donut",
126
+ "scatter",
127
+ "scatterplot",
128
+ "radar",
129
+ "funnel",
130
+ ]);
131
+
132
+ export const NIVO_TYPES: ReadonlySet<ChartType> = new Set<ChartType>([
133
+ "heatmap",
134
+ "calendar",
135
+ "treemap",
136
+ "sunburst",
137
+ ]);
138
+
139
+ /** The engine the package uses for a chart type. */
140
+ export function chartEngine(type: ChartType): ChartEngine {
141
+ return NIVO_TYPES.has(type) ? "nivo" : "recharts";
142
+ }
@@ -0,0 +1,49 @@
1
+ "use client";
2
+
3
+ import { Checkbox as BaseCheckbox } from "@base-ui/react/checkbox";
4
+ import { Check, Minus } from "lucide-react";
5
+ import type * as React from "react";
6
+ import { cn } from "@/lib/cn";
7
+
8
+ /**
9
+ * Checkbox.
10
+ *
11
+ * Base UI renders a `<span role="checkbox">` with a hidden `<input>` beside
12
+ * it, so it submits with forms and takes `checked`/`defaultChecked`. The
13
+ * indicator only mounts while checked or indeterminate; the icon inside it is
14
+ * a lucide Check, or a Minus when `indeterminate` is set.
15
+ *
16
+ * The 16px box takes half the small radius: the 8px ladder step would round
17
+ * it into a circle.
18
+ */
19
+ export interface CheckboxProps extends React.ComponentProps<typeof BaseCheckbox.Root> {}
20
+
21
+ function Checkbox({ className, indeterminate, ...props }: CheckboxProps) {
22
+ return (
23
+ <BaseCheckbox.Root
24
+ data-slot="checkbox"
25
+ indeterminate={indeterminate}
26
+ className={cn(
27
+ "peer inline-flex size-4 shrink-0 items-center justify-center rounded-[calc(var(--ds-radius-sm)/2)] border",
28
+ "border-[var(--ds-input)] bg-[var(--ds-background)]",
29
+ "transition-[background-color,border-color,box-shadow] duration-[var(--ds-duration-fast)] ease-[var(--ds-ease-out)]",
30
+ "outline-none focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40",
31
+ "data-[checked]:border-[var(--ds-primary)] data-[checked]:bg-[var(--ds-primary)] data-[checked]:text-[color:var(--ds-primary-foreground)]",
32
+ "data-[indeterminate]:border-[var(--ds-primary)] data-[indeterminate]:bg-[var(--ds-primary)] data-[indeterminate]:text-[color:var(--ds-primary-foreground)]",
33
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
34
+ "aria-invalid:border-[var(--ds-destructive)] aria-invalid:ring-[3px] aria-invalid:ring-[var(--ds-destructive)]/20",
35
+ className,
36
+ )}
37
+ {...props}
38
+ >
39
+ <BaseCheckbox.Indicator
40
+ data-slot="checkbox-indicator"
41
+ className="flex items-center justify-center text-current [&_svg]:size-3.5 [&_svg]:shrink-0"
42
+ >
43
+ {indeterminate ? <Minus strokeWidth={3} /> : <Check strokeWidth={3} />}
44
+ </BaseCheckbox.Indicator>
45
+ </BaseCheckbox.Root>
46
+ );
47
+ }
48
+
49
+ export { Checkbox };
@@ -0,0 +1,49 @@
1
+ "use client";
2
+
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+ import type * as React from "react";
5
+ import { cn } from "@/lib/cn";
6
+
7
+ /**
8
+ * Chip — a pill for filters, tags and selected values. Unlike Badge it keeps
9
+ * sentence case and the body font.
10
+ */
11
+ const chipVariants = cva(
12
+ [
13
+ "inline-flex items-center gap-1.5 whitespace-nowrap border font-medium",
14
+ "rounded-[var(--ds-radius-full)]",
15
+ "transition-[background-color,border-color,color]",
16
+ "duration-[var(--ds-duration-fast)] ease-[var(--ds-ease-out)]",
17
+ "[&_svg]:shrink-0",
18
+ ],
19
+ {
20
+ variants: {
21
+ variant: {
22
+ default:
23
+ "bg-[var(--ds-hover)] border-[var(--ds-border-subtle)] text-[color:var(--ds-muted-foreground)]",
24
+ accent: "bg-[var(--ds-primary-soft)] border-[var(--ds-ring)] text-[color:var(--ds-primary)]",
25
+ outline:
26
+ "bg-transparent border-[var(--ds-border)] text-[color:var(--ds-muted-foreground)]",
27
+ },
28
+ size: {
29
+ default: "px-2.5 py-1 text-[length:var(--ds-text-caption)] [&_svg]:size-3.5",
30
+ sm: "px-2 py-0.5 text-[length:var(--ds-text-caption-sm)] [&_svg]:size-3",
31
+ },
32
+ },
33
+ defaultVariants: {
34
+ variant: "default",
35
+ size: "default",
36
+ },
37
+ },
38
+ );
39
+
40
+ export interface ChipProps
41
+ extends React.ComponentProps<"span">, VariantProps<typeof chipVariants> {}
42
+
43
+ function Chip({ className, variant, size, ...props }: ChipProps) {
44
+ return (
45
+ <span data-slot="chip" className={cn(chipVariants({ variant, size }), className)} {...props} />
46
+ );
47
+ }
48
+
49
+ export { Chip, chipVariants };