@luxfi/ui 7.0.0 → 7.1.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/alert.cjs +162 -285
- package/dist/alert.js +162 -285
- package/dist/badge.cjs +162 -321
- package/dist/badge.d.cts +3 -7
- package/dist/badge.d.ts +3 -7
- package/dist/badge.js +162 -320
- package/dist/button.cjs +373 -484
- package/dist/button.d.cts +10 -7
- package/dist/button.d.ts +10 -7
- package/dist/button.js +373 -484
- package/dist/close-button.cjs +37 -18
- package/dist/close-button.d.cts +1 -1
- package/dist/close-button.d.ts +1 -1
- package/dist/close-button.js +37 -18
- package/dist/collapsible.cjs +125 -272
- package/dist/collapsible.js +125 -272
- package/dist/dialog.cjs +60 -44
- package/dist/dialog.d.cts +7 -8
- package/dist/dialog.d.ts +7 -8
- package/dist/dialog.js +60 -43
- package/dist/drawer.cjs +37 -13
- package/dist/drawer.js +37 -13
- package/dist/heading.cjs +3 -8
- package/dist/heading.js +3 -8
- package/dist/icon-button.cjs +213 -337
- package/dist/icon-button.d.cts +4 -4
- package/dist/icon-button.d.ts +4 -4
- package/dist/icon-button.js +213 -338
- package/dist/image.cjs +125 -272
- package/dist/image.js +125 -272
- package/dist/index.cjs +604 -700
- package/dist/index.d.cts +0 -3
- package/dist/index.d.ts +0 -3
- package/dist/index.js +604 -698
- package/dist/link.cjs +125 -272
- package/dist/link.js +125 -272
- package/dist/popover.cjs +55 -40
- package/dist/popover.js +55 -39
- package/dist/select.cjs +125 -272
- package/dist/select.js +125 -272
- package/dist/skeleton.cjs +125 -272
- package/dist/skeleton.js +125 -272
- package/dist/table.cjs +127 -274
- package/dist/table.js +127 -274
- package/dist/tag.cjs +184 -306
- package/dist/tag.js +184 -305
- package/dist/tooltip.cjs +22 -21
- package/dist/tooltip.d.cts +2 -3
- package/dist/tooltip.d.ts +2 -3
- package/dist/tooltip.js +22 -20
- package/package.json +9 -7
- package/src/badge.tsx +20 -31
- package/src/button.tsx +304 -238
- package/src/close-button.tsx +48 -22
- package/src/dialog.tsx +37 -47
- package/src/heading.tsx +8 -19
- package/src/icon-button.tsx +129 -104
- package/src/popover.tsx +30 -44
- package/src/skeleton.tsx +96 -144
- package/src/tooltip.tsx +54 -47
package/src/button.tsx
CHANGED
|
@@ -1,199 +1,212 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { cva } from 'class-variance-authority';
|
|
3
|
-
import type { VariantProps } from 'class-variance-authority';
|
|
1
|
+
import { styled, View } from '@hanzogui/core';
|
|
4
2
|
import * as React from 'react';
|
|
5
3
|
|
|
6
|
-
import { cn } from './utils';
|
|
7
|
-
|
|
8
4
|
import { Skeleton } from './skeleton';
|
|
9
5
|
|
|
10
6
|
// ---------------------------------------------------------------------------
|
|
11
|
-
//
|
|
7
|
+
// Spinner (CSS-only, no react-native ActivityIndicator)
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
const SpinnerFrame = styled(View, {
|
|
11
|
+
name: 'ButtonSpinner',
|
|
12
|
+
render: <span />,
|
|
13
|
+
width: 16,
|
|
14
|
+
height: 16,
|
|
15
|
+
borderRadius: 1000,
|
|
16
|
+
borderWidth: 2,
|
|
17
|
+
borderColor: 'currentColor' as any,
|
|
18
|
+
borderBottomColor: 'transparent' as any,
|
|
19
|
+
borderLeftColor: 'transparent' as any,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
function Spinner({ className }: { readonly className?: string }): React.ReactElement {
|
|
23
|
+
return (
|
|
24
|
+
<SpinnerFrame
|
|
25
|
+
role="status"
|
|
26
|
+
aria-label="Loading"
|
|
27
|
+
className={[
|
|
28
|
+
'animate-spin',
|
|
29
|
+
className,
|
|
30
|
+
].filter(Boolean).join(' ')}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
12
35
|
// ---------------------------------------------------------------------------
|
|
13
|
-
//
|
|
14
|
-
// automatically adapt to light/dark mode via the .dark class.
|
|
15
|
-
//
|
|
16
|
-
// Sizes use Tailwind spacing utilities that match the old Chakra recipe values:
|
|
17
|
-
// 2xs → h-5 (20px) xs → h-6 (24px) sm → h-8 (32px) md → h-10 (40px)
|
|
36
|
+
// ButtonFrame — styled base
|
|
18
37
|
// ---------------------------------------------------------------------------
|
|
19
38
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
],
|
|
44
|
-
// --- Outline ---
|
|
45
|
-
outline: [
|
|
46
|
-
'border-2 border-solid bg-transparent',
|
|
47
|
-
'text-[var(--color-button-outline-fg)] border-[var(--color-button-outline-fg)]',
|
|
48
|
-
'hover:bg-transparent hover:text-[var(--color-hover)] hover:border-[var(--color-hover)]',
|
|
49
|
-
],
|
|
50
|
-
// --- Outline Danger ---
|
|
51
|
-
outline_danger: [
|
|
52
|
-
'border-2 border-solid bg-transparent',
|
|
53
|
-
'text-red-600 border-red-600',
|
|
54
|
-
'hover:bg-transparent hover:text-red-500 hover:border-red-500',
|
|
55
|
-
],
|
|
56
|
-
// --- Dropdown ---
|
|
57
|
-
dropdown: [
|
|
58
|
-
'border-2 border-solid bg-transparent',
|
|
59
|
-
'text-[var(--color-button-dropdown-fg)] border-[var(--color-button-dropdown-border)]',
|
|
60
|
-
'hover:bg-transparent hover:text-[var(--color-hover)] hover:border-[var(--color-hover)]',
|
|
61
|
-
'data-[expanded]:bg-transparent data-[expanded]:text-[var(--color-hover)] data-[expanded]:border-[var(--color-hover)]',
|
|
62
|
-
'data-[selected]:bg-[var(--color-selected-control-bg)] data-[selected]:text-[var(--color-selected-control-text)] data-[selected]:border-transparent',
|
|
63
|
-
'data-[selected]:hover:bg-[var(--color-selected-control-bg)]',
|
|
64
|
-
'data-[selected]:hover:text-[var(--color-hover)] data-[selected]:hover:border-transparent',
|
|
65
|
-
'data-[selected]:data-[expanded]:text-[var(--color-hover)]',
|
|
66
|
-
],
|
|
67
|
-
// --- Header ---
|
|
68
|
-
header: [
|
|
69
|
-
'bg-transparent border-2 border-solid',
|
|
70
|
-
'text-[var(--color-button-header-fg)] border-[var(--color-button-header-border)]',
|
|
71
|
-
'hover:bg-transparent hover:text-[var(--color-hover)] hover:border-[var(--color-hover)]',
|
|
72
|
-
// selected
|
|
73
|
-
'data-[selected]:bg-[var(--color-button-header-bg-selected)]',
|
|
74
|
-
'data-[selected]:text-[var(--color-button-header-fg-selected)] data-[selected]:border-transparent data-[selected]:border-0',
|
|
75
|
-
'data-[selected]:hover:bg-[var(--color-button-header-bg-selected)] data-[selected]:hover:text-[var(--color-hover)]',
|
|
76
|
-
'data-[selected]:data-[expanded]:text-[var(--color-hover)]',
|
|
77
|
-
// selected + highlighted
|
|
78
|
-
'data-[selected]:data-[highlighted]:bg-[var(--color-button-header-bg-highlighted)]',
|
|
79
|
-
'data-[selected]:data-[highlighted]:text-[var(--color-button-header-fg-highlighted)]',
|
|
80
|
-
'data-[selected]:data-[highlighted]:border-transparent data-[selected]:data-[highlighted]:border-0',
|
|
81
|
-
'data-[selected]:data-[highlighted]:hover:bg-[var(--color-button-header-bg-highlighted)]',
|
|
82
|
-
'data-[selected]:data-[highlighted]:hover:text-[var(--color-hover)]',
|
|
83
|
-
'data-[selected]:data-[highlighted]:data-[expanded]:text-[var(--color-hover)]',
|
|
84
|
-
],
|
|
85
|
-
// --- Hero ---
|
|
86
|
-
hero: [
|
|
87
|
-
'bg-[var(--color-button-hero-bg)] text-[var(--color-button-hero-fg)]',
|
|
88
|
-
'hover:bg-[var(--color-button-hero-bg-hover)] hover:text-[var(--color-button-hero-fg-hover)]',
|
|
89
|
-
'data-[selected]:bg-[var(--color-button-hero-bg-selected)] data-[selected]:text-[var(--color-button-hero-fg-selected)]',
|
|
90
|
-
'data-[selected]:hover:bg-[var(--color-button-hero-bg-selected)] data-[selected]:hover:text-[var(--color-hover)]',
|
|
91
|
-
'data-[selected]:data-[expanded]:text-[var(--color-hover)]',
|
|
92
|
-
],
|
|
93
|
-
// --- Segmented ---
|
|
94
|
-
segmented: [
|
|
95
|
-
'bg-transparent border-2 border-solid rounded-none',
|
|
96
|
-
'text-[var(--color-button-segmented-fg)] border-[var(--color-selected-control-bg)]',
|
|
97
|
-
'hover:text-[var(--color-hover)]',
|
|
98
|
-
// selected
|
|
99
|
-
'data-[selected]:bg-[var(--color-selected-control-bg)] data-[selected]:text-[var(--color-selected-control-text)]',
|
|
100
|
-
'data-[selected]:hover:bg-[var(--color-selected-control-bg)] data-[selected]:hover:text-[var(--color-selected-control-text)]',
|
|
101
|
-
// border collapse: hide right border except when selected; first/last get rounded
|
|
102
|
-
'[&:not(:last-child)]:border-r-0 data-[selected]:[&:not(:last-child)]:border-r-2',
|
|
103
|
-
'data-[selected]+*:border-l-0',
|
|
104
|
-
'first:rounded-l-md last:rounded-r-md',
|
|
105
|
-
],
|
|
106
|
-
// --- Plain ---
|
|
107
|
-
plain: [
|
|
108
|
-
'bg-transparent text-inherit border-none',
|
|
109
|
-
'hover:bg-transparent',
|
|
110
|
-
],
|
|
111
|
-
// --- Subtle ---
|
|
112
|
-
subtle: [
|
|
113
|
-
'bg-[var(--color-button-subtle-bg)] text-[var(--color-button-subtle-fg)]',
|
|
114
|
-
'hover:bg-[var(--color-button-subtle-bg)] hover:text-[var(--color-hover)]',
|
|
115
|
-
'disabled:bg-[var(--color-button-subtle-bg)] disabled:text-[var(--color-button-subtle-fg)]',
|
|
116
|
-
],
|
|
117
|
-
// --- Link ---
|
|
118
|
-
link: [
|
|
119
|
-
'bg-transparent text-[var(--color-link-primary)] border-none font-normal',
|
|
120
|
-
'px-0 h-auto',
|
|
121
|
-
'hover:bg-transparent hover:text-[var(--color-link-primary-hover)]',
|
|
122
|
-
'disabled:text-[var(--color-text-secondary)]',
|
|
123
|
-
],
|
|
124
|
-
// --- Icon Secondary ---
|
|
125
|
-
icon_secondary: [
|
|
126
|
-
'bg-transparent text-[var(--color-icon-secondary)] border-none',
|
|
127
|
-
'hover:text-[var(--color-hover)]',
|
|
128
|
-
'data-[selected]:bg-[var(--color-selected-control-bg)] data-[selected]:text-[var(--color-selected-control-text)]',
|
|
129
|
-
'data-[selected]:hover:bg-[var(--color-selected-control-bg)] data-[selected]:hover:text-[var(--color-hover)]',
|
|
130
|
-
'data-[selected]:data-[expanded]:text-[var(--color-hover)]',
|
|
131
|
-
'data-[expanded]:text-[var(--color-hover)]',
|
|
132
|
-
],
|
|
133
|
-
// --- Icon Background ---
|
|
134
|
-
icon_background: [
|
|
135
|
-
'bg-[var(--color-button-icon-background-bg)] text-[var(--color-icon-secondary)] border-none',
|
|
136
|
-
'hover:text-[var(--color-hover)]',
|
|
137
|
-
'data-[selected]:bg-[var(--color-selected-control-bg)] data-[selected]:text-[var(--color-selected-control-text)]',
|
|
138
|
-
'data-[selected]:hover:bg-[var(--color-selected-control-bg)] data-[selected]:hover:text-[var(--color-hover)]',
|
|
139
|
-
'data-[selected]:data-[expanded]:text-[var(--color-hover)]',
|
|
140
|
-
'data-[expanded]:text-[var(--color-hover)]',
|
|
141
|
-
],
|
|
142
|
-
// --- Pagination ---
|
|
143
|
-
pagination: [
|
|
144
|
-
'border-2 border-solid bg-transparent',
|
|
145
|
-
'text-[var(--color-button-pagination-fg)] border-[var(--color-button-pagination-border)]',
|
|
146
|
-
'hover:bg-transparent hover:text-[var(--color-hover)] hover:border-[var(--color-hover)]',
|
|
147
|
-
'data-[selected]:bg-[var(--color-selected-control-bg)] data-[selected]:text-[var(--color-selected-control-text)] data-[selected]:border-transparent',
|
|
148
|
-
'data-[selected]:hover:bg-[var(--color-selected-control-bg)]',
|
|
149
|
-
'data-[selected]:hover:text-[var(--color-selected-control-text)] data-[selected]:hover:border-transparent',
|
|
150
|
-
],
|
|
151
|
-
},
|
|
39
|
+
const ButtonFrame = styled(View, {
|
|
40
|
+
name: 'LuxButton',
|
|
41
|
+
render: <button type="button" />,
|
|
42
|
+
role: 'button',
|
|
43
|
+
tabIndex: 0,
|
|
44
|
+
cursor: 'pointer',
|
|
45
|
+
userSelect: 'none' as any,
|
|
46
|
+
display: 'inline-flex',
|
|
47
|
+
alignItems: 'center',
|
|
48
|
+
justifyContent: 'center',
|
|
49
|
+
gap: 0,
|
|
50
|
+
overflow: 'hidden',
|
|
51
|
+
borderRadius: 6,
|
|
52
|
+
// Whitespace nowrap via className since hanzogui doesn't have a direct token
|
|
53
|
+
// Transition via className
|
|
54
|
+
|
|
55
|
+
variants: {
|
|
56
|
+
size: {
|
|
57
|
+
'2xs': { paddingHorizontal: 8, height: 20, minWidth: 20, fontSize: 12, borderRadius: 4, gap: 4 },
|
|
58
|
+
xs: { paddingHorizontal: 8, height: 24, minWidth: 24, fontSize: 14, borderRadius: 4, gap: 4 },
|
|
59
|
+
sm: { paddingHorizontal: 12, height: 32, minWidth: 32, fontSize: 14, borderRadius: 6, gap: 4 },
|
|
60
|
+
md: { paddingHorizontal: 12, height: 40, minWidth: 40, fontSize: 16, borderRadius: 6, gap: 8 },
|
|
61
|
+
},
|
|
152
62
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
63
|
+
disabled: {
|
|
64
|
+
true: {
|
|
65
|
+
opacity: 0.2,
|
|
66
|
+
cursor: 'not-allowed',
|
|
67
|
+
pointerEvents: 'none',
|
|
158
68
|
},
|
|
159
69
|
},
|
|
70
|
+
} as const,
|
|
160
71
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
size: 'md',
|
|
164
|
-
},
|
|
72
|
+
defaultVariants: {
|
|
73
|
+
size: 'md',
|
|
165
74
|
},
|
|
166
|
-
);
|
|
75
|
+
});
|
|
167
76
|
|
|
168
77
|
// ---------------------------------------------------------------------------
|
|
169
|
-
//
|
|
78
|
+
// Variant class maps — CSS custom properties referenced via Tailwind classes.
|
|
79
|
+
// hanzogui styled() handles layout/sizing; className handles color theming
|
|
80
|
+
// so the existing tokens.css contract is honored without duplication.
|
|
170
81
|
// ---------------------------------------------------------------------------
|
|
171
82
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
83
|
+
const VARIANT_CLASSES: Record<string, string> = {
|
|
84
|
+
solid: [
|
|
85
|
+
'bg-[var(--color-button-solid-bg)] text-[var(--color-button-solid-text)]',
|
|
86
|
+
'hover:bg-[var(--color-hover)]',
|
|
87
|
+
'data-[expanded]:bg-[var(--color-hover)]',
|
|
88
|
+
].join(' '),
|
|
89
|
+
|
|
90
|
+
solid_danger: [
|
|
91
|
+
'bg-red-600 text-[var(--color-button-solid-text)]',
|
|
92
|
+
'hover:bg-red-500',
|
|
93
|
+
'data-[expanded]:bg-red-500',
|
|
94
|
+
].join(' '),
|
|
95
|
+
|
|
96
|
+
outline: [
|
|
97
|
+
'border-2 border-solid bg-transparent',
|
|
98
|
+
'text-[var(--color-button-outline-fg)] border-[var(--color-button-outline-fg)]',
|
|
99
|
+
'hover:bg-transparent hover:text-[var(--color-hover)] hover:border-[var(--color-hover)]',
|
|
100
|
+
].join(' '),
|
|
101
|
+
|
|
102
|
+
outline_danger: [
|
|
103
|
+
'border-2 border-solid bg-transparent',
|
|
104
|
+
'text-red-600 border-red-600',
|
|
105
|
+
'hover:bg-transparent hover:text-red-500 hover:border-red-500',
|
|
106
|
+
].join(' '),
|
|
107
|
+
|
|
108
|
+
dropdown: [
|
|
109
|
+
'border-2 border-solid bg-transparent',
|
|
110
|
+
'text-[var(--color-button-dropdown-fg)] border-[var(--color-button-dropdown-border)]',
|
|
111
|
+
'hover:bg-transparent hover:text-[var(--color-hover)] hover:border-[var(--color-hover)]',
|
|
112
|
+
'data-[expanded]:bg-transparent data-[expanded]:text-[var(--color-hover)] data-[expanded]:border-[var(--color-hover)]',
|
|
113
|
+
'data-[selected]:bg-[var(--color-selected-control-bg)] data-[selected]:text-[var(--color-selected-control-text)] data-[selected]:border-transparent',
|
|
114
|
+
'data-[selected]:hover:bg-[var(--color-selected-control-bg)]',
|
|
115
|
+
'data-[selected]:hover:text-[var(--color-hover)] data-[selected]:hover:border-transparent',
|
|
116
|
+
'data-[selected]:data-[expanded]:text-[var(--color-hover)]',
|
|
117
|
+
].join(' '),
|
|
118
|
+
|
|
119
|
+
header: [
|
|
120
|
+
'bg-transparent border-2 border-solid',
|
|
121
|
+
'text-[var(--color-button-header-fg)] border-[var(--color-button-header-border)]',
|
|
122
|
+
'hover:bg-transparent hover:text-[var(--color-hover)] hover:border-[var(--color-hover)]',
|
|
123
|
+
'data-[selected]:bg-[var(--color-button-header-bg-selected)]',
|
|
124
|
+
'data-[selected]:text-[var(--color-button-header-fg-selected)] data-[selected]:border-transparent data-[selected]:border-0',
|
|
125
|
+
'data-[selected]:hover:bg-[var(--color-button-header-bg-selected)] data-[selected]:hover:text-[var(--color-hover)]',
|
|
126
|
+
'data-[selected]:data-[expanded]:text-[var(--color-hover)]',
|
|
127
|
+
'data-[selected]:data-[highlighted]:bg-[var(--color-button-header-bg-highlighted)]',
|
|
128
|
+
'data-[selected]:data-[highlighted]:text-[var(--color-button-header-fg-highlighted)]',
|
|
129
|
+
'data-[selected]:data-[highlighted]:border-transparent data-[selected]:data-[highlighted]:border-0',
|
|
130
|
+
'data-[selected]:data-[highlighted]:hover:bg-[var(--color-button-header-bg-highlighted)]',
|
|
131
|
+
'data-[selected]:data-[highlighted]:hover:text-[var(--color-hover)]',
|
|
132
|
+
'data-[selected]:data-[highlighted]:data-[expanded]:text-[var(--color-hover)]',
|
|
133
|
+
].join(' '),
|
|
134
|
+
|
|
135
|
+
hero: [
|
|
136
|
+
'bg-[var(--color-button-hero-bg)] text-[var(--color-button-hero-fg)]',
|
|
137
|
+
'hover:bg-[var(--color-button-hero-bg-hover)] hover:text-[var(--color-button-hero-fg-hover)]',
|
|
138
|
+
'data-[selected]:bg-[var(--color-button-hero-bg-selected)] data-[selected]:text-[var(--color-button-hero-fg-selected)]',
|
|
139
|
+
'data-[selected]:hover:bg-[var(--color-button-hero-bg-selected)] data-[selected]:hover:text-[var(--color-hover)]',
|
|
140
|
+
'data-[selected]:data-[expanded]:text-[var(--color-hover)]',
|
|
141
|
+
].join(' '),
|
|
142
|
+
|
|
143
|
+
segmented: [
|
|
144
|
+
'bg-transparent border-2 border-solid rounded-none',
|
|
145
|
+
'text-[var(--color-button-segmented-fg)] border-[var(--color-selected-control-bg)]',
|
|
146
|
+
'hover:text-[var(--color-hover)]',
|
|
147
|
+
'data-[selected]:bg-[var(--color-selected-control-bg)] data-[selected]:text-[var(--color-selected-control-text)]',
|
|
148
|
+
'data-[selected]:hover:bg-[var(--color-selected-control-bg)] data-[selected]:hover:text-[var(--color-selected-control-text)]',
|
|
149
|
+
'[&:not(:last-child)]:border-r-0 data-[selected]:[&:not(:last-child)]:border-r-2',
|
|
150
|
+
'data-[selected]+*:border-l-0',
|
|
151
|
+
'first:rounded-l-md last:rounded-r-md',
|
|
152
|
+
].join(' '),
|
|
153
|
+
|
|
154
|
+
plain: 'bg-transparent text-inherit border-none hover:bg-transparent',
|
|
155
|
+
|
|
156
|
+
subtle: [
|
|
157
|
+
'bg-[var(--color-button-subtle-bg)] text-[var(--color-button-subtle-fg)]',
|
|
158
|
+
'hover:bg-[var(--color-button-subtle-bg)] hover:text-[var(--color-hover)]',
|
|
159
|
+
'disabled:bg-[var(--color-button-subtle-bg)] disabled:text-[var(--color-button-subtle-fg)]',
|
|
160
|
+
].join(' '),
|
|
161
|
+
|
|
162
|
+
link: [
|
|
163
|
+
'bg-transparent text-[var(--color-link-primary)] border-none font-normal',
|
|
164
|
+
'px-0 h-auto',
|
|
165
|
+
'hover:bg-transparent hover:text-[var(--color-link-primary-hover)]',
|
|
166
|
+
'disabled:text-[var(--color-text-secondary)]',
|
|
167
|
+
].join(' '),
|
|
168
|
+
|
|
169
|
+
icon_secondary: [
|
|
170
|
+
'bg-transparent text-[var(--color-icon-secondary)] border-none',
|
|
171
|
+
'hover:text-[var(--color-hover)]',
|
|
172
|
+
'data-[selected]:bg-[var(--color-selected-control-bg)] data-[selected]:text-[var(--color-selected-control-text)]',
|
|
173
|
+
'data-[selected]:hover:bg-[var(--color-selected-control-bg)] data-[selected]:hover:text-[var(--color-hover)]',
|
|
174
|
+
'data-[selected]:data-[expanded]:text-[var(--color-hover)]',
|
|
175
|
+
'data-[expanded]:text-[var(--color-hover)]',
|
|
176
|
+
].join(' '),
|
|
177
|
+
|
|
178
|
+
icon_background: [
|
|
179
|
+
'bg-[var(--color-button-icon-background-bg)] text-[var(--color-icon-secondary)] border-none',
|
|
180
|
+
'hover:text-[var(--color-hover)]',
|
|
181
|
+
'data-[selected]:bg-[var(--color-selected-control-bg)] data-[selected]:text-[var(--color-selected-control-text)]',
|
|
182
|
+
'data-[selected]:hover:bg-[var(--color-selected-control-bg)] data-[selected]:hover:text-[var(--color-hover)]',
|
|
183
|
+
'data-[selected]:data-[expanded]:text-[var(--color-hover)]',
|
|
184
|
+
'data-[expanded]:text-[var(--color-hover)]',
|
|
185
|
+
].join(' '),
|
|
186
|
+
|
|
187
|
+
pagination: [
|
|
188
|
+
'border-2 border-solid bg-transparent',
|
|
189
|
+
'text-[var(--color-button-pagination-fg)] border-[var(--color-button-pagination-border)]',
|
|
190
|
+
'hover:bg-transparent hover:text-[var(--color-hover)] hover:border-[var(--color-hover)]',
|
|
191
|
+
'data-[selected]:bg-[var(--color-selected-control-bg)] data-[selected]:text-[var(--color-selected-control-text)] data-[selected]:border-transparent',
|
|
192
|
+
'data-[selected]:hover:bg-[var(--color-selected-control-bg)]',
|
|
193
|
+
'data-[selected]:hover:text-[var(--color-selected-control-text)] data-[selected]:hover:border-transparent',
|
|
194
|
+
].join(' '),
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const BASE_CLASSES = 'font-semibold whitespace-nowrap transition-colors duration-150';
|
|
198
|
+
|
|
199
|
+
type ButtonVariant = keyof typeof VARIANT_CLASSES;
|
|
200
|
+
type ButtonSize = '2xs' | 'xs' | 'sm' | 'md';
|
|
189
201
|
|
|
190
202
|
// ---------------------------------------------------------------------------
|
|
191
|
-
//
|
|
203
|
+
// ButtonProps
|
|
192
204
|
// ---------------------------------------------------------------------------
|
|
193
205
|
|
|
194
206
|
export interface ButtonProps
|
|
195
|
-
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
196
|
-
|
|
207
|
+
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'size'> {
|
|
208
|
+
readonly variant?: ButtonVariant;
|
|
209
|
+
readonly size?: ButtonSize;
|
|
197
210
|
readonly asChild?: boolean;
|
|
198
211
|
readonly loading?: boolean;
|
|
199
212
|
readonly loadingText?: React.ReactNode;
|
|
@@ -224,12 +237,65 @@ export interface ButtonProps
|
|
|
224
237
|
readonly 'data-call-strategy'?: string;
|
|
225
238
|
}
|
|
226
239
|
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
// Chakra shim style builder
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
const SP = 4;
|
|
245
|
+
|
|
246
|
+
function buildShimStyle(props: {
|
|
247
|
+
style?: React.CSSProperties;
|
|
248
|
+
mt?: number | string;
|
|
249
|
+
ml?: number | string;
|
|
250
|
+
mr?: number | string;
|
|
251
|
+
px?: number | string;
|
|
252
|
+
py?: number | string;
|
|
253
|
+
fontWeight?: number | string;
|
|
254
|
+
gap?: number | string;
|
|
255
|
+
flexShrink?: number;
|
|
256
|
+
columnGap?: number | string;
|
|
257
|
+
gridColumn?: number | string;
|
|
258
|
+
gridRow?: string;
|
|
259
|
+
justifySelf?: string;
|
|
260
|
+
alignSelf?: string;
|
|
261
|
+
h?: string;
|
|
262
|
+
w?: string;
|
|
263
|
+
display?: string;
|
|
264
|
+
borderBottomRightRadius?: number | string;
|
|
265
|
+
borderTopRightRadius?: number | string;
|
|
266
|
+
}): React.CSSProperties | undefined {
|
|
267
|
+
const s: React.CSSProperties = { ...props.style };
|
|
268
|
+
if (props.mt !== undefined) s.marginTop = typeof props.mt === 'number' ? `${props.mt * SP}px` : props.mt;
|
|
269
|
+
if (props.ml !== undefined) s.marginLeft = typeof props.ml === 'number' ? `${props.ml * SP}px` : props.ml;
|
|
270
|
+
if (props.mr !== undefined) s.marginRight = typeof props.mr === 'number' ? `${props.mr * SP}px` : props.mr;
|
|
271
|
+
if (props.px !== undefined) { const v = typeof props.px === 'number' ? `${props.px * SP}px` : props.px; s.paddingLeft = v; s.paddingRight = v; }
|
|
272
|
+
if (props.py !== undefined) { const v = typeof props.py === 'number' ? `${props.py * SP}px` : props.py; s.paddingTop = v; s.paddingBottom = v; }
|
|
273
|
+
if (props.fontWeight !== undefined) s.fontWeight = props.fontWeight;
|
|
274
|
+
if (props.gap !== undefined) s.gap = typeof props.gap === 'number' ? `${props.gap * SP}px` : props.gap;
|
|
275
|
+
if (props.flexShrink !== undefined) s.flexShrink = props.flexShrink;
|
|
276
|
+
if (props.columnGap !== undefined) s.columnGap = typeof props.columnGap === 'number' ? `${props.columnGap * SP}px` : props.columnGap;
|
|
277
|
+
if (props.gridColumn !== undefined) s.gridColumn = typeof props.gridColumn === 'number' ? String(props.gridColumn) : props.gridColumn;
|
|
278
|
+
if (props.gridRow) s.gridRow = props.gridRow;
|
|
279
|
+
if (props.justifySelf) s.justifySelf = props.justifySelf;
|
|
280
|
+
if (props.alignSelf) s.alignSelf = props.alignSelf;
|
|
281
|
+
if (props.h) s.height = props.h;
|
|
282
|
+
if (props.w) s.width = props.w;
|
|
283
|
+
if (props.display) s.display = props.display;
|
|
284
|
+
if (props.borderBottomRightRadius !== undefined) s.borderBottomRightRadius = typeof props.borderBottomRightRadius === 'number' ? `${props.borderBottomRightRadius}px` : props.borderBottomRightRadius;
|
|
285
|
+
if (props.borderTopRightRadius !== undefined) s.borderTopRightRadius = typeof props.borderTopRightRadius === 'number' ? `${props.borderTopRightRadius}px` : props.borderTopRightRadius;
|
|
286
|
+
return Object.keys(s).length > 0 ? s : undefined;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ---------------------------------------------------------------------------
|
|
290
|
+
// Button
|
|
291
|
+
// ---------------------------------------------------------------------------
|
|
292
|
+
|
|
227
293
|
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
228
294
|
function Button(props, ref) {
|
|
229
295
|
const {
|
|
230
296
|
className,
|
|
231
|
-
variant,
|
|
232
|
-
size,
|
|
297
|
+
variant = 'solid',
|
|
298
|
+
size = 'md',
|
|
233
299
|
asChild = false,
|
|
234
300
|
loading = false,
|
|
235
301
|
loadingText,
|
|
@@ -241,40 +307,21 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
241
307
|
children,
|
|
242
308
|
style: styleProp,
|
|
243
309
|
// Strip Chakra style props
|
|
244
|
-
mt
|
|
245
|
-
fontWeight
|
|
246
|
-
columnGap
|
|
247
|
-
justifySelf
|
|
248
|
-
h
|
|
249
|
-
borderBottomRightRadius
|
|
310
|
+
mt, ml, mr, px, py,
|
|
311
|
+
fontWeight, gap, flexShrink,
|
|
312
|
+
columnGap, gridColumn, gridRow,
|
|
313
|
+
justifySelf, alignSelf, textStyle: _textStyle,
|
|
314
|
+
h, w, display,
|
|
315
|
+
borderBottomRightRadius, borderTopRightRadius,
|
|
250
316
|
...rest
|
|
251
317
|
} = props;
|
|
252
318
|
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if (_py !== undefined) { const v = typeof _py === 'number' ? `${ _py * SP }px` : _py; shimStyle.paddingTop = v; shimStyle.paddingBottom = v; }
|
|
260
|
-
if (_fontWeight !== undefined) shimStyle.fontWeight = _fontWeight;
|
|
261
|
-
if (_gap !== undefined) shimStyle.gap = typeof _gap === 'number' ? `${ _gap * SP }px` : _gap;
|
|
262
|
-
if (_flexShrink !== undefined) shimStyle.flexShrink = _flexShrink;
|
|
263
|
-
if (_columnGap !== undefined) shimStyle.columnGap = typeof _columnGap === 'number' ? `${ _columnGap * SP }px` : _columnGap;
|
|
264
|
-
if (_gridColumn !== undefined) shimStyle.gridColumn = typeof _gridColumn === 'number' ? String(_gridColumn) : _gridColumn;
|
|
265
|
-
if (_gridRow) shimStyle.gridRow = _gridRow;
|
|
266
|
-
if (_justifySelf) shimStyle.justifySelf = _justifySelf;
|
|
267
|
-
if (_alignSelf) shimStyle.alignSelf = _alignSelf;
|
|
268
|
-
if (_h) shimStyle.height = _h;
|
|
269
|
-
if (_w) shimStyle.width = _w;
|
|
270
|
-
if (_display) shimStyle.display = _display;
|
|
271
|
-
if (_bbrr !== undefined) shimStyle.borderBottomRightRadius = typeof _bbrr === 'number' ? `${ _bbrr }px` : _bbrr;
|
|
272
|
-
if (_btrr !== undefined) shimStyle.borderTopRightRadius = typeof _btrr === 'number' ? `${ _btrr }px` : _btrr;
|
|
273
|
-
const mergedStyle = Object.keys(shimStyle).length > 0 ? shimStyle : undefined;
|
|
274
|
-
|
|
275
|
-
const Comp = asChild ? Slot : 'button';
|
|
276
|
-
|
|
277
|
-
// Build data-* attributes for state-driven variant styles
|
|
319
|
+
const mergedStyle = buildShimStyle({
|
|
320
|
+
style: styleProp, mt, ml, mr, px, py, fontWeight, gap, flexShrink,
|
|
321
|
+
columnGap, gridColumn, gridRow, justifySelf, alignSelf, h, w, display,
|
|
322
|
+
borderBottomRightRadius, borderTopRightRadius,
|
|
323
|
+
});
|
|
324
|
+
|
|
278
325
|
const dataAttrs: Record<string, true> = {};
|
|
279
326
|
if (expanded) dataAttrs['data-expanded'] = true;
|
|
280
327
|
if (selected) dataAttrs['data-selected'] = true;
|
|
@@ -285,30 +332,35 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
285
332
|
|
|
286
333
|
const inner = loading ? (
|
|
287
334
|
<>
|
|
288
|
-
<Spinner/>
|
|
289
|
-
{
|
|
335
|
+
<Spinner />
|
|
336
|
+
{loadingText != null && <span style={{ marginLeft: 8 }}>{loadingText}</span>}
|
|
290
337
|
</>
|
|
291
338
|
) : (
|
|
292
339
|
children
|
|
293
340
|
);
|
|
294
341
|
|
|
342
|
+
const variantClass = VARIANT_CLASSES[variant] ?? VARIANT_CLASSES.solid;
|
|
343
|
+
const combinedClassName = [BASE_CLASSES, variantClass, className].filter(Boolean).join(' ');
|
|
344
|
+
|
|
295
345
|
const button = (
|
|
296
|
-
<
|
|
297
|
-
ref={
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
{
|
|
302
|
-
{
|
|
346
|
+
<ButtonFrame
|
|
347
|
+
ref={ref as any}
|
|
348
|
+
size={size as any}
|
|
349
|
+
disabled={isDisabled || undefined}
|
|
350
|
+
asChild={asChild || undefined}
|
|
351
|
+
className={combinedClassName}
|
|
352
|
+
style={mergedStyle}
|
|
353
|
+
{...dataAttrs}
|
|
354
|
+
{...(rest as any)}
|
|
303
355
|
>
|
|
304
|
-
{
|
|
305
|
-
</
|
|
356
|
+
{inner}
|
|
357
|
+
</ButtonFrame>
|
|
306
358
|
);
|
|
307
359
|
|
|
308
360
|
if (loadingSkeleton) {
|
|
309
361
|
return (
|
|
310
|
-
<Skeleton loading={
|
|
311
|
-
{
|
|
362
|
+
<Skeleton loading={loadingSkeleton} asChild ref={ref as React.ForwardedRef<HTMLDivElement>}>
|
|
363
|
+
{button}
|
|
312
364
|
</Skeleton>
|
|
313
365
|
);
|
|
314
366
|
}
|
|
@@ -317,6 +369,24 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
317
369
|
},
|
|
318
370
|
);
|
|
319
371
|
|
|
372
|
+
// ---------------------------------------------------------------------------
|
|
373
|
+
// buttonVariants — compatibility shim for consumers that call buttonVariants()
|
|
374
|
+
// Returns the className string for a given variant + size combo.
|
|
375
|
+
// ---------------------------------------------------------------------------
|
|
376
|
+
|
|
377
|
+
const SIZE_CLASSES: Record<string, string> = {
|
|
378
|
+
'2xs': 'px-2 h-5 min-w-5 text-xs rounded-sm gap-1',
|
|
379
|
+
xs: 'px-2 h-6 min-w-6 text-sm rounded-sm gap-1',
|
|
380
|
+
sm: 'px-3 h-8 min-w-8 text-sm rounded-md gap-1',
|
|
381
|
+
md: 'px-3 h-10 min-w-10 text-base rounded-md gap-2',
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
export function buttonVariants(opts?: { variant?: string; size?: string }): string {
|
|
385
|
+
const v = opts?.variant ?? 'solid';
|
|
386
|
+
const s = opts?.size ?? 'md';
|
|
387
|
+
return [BASE_CLASSES, VARIANT_CLASSES[v] ?? '', SIZE_CLASSES[s] ?? ''].filter(Boolean).join(' ');
|
|
388
|
+
}
|
|
389
|
+
|
|
320
390
|
// ---------------------------------------------------------------------------
|
|
321
391
|
// ButtonGroup
|
|
322
392
|
// ---------------------------------------------------------------------------
|
|
@@ -326,13 +396,12 @@ export interface ButtonGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
326
396
|
export const ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(
|
|
327
397
|
function ButtonGroup(props, ref) {
|
|
328
398
|
const { className, ...rest } = props;
|
|
329
|
-
|
|
330
399
|
return (
|
|
331
400
|
<div
|
|
332
|
-
ref={
|
|
333
|
-
className={
|
|
401
|
+
ref={ref}
|
|
402
|
+
className={['inline-flex', className].filter(Boolean).join(' ')}
|
|
334
403
|
role="group"
|
|
335
|
-
{
|
|
404
|
+
{...rest}
|
|
336
405
|
/>
|
|
337
406
|
);
|
|
338
407
|
},
|
|
@@ -367,15 +436,15 @@ export const ButtonGroupRadio = React.forwardRef<HTMLDivElement, ButtonGroupRadi
|
|
|
367
436
|
const firstChildValue = React.useMemo(() => {
|
|
368
437
|
const firstChild = Array.isArray(children) ? children[0] : undefined;
|
|
369
438
|
return typeof firstChild?.props.value === 'string' ? firstChild.props.value : undefined;
|
|
370
|
-
}, [
|
|
439
|
+
}, [children]);
|
|
371
440
|
|
|
372
|
-
const [
|
|
441
|
+
const [value, setValue] = React.useState<string | undefined>(defaultValue ?? firstChildValue);
|
|
373
442
|
|
|
374
443
|
const handleItemClick = React.useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
|
|
375
444
|
const v = event.currentTarget.value;
|
|
376
445
|
setValue(v);
|
|
377
446
|
onChange?.(v);
|
|
378
|
-
}, [
|
|
447
|
+
}, [onChange]);
|
|
379
448
|
|
|
380
449
|
const clonedChildren = React.Children.map(children, (child: React.ReactElement<ButtonProps>) => {
|
|
381
450
|
return React.cloneElement(child, {
|
|
@@ -388,24 +457,21 @@ export const ButtonGroupRadio = React.forwardRef<HTMLDivElement, ButtonGroupRadi
|
|
|
388
457
|
const childrenLength = React.Children.count(children);
|
|
389
458
|
|
|
390
459
|
return (
|
|
391
|
-
<Skeleton loading={
|
|
460
|
+
<Skeleton loading={loading}>
|
|
392
461
|
<div
|
|
393
|
-
ref={
|
|
394
|
-
className={
|
|
462
|
+
ref={ref}
|
|
463
|
+
className={[
|
|
395
464
|
'inline-flex gap-0',
|
|
396
|
-
equalWidth
|
|
465
|
+
equalWidth ? 'grid' : '',
|
|
397
466
|
className,
|
|
398
|
-
) }
|
|
399
|
-
style={
|
|
467
|
+
].filter(Boolean).join(' ')}
|
|
468
|
+
style={equalWidth ? { gridTemplateColumns: `repeat(${childrenLength}, 1fr)` } : undefined}
|
|
400
469
|
role="group"
|
|
401
|
-
{
|
|
470
|
+
{...rest}
|
|
402
471
|
>
|
|
403
|
-
{
|
|
472
|
+
{clonedChildren}
|
|
404
473
|
</div>
|
|
405
474
|
</Skeleton>
|
|
406
475
|
);
|
|
407
476
|
},
|
|
408
477
|
);
|
|
409
|
-
|
|
410
|
-
// Re-export the variant function for consumers that need it (e.g., IconButton)
|
|
411
|
-
export { buttonVariants };
|