aplance-ui 1.0.1

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 (132) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +29 -0
  3. package/dist/api.d.ts +175 -0
  4. package/dist/api.js +1954 -0
  5. package/dist/api.js.map +1 -0
  6. package/dist/frameworks/astro/astro.config.snippet.md +23 -0
  7. package/dist/frameworks/astro/postcss.config.mjs.template +20 -0
  8. package/dist/frameworks/astro/vite-inline.snippet.md +22 -0
  9. package/dist/frameworks/electron-vite/vite.config.snippet.md +44 -0
  10. package/dist/frameworks/next/layout-import.snippet.md +19 -0
  11. package/dist/frameworks/next/next.config.snippet.md +15 -0
  12. package/dist/frameworks/next/postcss.config.js.template +16 -0
  13. package/dist/frameworks/tanstack-start/postcss.config.ts.template +17 -0
  14. package/dist/frameworks/tanstack-start/vite-inline.snippet.md +22 -0
  15. package/dist/frameworks/vite-react/postcss.config.ts.template +16 -0
  16. package/dist/frameworks/vite-react/vite-inline.snippet.md +20 -0
  17. package/dist/frameworks/vite-react/vite.config.ts.template +48 -0
  18. package/dist/index.js +2047 -0
  19. package/dist/index.js.map +1 -0
  20. package/package.json +63 -0
  21. package/registry/components/primitives/icon/icon.module.css +63 -0
  22. package/registry/components/primitives/icon/icon.tsx +53 -0
  23. package/registry/components/primitives/modal/modal-context.ts +19 -0
  24. package/registry/components/primitives/modal/modal.module.css +415 -0
  25. package/registry/components/primitives/modal/modal.tsx +550 -0
  26. package/registry/components/primitives/popover/popover-context.ts +28 -0
  27. package/registry/components/primitives/popover/popover.module.css +28 -0
  28. package/registry/components/primitives/popover/popover.tsx +465 -0
  29. package/registry/components/ui/accordion/accordion.module.css +208 -0
  30. package/registry/components/ui/accordion/accordion.tsx +327 -0
  31. package/registry/components/ui/avatar/avatar.module.css +32 -0
  32. package/registry/components/ui/avatar/avatar.tsx +37 -0
  33. package/registry/components/ui/badge/badge.module.css +103 -0
  34. package/registry/components/ui/badge/badge.tsx +29 -0
  35. package/registry/components/ui/button/button.module.css +216 -0
  36. package/registry/components/ui/button/button.tsx +133 -0
  37. package/registry/components/ui/chart/chart.module.css +168 -0
  38. package/registry/components/ui/chart/chart.tsx +1293 -0
  39. package/registry/components/ui/chart/types.ts +210 -0
  40. package/registry/components/ui/chart/utils.ts +59 -0
  41. package/registry/components/ui/copy-button/copy-button.module.css +21 -0
  42. package/registry/components/ui/copy-button/copy-button.tsx +123 -0
  43. package/registry/components/ui/countdown/countdown.module.css +5 -0
  44. package/registry/components/ui/countdown/countdown.tsx +152 -0
  45. package/registry/components/ui/data-table/checkbox-column.tsx +59 -0
  46. package/registry/components/ui/data-table/data-table.module.css +594 -0
  47. package/registry/components/ui/data-table/data-table.tsx +1109 -0
  48. package/registry/components/ui/data-table/filter-bar.tsx +33 -0
  49. package/registry/components/ui/data-table/pagination-bar.tsx +125 -0
  50. package/registry/components/ui/data-table/types.ts +594 -0
  51. package/registry/components/ui/data-table/useDataTable.tsx +573 -0
  52. package/registry/components/ui/data-table/utils.ts +445 -0
  53. package/registry/components/ui/empty-state/empty-state.module.css +26 -0
  54. package/registry/components/ui/empty-state/empty-state.tsx +32 -0
  55. package/registry/components/ui/input-fields/color-picker/color-palette.module.css +102 -0
  56. package/registry/components/ui/input-fields/color-picker/color-palette.tsx +92 -0
  57. package/registry/components/ui/input-fields/color-picker/color-picker.module.css +29 -0
  58. package/registry/components/ui/input-fields/color-picker/color-picker.tsx +168 -0
  59. package/registry/components/ui/input-fields/common/field-message/field-message.module.css +28 -0
  60. package/registry/components/ui/input-fields/common/field-message/field-message.tsx +39 -0
  61. package/registry/components/ui/input-fields/common/input-field-common.module.css +318 -0
  62. package/registry/components/ui/input-fields/common/input-field-native.module.css +267 -0
  63. package/registry/components/ui/input-fields/common/picker-popover-common.module.css +40 -0
  64. package/registry/components/ui/input-fields/common/select-button/select-button.module.css +9 -0
  65. package/registry/components/ui/input-fields/common/select-button/select-button.tsx +171 -0
  66. package/registry/components/ui/input-fields/common/types.ts +38 -0
  67. package/registry/components/ui/input-fields/custom-select/custom-select.module.css +56 -0
  68. package/registry/components/ui/input-fields/custom-select/custom-select.tsx +403 -0
  69. package/registry/components/ui/input-fields/date-picker/calendar.module.css +311 -0
  70. package/registry/components/ui/input-fields/date-picker/calendar.tsx +186 -0
  71. package/registry/components/ui/input-fields/date-picker/date-picker.module.css +57 -0
  72. package/registry/components/ui/input-fields/date-picker/date-picker.tsx +945 -0
  73. package/registry/components/ui/input-fields/date-picker/types.ts +102 -0
  74. package/registry/components/ui/input-fields/date-picker/utils.ts +236 -0
  75. package/registry/components/ui/input-fields/emoji-picker/emoji-palette.module.css +90 -0
  76. package/registry/components/ui/input-fields/emoji-picker/emoji-palette.tsx +168 -0
  77. package/registry/components/ui/input-fields/emoji-picker/emoji-picker.module.css +8 -0
  78. package/registry/components/ui/input-fields/emoji-picker/emoji-picker.tsx +102 -0
  79. package/registry/components/ui/input-fields/emoji-picker/utils.ts +505 -0
  80. package/registry/components/ui/input-fields/field-group/field-group.module.css +86 -0
  81. package/registry/components/ui/input-fields/field-group/field-group.tsx +86 -0
  82. package/registry/components/ui/input-fields/input/input.tsx +319 -0
  83. package/registry/components/ui/input-fields/media-input/kind-icons.tsx +62 -0
  84. package/registry/components/ui/input-fields/media-input/media-input.module.css +294 -0
  85. package/registry/components/ui/input-fields/media-input/media-input.tsx +327 -0
  86. package/registry/components/ui/input-fields/media-input/media-preview.tsx +101 -0
  87. package/registry/components/ui/input-fields/media-input/types.ts +129 -0
  88. package/registry/components/ui/input-fields/media-input/useMediaUpload.tsx +302 -0
  89. package/registry/components/ui/input-fields/media-input/utils.ts +325 -0
  90. package/registry/components/ui/input-fields/otp-input/otp-input.module.css +24 -0
  91. package/registry/components/ui/input-fields/otp-input/otp-input.tsx +291 -0
  92. package/registry/components/ui/input-fields/select/select.tsx +118 -0
  93. package/registry/components/ui/input-fields/switch/switch.module.css +70 -0
  94. package/registry/components/ui/input-fields/switch/switch.tsx +59 -0
  95. package/registry/components/ui/input-fields/textarea/textarea.tsx +133 -0
  96. package/registry/components/ui/input-fields/time-picker/time-picker.module.css +57 -0
  97. package/registry/components/ui/input-fields/time-picker/time-picker.tsx +437 -0
  98. package/registry/components/ui/loader/loader.module.css +156 -0
  99. package/registry/components/ui/loader/loader.tsx +102 -0
  100. package/registry/components/ui/stepper/stepper.module.css +294 -0
  101. package/registry/components/ui/stepper/stepper.tsx +197 -0
  102. package/registry/components/ui/tabs/tabs.module.css +150 -0
  103. package/registry/components/ui/tabs/tabs.tsx +134 -0
  104. package/registry/components/ui/toast/toast.module.css +159 -0
  105. package/registry/components/ui/toast/toast.tsx +104 -0
  106. package/registry/components/ui/tooltip/tooltip.module.css +82 -0
  107. package/registry/components/ui/tooltip/tooltip.tsx +72 -0
  108. package/registry/global.d.ts +4 -0
  109. package/registry/hooks/useFocusOutside.tsx +33 -0
  110. package/registry/hooks/useResizeObserver.tsx +32 -0
  111. package/registry/lib/dom.ts +25 -0
  112. package/registry/lib/string.ts +23 -0
  113. package/registry/registry.json +719 -0
  114. package/registry/styles/app.css +240 -0
  115. package/registry/styles/components.css +78 -0
  116. package/registry/styles/fonts/geist-regular.woff2 +0 -0
  117. package/registry/styles/globals.css +7 -0
  118. package/registry/styles/media.css +25 -0
  119. package/registry/styles/utilities.css +90 -0
  120. package/registry/themes/colors/blue.css +37 -0
  121. package/registry/themes/colors/green.css +37 -0
  122. package/registry/themes/colors/rose.css +37 -0
  123. package/registry/themes/colors/slate.css +37 -0
  124. package/registry/themes/colors/stone.css +37 -0
  125. package/registry/themes/colors/violet.css +37 -0
  126. package/registry/themes/colors/zinc.css +37 -0
  127. package/registry/themes/radius/pill.css +8 -0
  128. package/registry/themes/radius/rounded.css +8 -0
  129. package/registry/themes/radius/sharp.css +8 -0
  130. package/registry/themes/spacing/compact.css +17 -0
  131. package/registry/themes/spacing/spacious.css +17 -0
  132. package/registry/tsconfig.json +25 -0
@@ -0,0 +1,210 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ export type ChartDatum = Record<string, string | number | null | undefined>;
4
+
5
+ export type ChartColorMap = Record<string, string>;
6
+
7
+ export type ChartSeriesType = "bar" | "line" | "area";
8
+
9
+ export type AxisFormatter = (value: string | number) => string;
10
+
11
+ export type TooltipValueFormatter = (
12
+ value: string | number,
13
+ meta: { name?: string | number; dataKey?: string | number },
14
+ ) => string;
15
+
16
+ export type TooltipLabelFormatter = (label: string | number) => string;
17
+
18
+ export type ReferenceLineConfig = {
19
+ value: number;
20
+ /** Which axis to draw the line on. Default: "y". */
21
+ axis?: "x" | "y";
22
+ label?: string;
23
+ /** CSS token (e.g. "--color-chart-secondary") or raw CSS color. */
24
+ color?: string;
25
+ strokeDasharray?: string;
26
+ /** Which Y axis to attach to when axis is "y". Default: "left". */
27
+ yAxisId?: "left" | "right";
28
+ };
29
+
30
+ export type ChartSeries = {
31
+ key: string;
32
+ label?: string;
33
+ /** CSS token or raw CSS color. */
34
+ color?: string;
35
+ type?: ChartSeriesType;
36
+ stackId?: string;
37
+ yAxisId?: "left" | "right";
38
+ strokeWidth?: number;
39
+ fillOpacity?: number;
40
+ /** Show dots on data points. Applies to line and area series. Default: false. */
41
+ dot?: boolean;
42
+ /** Connect across null/undefined data gaps. Applies to line and area series. Default: false. */
43
+ connectNulls?: boolean;
44
+ };
45
+
46
+ export type ScatterSeries = {
47
+ key: string;
48
+ label?: string;
49
+ /** CSS token or raw CSS color. */
50
+ color?: string;
51
+ data: ChartDatum[];
52
+ };
53
+
54
+ export const CHART_COLOR_TOKENS = {
55
+ primary: "--color-chart-primary",
56
+ secondary: "--color-chart-secondary",
57
+ tertiary: "--color-chart-tertiary",
58
+ quaternary: "--color-chart-quaternary",
59
+ quinary: "--color-chart-quinary",
60
+ muted: "--color-chart-muted",
61
+ grid: "--color-chart-grid",
62
+ cursor: "--color-chart-cursor",
63
+ } as const;
64
+
65
+ export type ChartColorTokenName = keyof typeof CHART_COLOR_TOKENS;
66
+
67
+ // ─── Base prop shapes ─────────────────────────────────────────────────────────
68
+
69
+ type ChartBaseProps = {
70
+ title: string;
71
+ description?: string;
72
+ height?: number;
73
+ className?: string;
74
+ emptyMessage?: string;
75
+ /** Show a loading indicator in place of the chart. */
76
+ isLoading?: boolean;
77
+ };
78
+
79
+ type CartesianChartBaseProps = ChartBaseProps & {
80
+ data: ChartDatum[];
81
+ categoryKey: string;
82
+ showGrid?: boolean;
83
+ showXAxis?: boolean;
84
+ showYAxis?: boolean;
85
+ showTooltip?: boolean;
86
+ /** Show a color legend below the chart. Default: false. */
87
+ showLegend?: boolean;
88
+ allowDecimals?: boolean;
89
+ xTickFormatter?: AxisFormatter;
90
+ yTickFormatter?: AxisFormatter;
91
+ tooltipValueFormatter?: TooltipValueFormatter;
92
+ tooltipLabelFormatter?: TooltipLabelFormatter;
93
+ /** Horizontal or vertical threshold / target lines drawn across the chart. */
94
+ referenceLines?: ReferenceLineConfig[];
95
+ };
96
+
97
+ // ─── Chart variant props ──────────────────────────────────────────────────────
98
+
99
+ export type BarChartProps = CartesianChartBaseProps & {
100
+ type: "bar";
101
+ dataKey?: string;
102
+ series?: ChartSeries[];
103
+ maxBarSize?: number;
104
+ /** Map of normalized category label → token/color (e.g. "qualified": "--color-chart-secondary"). */
105
+ colors?: ChartColorMap;
106
+ /**
107
+ * Bar orientation. "vertical" = bars grow up (default).
108
+ * "horizontal" = bars grow right (category axis on Y).
109
+ */
110
+ orientation?: "vertical" | "horizontal";
111
+ /** Gap between bar groups as a percent string ("20%") or pixel number. */
112
+ barCategoryGap?: number | string;
113
+ };
114
+
115
+ export type AreaChartProps = CartesianChartBaseProps & {
116
+ type: "area";
117
+ dataKey?: string;
118
+ series?: ChartSeries[];
119
+ /** Token or CSS color for single-series shorthand. */
120
+ color?: string;
121
+ seriesLabel?: string;
122
+ /** Show dots on data points. Default: false. */
123
+ showDots?: boolean;
124
+ /** Connect across null/undefined data gaps. Default: false. */
125
+ connectNulls?: boolean;
126
+ };
127
+
128
+ export type LineChartProps = CartesianChartBaseProps & {
129
+ type: "line";
130
+ dataKey?: string;
131
+ series?: ChartSeries[];
132
+ /** Token or CSS color for single-series shorthand. */
133
+ color?: string;
134
+ seriesLabel?: string;
135
+ curveType?: "linear" | "monotone" | "step";
136
+ /** Show dots on data points. Default: false. */
137
+ showDots?: boolean;
138
+ /** Connect across null/undefined data gaps. Default: false. */
139
+ connectNulls?: boolean;
140
+ };
141
+
142
+ export type ComposedChartProps = CartesianChartBaseProps & {
143
+ type: "composed";
144
+ series: ChartSeries[];
145
+ };
146
+
147
+ export type DonutChartProps = ChartBaseProps & {
148
+ type: "donut";
149
+ data: ChartDatum[];
150
+ dataKey: string;
151
+ nameKey: string;
152
+ /** Map of normalized segment label → token/color. */
153
+ colors?: ChartColorMap;
154
+ centerContent?: ReactNode;
155
+ /** Show color legend below the chart. Default: true. */
156
+ showLegend?: boolean;
157
+ innerRadius?: number;
158
+ outerRadius?: number;
159
+ paddingAngle?: number;
160
+ tooltipValueFormatter?: TooltipValueFormatter;
161
+ tooltipLabelFormatter?: TooltipLabelFormatter;
162
+ };
163
+
164
+ export type RadialBarChartProps = ChartBaseProps & {
165
+ type: "radialbar";
166
+ data: ChartDatum[];
167
+ dataKey: string;
168
+ nameKey: string;
169
+ /** Map of normalized item label → token/color. */
170
+ colors?: ChartColorMap;
171
+ /** Show color legend below the chart. Default: true. */
172
+ showLegend?: boolean;
173
+ /**
174
+ * Domain maximum - all bars scale relative to this value.
175
+ * Defaults to the maximum value found in the dataset.
176
+ */
177
+ maxValue?: number;
178
+ tooltipValueFormatter?: TooltipValueFormatter;
179
+ tooltipLabelFormatter?: TooltipLabelFormatter;
180
+ };
181
+
182
+ export type ScatterChartProps = ChartBaseProps & {
183
+ type: "scatter";
184
+ series: ScatterSeries[];
185
+ /** Data key used for the X axis across all series. */
186
+ xKey: string;
187
+ /** Data key used for the Y axis across all series. */
188
+ yKey: string;
189
+ /** Optional data key for bubble size (Z axis). */
190
+ zKey?: string;
191
+ showGrid?: boolean;
192
+ showXAxis?: boolean;
193
+ showYAxis?: boolean;
194
+ showTooltip?: boolean;
195
+ /** Show color legend below the chart when there are multiple series. Default: false. */
196
+ showLegend?: boolean;
197
+ allowDecimals?: boolean;
198
+ xTickFormatter?: AxisFormatter;
199
+ yTickFormatter?: AxisFormatter;
200
+ tooltipValueFormatter?: TooltipValueFormatter;
201
+ };
202
+
203
+ export type ChartProps =
204
+ | BarChartProps
205
+ | AreaChartProps
206
+ | LineChartProps
207
+ | ComposedChartProps
208
+ | DonutChartProps
209
+ | RadialBarChartProps
210
+ | ScatterChartProps;
@@ -0,0 +1,59 @@
1
+ import { CHART_COLOR_TOKENS } from "./types";
2
+
3
+ const SERIES_COLOR_ORDER = [
4
+ "primary",
5
+ "secondary",
6
+ "tertiary",
7
+ "quaternary",
8
+ "quinary",
9
+ ] as const;
10
+
11
+ const FALLBACK_COLORS = {
12
+ primary: "#3b82f6",
13
+ secondary: "#5f97f7",
14
+ tertiary: "#82adf9",
15
+ quaternary: "#a4c2fb",
16
+ quinary: "#6d79e6",
17
+ muted: "#6b7280",
18
+ grid: "#e5e7eb",
19
+ cursor: "rgba(148, 163, 184, 0.20)",
20
+ } as const;
21
+
22
+ /**
23
+ * Accepts either a CSS variable token (e.g. "--color-chart-primary") or a raw CSS color.
24
+ * Falls back safely when tokens are missing.
25
+ */
26
+ export const resolveColorToken = (tokenOrColor: string | undefined, fallback: string): string => {
27
+ if (!tokenOrColor) return fallback;
28
+
29
+ const value = tokenOrColor.trim();
30
+ if (!value) return fallback;
31
+
32
+ if (!value.startsWith("--")) {
33
+ return value;
34
+ }
35
+
36
+ // Use CSS variable references directly so theme changes update chart colors
37
+ // even when Storybook toggles theme tokens without triggering a React rerender.
38
+ return `var(${value}, ${fallback})`;
39
+ };
40
+
41
+ export const createChartPalette = () => ({
42
+ primary: resolveColorToken(CHART_COLOR_TOKENS.primary, FALLBACK_COLORS.primary),
43
+ secondary: resolveColorToken(CHART_COLOR_TOKENS.secondary, FALLBACK_COLORS.secondary),
44
+ tertiary: resolveColorToken(CHART_COLOR_TOKENS.tertiary, FALLBACK_COLORS.tertiary),
45
+ quaternary: resolveColorToken(CHART_COLOR_TOKENS.quaternary, FALLBACK_COLORS.quaternary),
46
+ quinary: resolveColorToken(CHART_COLOR_TOKENS.quinary, FALLBACK_COLORS.quinary),
47
+ muted: resolveColorToken(CHART_COLOR_TOKENS.muted, FALLBACK_COLORS.muted),
48
+ grid: resolveColorToken(CHART_COLOR_TOKENS.grid, FALLBACK_COLORS.grid),
49
+ cursor: resolveColorToken(CHART_COLOR_TOKENS.cursor, FALLBACK_COLORS.cursor),
50
+ });
51
+
52
+ export type ChartPalette = ReturnType<typeof createChartPalette>;
53
+
54
+ export const pickSeriesColor = (palette: ChartPalette, index: number): string => {
55
+ const tokenName = SERIES_COLOR_ORDER[index % SERIES_COLOR_ORDER.length];
56
+ return palette[tokenName];
57
+ };
58
+
59
+ export const toLookupKey = (value: unknown): string => String(value ?? "").trim().toLowerCase();
@@ -0,0 +1,21 @@
1
+ .base {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ }
5
+
6
+ .icon {
7
+ flex-shrink: 0;
8
+ }
9
+
10
+ .preview {
11
+ max-width: 20ch;
12
+ overflow: hidden;
13
+ text-overflow: ellipsis;
14
+ white-space: nowrap;
15
+ }
16
+
17
+ .token_preview {
18
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas,
19
+ "Liberation Mono", "Courier New", monospace;
20
+ letter-spacing: 0.01em;
21
+ }
@@ -0,0 +1,123 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import clsx from "clsx";
3
+ import { Button } from "../button/button";
4
+ import { IconCopy, IconCheck } from "@tabler/icons-react";
5
+ import type { ButtonLayout, ButtonSize, ButtonVariant } from "../button/button";
6
+ import s from "./copy-button.module.css";
7
+
8
+ export type CopyPreview = "none" | "truncate" | "mask-token";
9
+
10
+ export type CopyButtonProps = {
11
+ value: string;
12
+ className?: string;
13
+ buttonVariant?: ButtonVariant;
14
+ buttonSize?: ButtonSize;
15
+ buttonLayout?: ButtonLayout;
16
+ variant?: ButtonVariant;
17
+ size?: ButtonSize;
18
+ layout?: ButtonLayout;
19
+ preview?: CopyPreview;
20
+ previewMaxLength?: number;
21
+ };
22
+
23
+ const maskToken = (raw: string): string => {
24
+ const value = raw.trim();
25
+ if (value.length <= 8) return "*".repeat(Math.max(value.length, 4));
26
+ return `${value.slice(0, 4)}****${value.slice(-4)}`;
27
+ };
28
+
29
+ const previewValue = (value: string, preview: CopyPreview, maxLength: number): string | null => {
30
+ if (preview === "none") return null;
31
+ if (preview === "mask-token") return maskToken(value);
32
+ if (value.length <= maxLength) return value;
33
+ return `${value.slice(0, maxLength)}...`;
34
+ };
35
+
36
+ export function CopyButton({
37
+ value,
38
+ className,
39
+ buttonVariant,
40
+ buttonSize,
41
+ buttonLayout,
42
+ variant,
43
+ size,
44
+ layout,
45
+ preview = "none",
46
+ previewMaxLength = 24,
47
+ }: CopyButtonProps) {
48
+ const [copied, setCopied] = useState(false);
49
+ const timeoutRef = useRef<number | null>(null);
50
+
51
+ useEffect(() => {
52
+ return () => {
53
+ if (timeoutRef.current !== null) {
54
+ window.clearTimeout(timeoutRef.current);
55
+ }
56
+ };
57
+ }, []);
58
+
59
+ const copyWithFallback = async (text: string): Promise<boolean> => {
60
+ try {
61
+ if (navigator.clipboard?.writeText) {
62
+ await navigator.clipboard.writeText(text);
63
+ return true;
64
+ }
65
+ } catch {
66
+ // fallback below
67
+ }
68
+
69
+ const area = document.createElement("textarea");
70
+ area.value = text;
71
+ area.setAttribute("readonly", "");
72
+ area.style.position = "fixed";
73
+ area.style.opacity = "0";
74
+ document.body.appendChild(area);
75
+ area.select();
76
+ const success = document.execCommand("copy");
77
+ document.body.removeChild(area);
78
+ return success;
79
+ };
80
+
81
+ const handleCopy = async () => {
82
+ const success = await copyWithFallback(value);
83
+ if (!success) return;
84
+
85
+ setCopied(true);
86
+ if (timeoutRef.current !== null) {
87
+ window.clearTimeout(timeoutRef.current);
88
+ }
89
+ timeoutRef.current = window.setTimeout(() => setCopied(false), 2000);
90
+ };
91
+
92
+ const previewText = previewValue(value, preview, previewMaxLength);
93
+ const hasPreview = previewText !== null;
94
+ const resolvedVariant = buttonVariant ?? variant ?? "bordered";
95
+ const resolvedSize = buttonSize ?? size ?? "default";
96
+ const resolvedLayout = buttonLayout ?? layout ?? (hasPreview ? "default" : "box");
97
+ const showPreview = hasPreview && resolvedLayout !== "box";
98
+
99
+ return (
100
+ <Button
101
+ variant={resolvedVariant}
102
+ size={resolvedSize}
103
+ layout={resolvedLayout}
104
+ onClick={handleCopy}
105
+ type="button"
106
+ className={clsx(s.base, className)}
107
+ title={copied ? "Copied!" : "Copy to clipboard"}
108
+ aria-label={copied ? "Copied value" : "Copy value"}
109
+ >
110
+ {copied ? <IconCheck size={16} className={s.icon} /> : <IconCopy size={16} className={s.icon} />}
111
+ {showPreview && (
112
+ <span
113
+ className={clsx(
114
+ s.preview,
115
+ preview === "mask-token" && s.token_preview,
116
+ )}
117
+ >
118
+ {copied ? "Copied" : previewText}
119
+ </span>
120
+ )}
121
+ </Button>
122
+ );
123
+ }
@@ -0,0 +1,5 @@
1
+ .root {
2
+ font-family: var(--font-family-1);
3
+ font-variant-numeric: tabular-nums;
4
+ color: var(--color-foreground);
5
+ }
@@ -0,0 +1,152 @@
1
+ import {
2
+ useCallback,
3
+ useEffect,
4
+ useImperativeHandle,
5
+ useRef,
6
+ useState,
7
+ type ReactNode,
8
+ type Ref,
9
+ } from "react";
10
+ import clsx from "clsx";
11
+ import s from "./countdown.module.css";
12
+
13
+ export type CountdownFormat =
14
+ | "mm:ss"
15
+ | "ss"
16
+ | "hh:mm:ss"
17
+ | ((remaining: number) => ReactNode);
18
+
19
+ export type CountdownState = {
20
+ remaining: number;
21
+ isRunning: boolean;
22
+ isComplete: boolean;
23
+ formatted: ReactNode;
24
+ start: () => void;
25
+ pause: () => void;
26
+ reset: (newDuration?: number) => void;
27
+ };
28
+
29
+ export type CountdownHandle = Pick<CountdownState, "start" | "pause" | "reset">;
30
+
31
+ export type CountdownProps = {
32
+ /** Initial duration in seconds. */
33
+ duration: number;
34
+ /** Display format. Default: "mm:ss". Pass a function for full control. */
35
+ format?: CountdownFormat;
36
+ /** Auto-start on mount. Default: true. */
37
+ autoStart?: boolean;
38
+ /** Fired every second with the remaining seconds (post-decrement). */
39
+ onTick?: (remaining: number) => void;
40
+ /** Fired once when remaining reaches 0. */
41
+ onComplete?: () => void;
42
+ /**
43
+ * Render-prop children. Called with the countdown state on every tick -
44
+ * lets you swap in any UI (e.g. a Button when complete) without needing
45
+ * a wrapper component. When omitted, the formatted time is rendered.
46
+ */
47
+ children?: (state: CountdownState) => ReactNode;
48
+ className?: string;
49
+ style?: React.CSSProperties;
50
+ ref?: Ref<CountdownHandle>;
51
+ };
52
+
53
+ const pad2 = (n: number) => String(n).padStart(2, "0");
54
+
55
+ const formatRemaining = (
56
+ remaining: number,
57
+ format: CountdownFormat,
58
+ ): ReactNode => {
59
+ if (typeof format === "function") return format(remaining);
60
+ const seconds = remaining % 60;
61
+ const minutes = Math.floor(remaining / 60) % 60;
62
+ const hours = Math.floor(remaining / 3600);
63
+ switch (format) {
64
+ case "ss":
65
+ return String(remaining);
66
+ case "hh:mm:ss":
67
+ return `${pad2(hours)}:${pad2(minutes)}:${pad2(seconds)}`;
68
+ case "mm:ss":
69
+ default:
70
+ return `${pad2(Math.floor(remaining / 60))}:${pad2(seconds)}`;
71
+ }
72
+ };
73
+
74
+ export const Countdown = ({
75
+ duration,
76
+ format = "mm:ss",
77
+ autoStart = true,
78
+ onTick,
79
+ onComplete,
80
+ children,
81
+ className,
82
+ style,
83
+ ref,
84
+ }: CountdownProps) => {
85
+ const [remaining, setRemaining] = useState(duration);
86
+ const [running, setRunning] = useState(autoStart);
87
+
88
+ // Latest-callback refs - keep the interval effect free of callback-identity
89
+ // deps so a parent re-render doesn't tear down and recreate the interval.
90
+ const onTickRef = useRef(onTick);
91
+ const onCompleteRef = useRef(onComplete);
92
+ useEffect(() => {
93
+ onTickRef.current = onTick;
94
+ onCompleteRef.current = onComplete;
95
+ }, [onTick, onComplete]);
96
+
97
+ useEffect(() => {
98
+ if (!running) return;
99
+ if (remaining <= 0) {
100
+ setRunning(false);
101
+ onCompleteRef.current?.();
102
+ return;
103
+ }
104
+ const id = setInterval(() => {
105
+ setRemaining((r) => {
106
+ const next = r - 1;
107
+ onTickRef.current?.(next);
108
+ return next;
109
+ });
110
+ }, 1000);
111
+ return () => clearInterval(id);
112
+ }, [running, remaining]);
113
+
114
+ const start = useCallback(() => setRunning(true), []);
115
+ const pause = useCallback(() => setRunning(false), []);
116
+ const reset = useCallback(
117
+ (newDuration?: number) => {
118
+ setRemaining(newDuration ?? duration);
119
+ setRunning(true);
120
+ },
121
+ [duration],
122
+ );
123
+
124
+ useImperativeHandle(ref, () => ({ start, pause, reset }), [start, pause, reset]);
125
+
126
+ if (typeof children === "function") {
127
+ return (
128
+ <>
129
+ {children({
130
+ remaining,
131
+ isRunning: running,
132
+ isComplete: remaining <= 0,
133
+ formatted: formatRemaining(remaining, format),
134
+ start,
135
+ pause,
136
+ reset,
137
+ })}
138
+ </>
139
+ );
140
+ }
141
+
142
+ return (
143
+ <span
144
+ className={clsx(s.root, className)}
145
+ style={style}
146
+ role="timer"
147
+ aria-live="off"
148
+ >
149
+ {formatRemaining(remaining, format)}
150
+ </span>
151
+ );
152
+ };
@@ -0,0 +1,59 @@
1
+ import type { ColumnDef } from "@tanstack/react-table";
2
+ import { Input } from "../input-fields/input/input";
3
+ import s from "./data-table.module.css";
4
+
5
+ /**
6
+ * Pre-built select-all / per-row checkbox column using `<Input type="checkbox">`.
7
+ * Handles indeterminate state on the header checkbox.
8
+ *
9
+ * @example
10
+ * const cols = useMemo(
11
+ * () => [checkboxColumn<MyRow>(editingRowId), ...rest],
12
+ * [editingRowId]
13
+ * )
14
+ */
15
+ export function checkboxColumn<TData extends { id: string }>(
16
+ editingRowId?: string | null,
17
+ ): ColumnDef<TData> {
18
+ return {
19
+ id: "select",
20
+ // Compact fixed width keeps checkbox cells visually balanced while
21
+ // reserving enough room for focus ring rendering.
22
+ size: 42,
23
+ minSize: 42,
24
+ maxSize: 50,
25
+ enableSorting: false,
26
+ enableHiding: false,
27
+ enableResizing: false,
28
+ enableGrouping: false,
29
+ meta: {
30
+ align: "center",
31
+ truncate: false,
32
+ },
33
+ header: ({ table }) => (
34
+ <Input
35
+ type="checkbox"
36
+ checked={table.getIsAllRowsSelected()}
37
+ className={s.selection_checkbox}
38
+ disabled={editingRowId != null}
39
+ ref={(el: HTMLInputElement | null) => {
40
+ if (el)
41
+ el.indeterminate =
42
+ table.getIsSomeRowsSelected() && !table.getIsAllRowsSelected();
43
+ }}
44
+ onChange={table.getToggleAllRowsSelectedHandler()}
45
+ aria-label="Select all rows"
46
+ />
47
+ ),
48
+ cell: ({ row }) => (
49
+ <Input
50
+ type="checkbox"
51
+ className={s.selection_checkbox}
52
+ checked={row.getIsSelected()}
53
+ disabled={!row.getCanSelect() || editingRowId != null}
54
+ onChange={row.getToggleSelectedHandler()}
55
+ aria-label={`Select row ${row.id}`}
56
+ />
57
+ ),
58
+ };
59
+ }