minka-ds 0.1.4 → 0.1.7
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/package.json +2 -1
- package/src/components/ui/cell.tsx +3 -2
- package/src/components/ui/data-table.tsx +13 -4
- package/src/components/ui/dropdown-menu.tsx +2 -2
- package/src/components/ui/filter-chip.tsx +1 -1
- package/src/components/ui/filter-combobox.tsx +37 -18
- package/src/components/ui/search-bar.tsx +100 -35
- package/src/components/ui/select.tsx +1 -1
- package/src/components/ui/sonner.tsx +64 -0
- package/src/components/ui/tabs.tsx +2 -2
- package/src/components/ui/tooltip.tsx +27 -1
- package/src/index.ts +1 -0
- package/tokens/gradients.css +13 -0
- package/tokens/primitives.css +30 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "minka-ds",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Minka product design system — tokenized component library",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"lucide-react": "^1.14.0",
|
|
24
24
|
"radix-ui": "^1.4.3",
|
|
25
25
|
"react-day-picker": "^10.0.0",
|
|
26
|
+
"sonner": "^2.0.7",
|
|
26
27
|
"tailwind-merge": "^3.5.0",
|
|
27
28
|
"tw-animate-css": "^1.4.0"
|
|
28
29
|
}
|
|
@@ -10,12 +10,13 @@ import { cn } from "../../lib/utils"
|
|
|
10
10
|
interface TextStackProps {
|
|
11
11
|
primary: React.ReactNode
|
|
12
12
|
secondary?: React.ReactNode
|
|
13
|
+
inline?: boolean
|
|
13
14
|
className?: string
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
function TextStack({ primary, secondary, className }: TextStackProps) {
|
|
17
|
+
function TextStack({ primary, secondary, inline, className }: TextStackProps) {
|
|
17
18
|
return (
|
|
18
|
-
<div className={cn("flex flex-col gap-0.5", className)}>
|
|
19
|
+
<div className={cn(inline ? "flex flex-row items-baseline gap-1.5" : "flex flex-col gap-0.5", className)}>
|
|
19
20
|
<span className="text-caption-light text-[var(--color-text-default)]">{primary}</span>
|
|
20
21
|
{secondary && (
|
|
21
22
|
<span className="text-caption-sm-light text-[var(--color-text-muted)]">{secondary}</span>
|
|
@@ -108,16 +108,19 @@ interface DataTableProps<TData, TValue> {
|
|
|
108
108
|
data: TData[]
|
|
109
109
|
batchSize?: number
|
|
110
110
|
onRowClick?: (row: TData) => void
|
|
111
|
+
variant?: "default" | "compact"
|
|
111
112
|
className?: string
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
function DataTable<TData, TValue>({
|
|
115
116
|
columns,
|
|
116
117
|
data,
|
|
117
|
-
batchSize =
|
|
118
|
+
batchSize = 40,
|
|
118
119
|
onRowClick,
|
|
120
|
+
variant = "default",
|
|
119
121
|
className,
|
|
120
122
|
}: DataTableProps<TData, TValue>) {
|
|
123
|
+
const compact = variant === "compact"
|
|
121
124
|
const [sorting, setSorting] = React.useState<SortingState>([])
|
|
122
125
|
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({})
|
|
123
126
|
const [displayCount, setDisplayCount] = React.useState(batchSize)
|
|
@@ -154,15 +157,21 @@ function DataTable<TData, TValue>({
|
|
|
154
157
|
className="flex-1 min-h-0 overflow-auto rounded-[var(--radius-card)] border border-[var(--color-border-default)] bg-[var(--color-bg-raised)] [&_[data-slot=table-container]]:overflow-visible"
|
|
155
158
|
>
|
|
156
159
|
|
|
157
|
-
<Table className=
|
|
158
|
-
|
|
160
|
+
<Table className={cn(
|
|
161
|
+
"[&_th:first-child]:pl-4 [&_td:first-child]:pl-4",
|
|
162
|
+
compact && "[&_th]:h-7 [&_th]:text-caption [&_th]:text-[var(--color-text-default)] [&_td]:h-11 [&_td]:py-1.5 [&_td]:text-body-sm"
|
|
163
|
+
)}>
|
|
164
|
+
<TableHeader className={cn(
|
|
165
|
+
"sticky top-0 [z-index:var(--z-sticky)]",
|
|
166
|
+
"bg-[var(--color-bg-base)]"
|
|
167
|
+
)}>
|
|
159
168
|
{table.getHeaderGroups().map((headerGroup) => (
|
|
160
169
|
<TableRow key={headerGroup.id}>
|
|
161
170
|
{headerGroup.headers.map((header, index) => (
|
|
162
171
|
<TableHead key={header.id}>
|
|
163
172
|
{index === headerGroup.headers.length - 1 ? (
|
|
164
173
|
<div className="flex items-center justify-between gap-2">
|
|
165
|
-
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
|
174
|
+
<span>{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}</span>
|
|
166
175
|
<DataTableColumnToggle table={table} />
|
|
167
176
|
</div>
|
|
168
177
|
) : (
|
|
@@ -42,7 +42,7 @@ function DropdownMenuContent({
|
|
|
42
42
|
data-slot="dropdown-menu-content"
|
|
43
43
|
sideOffset={sideOffset}
|
|
44
44
|
className={cn(
|
|
45
|
-
"[z-index:var(--z-
|
|
45
|
+
"[z-index:var(--z-floating)] max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto [border-radius:var(--radius-popover)] border border-[var(--color-border-default)] bg-[var(--color-bg-overlay)] p-1 text-body-sm text-[var(--color-text-default)] shadow-[var(--shadow-popover)] data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
46
46
|
className
|
|
47
47
|
)}
|
|
48
48
|
{...props}
|
|
@@ -230,7 +230,7 @@ function DropdownMenuSubContent({
|
|
|
230
230
|
<DropdownMenuPrimitive.SubContent
|
|
231
231
|
data-slot="dropdown-menu-sub-content"
|
|
232
232
|
className={cn(
|
|
233
|
-
"[z-index:var(--z-
|
|
233
|
+
"[z-index:var(--z-floating)] min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden [border-radius:var(--radius-popover)] border border-[var(--color-border-default)] bg-[var(--color-bg-overlay)] p-1 text-[var(--color-text-default)] shadow-[var(--shadow-popover)] data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
234
234
|
className
|
|
235
235
|
)}
|
|
236
236
|
{...props}
|
|
@@ -52,7 +52,7 @@ function FilterChip(props: FilterChipProps) {
|
|
|
52
52
|
{values.map((value, i) => (
|
|
53
53
|
<span
|
|
54
54
|
key={i}
|
|
55
|
-
className="inline-flex items-center gap-1 rounded-full border border-[var(--color-border-default)] px-2 py-0.5 text-caption text-[var(--color-text-default)]"
|
|
55
|
+
className="inline-flex items-center gap-1 rounded-full border border-[var(--color-border-default)] bg-[var(--color-bg-base)] px-2 py-0.5 text-caption text-[var(--color-text-default)]"
|
|
56
56
|
>
|
|
57
57
|
{value.label}
|
|
58
58
|
<button
|
|
@@ -62,6 +62,7 @@ function FilterCombobox({
|
|
|
62
62
|
|
|
63
63
|
const containerRef = React.useRef<HTMLDivElement>(null)
|
|
64
64
|
const searchRef = React.useRef<HTMLInputElement>(null)
|
|
65
|
+
const isSingle = categories.length === 1
|
|
65
66
|
|
|
66
67
|
// Close on outside click
|
|
67
68
|
React.useEffect(() => {
|
|
@@ -91,6 +92,18 @@ function FilterCombobox({
|
|
|
91
92
|
setAmountMax("")
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
function handleToggle() {
|
|
96
|
+
if (open) {
|
|
97
|
+
handleClose()
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
// Single-category: skip step 1 and jump straight to options
|
|
101
|
+
if (isSingle) {
|
|
102
|
+
openCategory(categories[0])
|
|
103
|
+
}
|
|
104
|
+
setOpen(true)
|
|
105
|
+
}
|
|
106
|
+
|
|
94
107
|
function openCategory(cat: FilterCategory) {
|
|
95
108
|
const existing = activeFilters[cat.id] ?? []
|
|
96
109
|
|
|
@@ -193,8 +206,8 @@ function FilterCombobox({
|
|
|
193
206
|
|
|
194
207
|
return (
|
|
195
208
|
<div ref={containerRef} className={cn("relative inline-block", className)}>
|
|
196
|
-
{trigger ? trigger({ open, onClick:
|
|
197
|
-
<Button variant="default" size="sm" className="h-7 text-caption gap-1.5 px-2.5" onClick={
|
|
209
|
+
{trigger ? trigger({ open, onClick: handleToggle }) : (
|
|
210
|
+
<Button variant="default" size="sm" className="h-7 text-caption gap-1.5 px-2.5" onClick={handleToggle}>
|
|
198
211
|
<PlusIcon className="size-3.5" />
|
|
199
212
|
Add filter
|
|
200
213
|
</Button>
|
|
@@ -204,11 +217,11 @@ function FilterCombobox({
|
|
|
204
217
|
<div className={cn(
|
|
205
218
|
dropdownAlign === "right" ? "absolute right-0 top-full mt-1.5 overflow-hidden [border-radius:var(--radius-popover)]" : "absolute left-0 top-full mt-1.5 overflow-hidden [border-radius:var(--radius-popover)]",
|
|
206
219
|
"bg-[var(--color-bg-overlay)] shadow-[var(--shadow-popover)] ring-1 ring-[var(--color-border-subtle)]",
|
|
207
|
-
"[z-index:var(--z-
|
|
220
|
+
"[z-index:var(--z-floating)]",
|
|
208
221
|
step === 3 && isDate ? "w-auto" : "w-56"
|
|
209
222
|
)}>
|
|
210
223
|
|
|
211
|
-
{/* Step 1 — category list */}
|
|
224
|
+
{/* Step 1 — category list (multi-category mode only) */}
|
|
212
225
|
{step === 1 && (
|
|
213
226
|
<ul className="p-1">
|
|
214
227
|
{categories.map(cat => (
|
|
@@ -222,10 +235,12 @@ function FilterCombobox({
|
|
|
222
235
|
{/* Step 2 — value list */}
|
|
223
236
|
{step === 2 && selectedCategory && (
|
|
224
237
|
<>
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
238
|
+
{!isSingle && (
|
|
239
|
+
<StepHeader
|
|
240
|
+
title={selectedCategory.label}
|
|
241
|
+
onBack={() => { setStep(1); setSearch("") }}
|
|
242
|
+
/>
|
|
243
|
+
)}
|
|
229
244
|
{showSearch && (
|
|
230
245
|
<div className="px-1 pt-1">
|
|
231
246
|
<SearchInput ref={searchRef} value={search} onChange={setSearch} />
|
|
@@ -259,10 +274,12 @@ function FilterCombobox({
|
|
|
259
274
|
{/* Step 3 — custom date range */}
|
|
260
275
|
{step === 3 && isDate && (
|
|
261
276
|
<>
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
277
|
+
{!isSingle && (
|
|
278
|
+
<StepHeader
|
|
279
|
+
title={selectedCategory?.label ?? "Date"}
|
|
280
|
+
onBack={() => setStep(1)}
|
|
281
|
+
/>
|
|
282
|
+
)}
|
|
266
283
|
<div className="p-1">
|
|
267
284
|
<Calendar mode="range" selected={dateRange} onSelect={setDateRange} numberOfMonths={1} />
|
|
268
285
|
</div>
|
|
@@ -276,14 +293,16 @@ function FilterCombobox({
|
|
|
276
293
|
</>
|
|
277
294
|
)}
|
|
278
295
|
|
|
279
|
-
{/* Step 3 — custom amount
|
|
296
|
+
{/* Step 3 — custom amount */}
|
|
280
297
|
{step === 3 && isAmount && (
|
|
281
298
|
<>
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
299
|
+
{!isSingle && (
|
|
300
|
+
<StepHeader
|
|
301
|
+
title="Amount"
|
|
302
|
+
onBack={() => setStep(1)}
|
|
303
|
+
/>
|
|
304
|
+
)}
|
|
305
|
+
<div className="p-2 flex flex-col gap-2">
|
|
287
306
|
<Tabs
|
|
288
307
|
value={amountMode}
|
|
289
308
|
onValueChange={v => setAmountMode(v as "exact" | "range")}
|
|
@@ -52,6 +52,7 @@ interface SearchBarProps {
|
|
|
52
52
|
onRemoveFilter?: (categoryId: string, value: CategoryValue) => void
|
|
53
53
|
onClearFilters?: () => void
|
|
54
54
|
filterValueLabel?: (categoryId: string, value: CategoryValue) => string
|
|
55
|
+
alwaysShowFilterBar?: boolean
|
|
55
56
|
|
|
56
57
|
children?: React.ReactNode
|
|
57
58
|
className?: string
|
|
@@ -73,11 +74,13 @@ function SearchBar({
|
|
|
73
74
|
onRemoveFilter,
|
|
74
75
|
onClearFilters,
|
|
75
76
|
filterValueLabel = defaultFilterValueLabel,
|
|
77
|
+
alwaysShowFilterBar = false,
|
|
76
78
|
children,
|
|
77
79
|
className,
|
|
78
80
|
}: SearchBarProps) {
|
|
79
81
|
const hasActiveFilters = Object.values(activeFilters).some(v => v.length > 0)
|
|
80
82
|
const hasFilterCategories = filterCategories.length > 0
|
|
83
|
+
const showFilterBar = hasActiveFilters || alwaysShowFilterBar
|
|
81
84
|
|
|
82
85
|
return (
|
|
83
86
|
<div data-search-bar className={cn("relative flex flex-col", className)}>
|
|
@@ -86,7 +89,7 @@ function SearchBar({
|
|
|
86
89
|
<InputGroup
|
|
87
90
|
className={cn(
|
|
88
91
|
"h-12",
|
|
89
|
-
|
|
92
|
+
showFilterBar && "[border-bottom-left-radius:0] [border-bottom-right-radius:0]"
|
|
90
93
|
)}
|
|
91
94
|
>
|
|
92
95
|
<InputGroupAddon align="inline-start">
|
|
@@ -101,14 +104,15 @@ function SearchBar({
|
|
|
101
104
|
onFocus={onFocus}
|
|
102
105
|
autoComplete="off"
|
|
103
106
|
/>
|
|
104
|
-
{(!!value || (hasFilterCategories && !hasActiveFilters)) && (
|
|
107
|
+
{(!!value || (hasFilterCategories && !hasActiveFilters && !alwaysShowFilterBar) || (!value && !!kbdHint)) && (
|
|
105
108
|
<InputGroupAddon align="inline-end">
|
|
106
109
|
{value && (
|
|
107
110
|
<InputGroupButton size="sm" variant="ghost" onClick={() => onChange("")} className="text-[var(--color-text-muted)] hover:text-[var(--color-text-default)]">
|
|
108
111
|
<XIcon className="size-4" />
|
|
109
112
|
</InputGroupButton>
|
|
110
113
|
)}
|
|
111
|
-
{
|
|
114
|
+
{!value && kbdHint && (!hasFilterCategories || alwaysShowFilterBar) && <Kbd>{kbdHint}</Kbd>}
|
|
115
|
+
{hasFilterCategories && !hasActiveFilters && !alwaysShowFilterBar && (
|
|
112
116
|
<>
|
|
113
117
|
{!value && kbdHint && <Kbd>{kbdHint}</Kbd>}
|
|
114
118
|
<span className="h-4 w-px bg-[var(--color-border-default)]" />
|
|
@@ -129,41 +133,102 @@ function SearchBar({
|
|
|
129
133
|
)}
|
|
130
134
|
</InputGroup>
|
|
131
135
|
|
|
132
|
-
{/* Filter bar
|
|
133
|
-
{
|
|
136
|
+
{/* Filter bar */}
|
|
137
|
+
{showFilterBar && (
|
|
134
138
|
<div className="flex flex-wrap items-center gap-3 [border-bottom-left-radius:var(--radius-card)] [border-bottom-right-radius:var(--radius-card)] border border-t-0 border-[var(--color-border-default)] bg-[var(--color-bg-raised)] px-3 py-2.5">
|
|
135
|
-
{
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
{alwaysShowFilterBar ? (
|
|
140
|
+
// Always-open mode: one trigger per category, active ones show chips
|
|
141
|
+
<>
|
|
142
|
+
<span className="text-caption text-[var(--color-text-default)] shrink-0">Filters:</span>
|
|
143
|
+
{filterCategories.map(cat => {
|
|
144
|
+
const activeVals = activeFilters[cat.id] ?? []
|
|
145
|
+
const hasActive = activeVals.length > 0
|
|
146
|
+
|
|
147
|
+
if (hasActive) {
|
|
148
|
+
return (
|
|
149
|
+
<FilterCombobox
|
|
150
|
+
key={cat.id}
|
|
151
|
+
categories={[cat]}
|
|
152
|
+
onApply={onApplyFilter ?? (() => {})}
|
|
153
|
+
activeFilters={activeFilters}
|
|
154
|
+
trigger={({ onClick }) => (
|
|
155
|
+
<FilterChip
|
|
156
|
+
label={cat.label}
|
|
157
|
+
values={activeVals.map(v => ({
|
|
158
|
+
label: filterValueLabel(cat.id, v),
|
|
159
|
+
onRemove: () => onRemoveFilter?.(cat.id, v),
|
|
160
|
+
}))}
|
|
161
|
+
onLabelClick={onClick}
|
|
162
|
+
/>
|
|
163
|
+
)}
|
|
164
|
+
/>
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<FilterCombobox
|
|
170
|
+
key={cat.id}
|
|
171
|
+
categories={[cat]}
|
|
172
|
+
onApply={onApplyFilter ?? (() => {})}
|
|
173
|
+
activeFilters={activeFilters}
|
|
174
|
+
trigger={({ onClick }) => (
|
|
175
|
+
<button
|
|
176
|
+
type="button"
|
|
177
|
+
onClick={onClick}
|
|
178
|
+
className="inline-flex items-center gap-1 rounded-full border border-[var(--color-border-default)] px-2 py-0.5 text-caption text-[var(--color-text-muted)] hover:text-[var(--color-text-default)] hover:border-[var(--color-border-hover,var(--color-border-default))] transition-colors"
|
|
179
|
+
>
|
|
180
|
+
{cat.label}
|
|
181
|
+
<PlusIcon className="size-3 shrink-0" />
|
|
182
|
+
</button>
|
|
183
|
+
)}
|
|
184
|
+
/>
|
|
185
|
+
)
|
|
186
|
+
})}
|
|
187
|
+
{hasActiveFilters && (
|
|
140
188
|
<FilterChip
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
label: filterValueLabel(categoryId, v),
|
|
145
|
-
onRemove: () => onRemoveFilter?.(categoryId, v),
|
|
146
|
-
}))}
|
|
147
|
-
onLabelClick={() => {}}
|
|
189
|
+
variant="clear-all"
|
|
190
|
+
className="ml-auto"
|
|
191
|
+
onClear={onClearFilters ?? (() => {})}
|
|
148
192
|
/>
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
193
|
+
)}
|
|
194
|
+
</>
|
|
195
|
+
) : (
|
|
196
|
+
// Legacy mode: active chips + generic Add button
|
|
197
|
+
<>
|
|
198
|
+
{Object.entries(activeFilters)
|
|
199
|
+
.filter(([, vals]) => vals.length > 0)
|
|
200
|
+
.map(([categoryId, values]) => {
|
|
201
|
+
const cat = filterCategories.find(c => c.id === categoryId)
|
|
202
|
+
return (
|
|
203
|
+
<FilterChip
|
|
204
|
+
key={categoryId}
|
|
205
|
+
label={cat?.label ?? categoryId}
|
|
206
|
+
values={values.map(v => ({
|
|
207
|
+
label: filterValueLabel(categoryId, v),
|
|
208
|
+
onRemove: () => onRemoveFilter?.(categoryId, v),
|
|
209
|
+
}))}
|
|
210
|
+
onLabelClick={() => {}}
|
|
211
|
+
/>
|
|
212
|
+
)
|
|
213
|
+
})}
|
|
214
|
+
<FilterCombobox
|
|
215
|
+
categories={filterCategories}
|
|
216
|
+
onApply={onApplyFilter ?? (() => {})}
|
|
217
|
+
activeFilters={activeFilters}
|
|
218
|
+
trigger={({ onClick }) => (
|
|
219
|
+
<Button variant="default" size="sm" className="h-7 text-caption gap-1.5 px-2.5" onClick={onClick}>
|
|
220
|
+
<PlusIcon className="size-3.5" />
|
|
221
|
+
Add
|
|
222
|
+
</Button>
|
|
223
|
+
)}
|
|
224
|
+
/>
|
|
225
|
+
<FilterChip
|
|
226
|
+
variant="clear-all"
|
|
227
|
+
className="ml-auto"
|
|
228
|
+
onClear={onClearFilters ?? (() => {})}
|
|
229
|
+
/>
|
|
230
|
+
</>
|
|
231
|
+
)}
|
|
167
232
|
</div>
|
|
168
233
|
)}
|
|
169
234
|
|
|
@@ -69,7 +69,7 @@ function SelectContent({
|
|
|
69
69
|
<SelectPrimitive.Content
|
|
70
70
|
data-slot="select-content"
|
|
71
71
|
className={cn(
|
|
72
|
-
"relative z-[var(--z-
|
|
72
|
+
"relative z-[var(--z-floating)] max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto [border-radius:var(--radius-popover)] border border-[var(--color-border-default)] bg-[var(--color-bg-overlay)] text-[var(--color-text-default)] shadow-[var(--shadow-popover)]",
|
|
73
73
|
"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
74
74
|
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
|
75
75
|
"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Toaster as Sonner, toast } from "sonner"
|
|
4
|
+
|
|
5
|
+
type ToasterProps = React.ComponentProps<typeof Sonner>
|
|
6
|
+
|
|
7
|
+
function Toaster({ ...props }: ToasterProps) {
|
|
8
|
+
return (
|
|
9
|
+
<Sonner
|
|
10
|
+
className="toaster group"
|
|
11
|
+
style={
|
|
12
|
+
{
|
|
13
|
+
"--normal-bg": "var(--primitive-neutral-950)",
|
|
14
|
+
"--normal-text": "var(--primitive-neutral-50)",
|
|
15
|
+
"--normal-border": "var(--primitive-neutral-900)",
|
|
16
|
+
|
|
17
|
+
"--success-bg": "var(--primitive-neutral-950)",
|
|
18
|
+
"--success-text": "var(--primitive-green-400)",
|
|
19
|
+
"--success-border": "var(--primitive-neutral-900)",
|
|
20
|
+
|
|
21
|
+
"--error-bg": "var(--primitive-neutral-950)",
|
|
22
|
+
"--error-text": "var(--primitive-red-400)",
|
|
23
|
+
"--error-border": "var(--primitive-neutral-900)",
|
|
24
|
+
|
|
25
|
+
"--warning-bg": "var(--primitive-neutral-950)",
|
|
26
|
+
"--warning-text": "var(--primitive-yellow-300)",
|
|
27
|
+
"--warning-border": "var(--primitive-neutral-900)",
|
|
28
|
+
|
|
29
|
+
"--info-bg": "var(--primitive-neutral-950)",
|
|
30
|
+
"--info-text": "var(--primitive-blue-400)",
|
|
31
|
+
"--info-border": "var(--primitive-neutral-900)",
|
|
32
|
+
|
|
33
|
+
"--border-radius": "var(--radius-card)",
|
|
34
|
+
"--font": "inherit",
|
|
35
|
+
} as React.CSSProperties
|
|
36
|
+
}
|
|
37
|
+
toastOptions={{
|
|
38
|
+
classNames: {
|
|
39
|
+
toast: "!items-start",
|
|
40
|
+
icon: "mt-0.5",
|
|
41
|
+
title: "text-label !text-[var(--primitive-neutral-50)]",
|
|
42
|
+
description: "text-body-sm !text-[var(--primitive-neutral-300)]",
|
|
43
|
+
success: "[&_[data-icon]]:text-[var(--primitive-green-400)]",
|
|
44
|
+
error: "[&_[data-icon]]:text-[var(--primitive-red-400)]",
|
|
45
|
+
warning: "[&_[data-icon]]:text-[var(--primitive-yellow-300)]",
|
|
46
|
+
info: "[&_[data-icon]]:text-[var(--primitive-neutral-50)]",
|
|
47
|
+
},
|
|
48
|
+
actionButtonStyle: {
|
|
49
|
+
height: "2rem",
|
|
50
|
+
padding: "0 0.75rem",
|
|
51
|
+
borderRadius: "var(--radius-button)",
|
|
52
|
+
border: "1px solid var(--primitive-neutral-700)",
|
|
53
|
+
background: "var(--primitive-neutral-800)",
|
|
54
|
+
color: "var(--primitive-neutral-50)",
|
|
55
|
+
fontSize: "0.875rem",
|
|
56
|
+
fontWeight: "600",
|
|
57
|
+
},
|
|
58
|
+
}}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { Toaster, toast }
|
|
@@ -30,7 +30,7 @@ const tabsListVariants = cva(
|
|
|
30
30
|
{
|
|
31
31
|
variants: {
|
|
32
32
|
variant: {
|
|
33
|
-
default: "[border-radius:var(--radius-card)] bg-[var(--color-bg-
|
|
33
|
+
default: "[border-radius:var(--radius-card)] border border-[var(--color-border-default)] bg-[var(--color-bg-raised)] shadow-xs",
|
|
34
34
|
line: "gap-1 rounded-none bg-transparent",
|
|
35
35
|
},
|
|
36
36
|
},
|
|
@@ -71,7 +71,7 @@ function TabsTrigger({
|
|
|
71
71
|
"focus-visible:border-[var(--color-border-focus)] focus-visible:ring-[3px] focus-visible:ring-[var(--color-border-focus)]/50",
|
|
72
72
|
"disabled:pointer-events-none disabled:text-[var(--color-text-disabled)]",
|
|
73
73
|
// default variant — active tab gets raised surface
|
|
74
|
-
"group-data-[variant=default]/tabs-list:data-[state=active]:bg-[var(--color-bg-
|
|
74
|
+
"group-data-[variant=default]/tabs-list:data-[state=active]:bg-[var(--color-bg-inverted)] group-data-[variant=default]/tabs-list:data-[state=active]:text-[var(--color-bg-raised)] group-data-[variant=default]/tabs-list:data-[state=active]:shadow-[var(--shadow-card)]",
|
|
75
75
|
// line variant — active tab is transparent, underline indicator
|
|
76
76
|
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:text-[var(--color-text-default)] group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none",
|
|
77
77
|
// underline indicator for line variant
|
|
@@ -54,4 +54,30 @@ function TooltipContent({
|
|
|
54
54
|
)
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
function TooltipLabel({
|
|
58
|
+
className,
|
|
59
|
+
...props
|
|
60
|
+
}: React.ComponentProps<"p">) {
|
|
61
|
+
return (
|
|
62
|
+
<p
|
|
63
|
+
data-slot="tooltip-label"
|
|
64
|
+
className={cn("text-caption text-[var(--color-text-inverse)]", className)}
|
|
65
|
+
{...props}
|
|
66
|
+
/>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function TooltipDescription({
|
|
71
|
+
className,
|
|
72
|
+
...props
|
|
73
|
+
}: React.ComponentProps<"p">) {
|
|
74
|
+
return (
|
|
75
|
+
<p
|
|
76
|
+
data-slot="tooltip-description"
|
|
77
|
+
className={cn("text-caption text-[var(--color-text-inverse-muted)]", className)}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider, TooltipLabel, TooltipDescription }
|
package/src/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ export * from "./components/ui/kbd"
|
|
|
27
27
|
export * from "./components/ui/label"
|
|
28
28
|
export * from "./components/ui/pagination"
|
|
29
29
|
export * from "./components/ui/select"
|
|
30
|
+
export * from "./components/ui/sonner"
|
|
30
31
|
export * from "./components/ui/separator"
|
|
31
32
|
export * from "./components/ui/sheet"
|
|
32
33
|
export * from "./components/ui/sidebar"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Gradient tokens — semantic gradient expressions.
|
|
3
|
+
* Import after primitives.css. References semantic tokens (e.g. --color-bg-canvas)
|
|
4
|
+
* which are resolved at paint time, so import order with app globals doesn't matter.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
:root {
|
|
8
|
+
--gradient-page: linear-gradient(180deg, var(--color-bg-base) 0%, var(--color-bg-base) 20%, var(--primitive-neutral-0) 100%);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.dark {
|
|
12
|
+
--gradient-page: linear-gradient(180deg, var(--color-bg-base) 0%, var(--color-bg-base) 20%, var(--primitive-neutral-800) 100%);
|
|
13
|
+
}
|
package/tokens/primitives.css
CHANGED
|
@@ -22,6 +22,18 @@
|
|
|
22
22
|
|
|
23
23
|
/* --- Color scales --- */
|
|
24
24
|
|
|
25
|
+
--primitive-beige-50: oklch(0.990 0.008 98);
|
|
26
|
+
--primitive-beige-100: oklch(0.982 0.030 98);
|
|
27
|
+
--primitive-beige-200: oklch(0.968 0.052 98);
|
|
28
|
+
--primitive-beige-300: oklch(0.948 0.072 98);
|
|
29
|
+
--primitive-beige-400: oklch(0.918 0.083 98);
|
|
30
|
+
--primitive-beige-500: oklch(0.876 0.085 98);
|
|
31
|
+
--primitive-beige-600: oklch(0.792 0.076 98);
|
|
32
|
+
--primitive-beige-700: oklch(0.680 0.062 98);
|
|
33
|
+
--primitive-beige-800: oklch(0.480 0.044 98);
|
|
34
|
+
--primitive-beige-900: oklch(0.310 0.028 98);
|
|
35
|
+
--primitive-beige-950: oklch(0.230 0.020 98);
|
|
36
|
+
|
|
25
37
|
--primitive-red-50: oklch(0.95 0.03 28);
|
|
26
38
|
--primitive-red-100: oklch(0.93 0.05 28);
|
|
27
39
|
--primitive-red-200: oklch(0.89 0.07 28);
|
|
@@ -58,9 +70,9 @@
|
|
|
58
70
|
--primitive-yellow-900: oklch(0.3 0.07 98);
|
|
59
71
|
--primitive-yellow-950: oklch(0.25 0.06 98);
|
|
60
72
|
|
|
61
|
-
--primitive-lulo-50: oklch(0.
|
|
62
|
-
--primitive-lulo-100: oklch(0.
|
|
63
|
-
--primitive-lulo-200: oklch(0.
|
|
73
|
+
--primitive-lulo-50: oklch(0.967 0.099 118);
|
|
74
|
+
--primitive-lulo-100: oklch(0.918 0.136 116);
|
|
75
|
+
--primitive-lulo-200: oklch(0.869 0.173 114);
|
|
64
76
|
--primitive-lulo-300: oklch(0.82 0.21 112);
|
|
65
77
|
--primitive-lulo-400: oklch(0.76 0.2 112);
|
|
66
78
|
--primitive-lulo-500: oklch(0.7 0.18 112);
|
|
@@ -106,6 +118,18 @@
|
|
|
106
118
|
--primitive-sea-900: oklch(0.29 0.07 220);
|
|
107
119
|
--primitive-sea-950: oklch(0.24 0.06 220);
|
|
108
120
|
|
|
121
|
+
--primitive-slate-50: oklch(0.950 0.012 218);
|
|
122
|
+
--primitive-slate-100: oklch(0.932 0.020 218);
|
|
123
|
+
--primitive-slate-200: oklch(0.898 0.032 218);
|
|
124
|
+
--primitive-slate-300: oklch(0.848 0.042 218);
|
|
125
|
+
--primitive-slate-400: oklch(0.762 0.054 218);
|
|
126
|
+
--primitive-slate-500: oklch(0.656 0.063 218);
|
|
127
|
+
--primitive-slate-600: oklch(0.560 0.054 218);
|
|
128
|
+
--primitive-slate-700: oklch(0.472 0.046 218);
|
|
129
|
+
--primitive-slate-800: oklch(0.352 0.034 218);
|
|
130
|
+
--primitive-slate-900: oklch(0.248 0.023 218);
|
|
131
|
+
--primitive-slate-950: oklch(0.198 0.017 218);
|
|
132
|
+
|
|
109
133
|
--primitive-blue-50: oklch(0.95 0.03 257);
|
|
110
134
|
--primitive-blue-100: oklch(0.93 0.04 257);
|
|
111
135
|
--primitive-blue-200: oklch(0.88 0.06 257);
|
|
@@ -222,7 +246,8 @@
|
|
|
222
246
|
--primitive-z-raised: 10;
|
|
223
247
|
--primitive-z-dropdown: 20;
|
|
224
248
|
--primitive-z-overlay: 100;
|
|
225
|
-
--primitive-z-modal:
|
|
226
|
-
--primitive-z-
|
|
249
|
+
--primitive-z-modal: 200;
|
|
250
|
+
--primitive-z-floating: 210;
|
|
251
|
+
--primitive-z-toast: 300;
|
|
227
252
|
--primitive-z-tooltip: 400;
|
|
228
253
|
}
|