minka-ds 0.1.5 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minka-ds",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Minka product design system — tokenized component library",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -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: () => setOpen(v => !v) }) : (
197
- <Button variant="default" size="sm" className="h-7 text-caption gap-1.5 px-2.5" onClick={() => setOpen(v => !v)}>
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-dropdown)]",
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
- <StepHeader
226
- title={selectedCategory.label}
227
- onBack={() => { setStep(1); setSearch("") }}
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
- <StepHeader
263
- title={selectedCategory?.label ?? "Date"}
264
- onBack={() => setStep(1)}
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 (no step 2 for amount — opens here directly) */}
296
+ {/* Step 3 — custom amount */}
280
297
  {step === 3 && isAmount && (
281
298
  <>
282
- <StepHeader
283
- title="Amount"
284
- onBack={() => { setStep(1) }}
285
- />
286
- <div className="px-2 pb-2 flex flex-col gap-2">
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
- hasActiveFilters && "[border-bottom-left-radius:0] [border-bottom-right-radius:0]"
92
+ showFilterBar && "[border-bottom-left-radius:0] [border-bottom-right-radius:0]"
90
93
  )}
91
94
  >
92
95
  <InputGroupAddon align="inline-start">
@@ -101,15 +104,15 @@ function SearchBar({
101
104
  onFocus={onFocus}
102
105
  autoComplete="off"
103
106
  />
104
- {(!!value || (hasFilterCategories && !hasActiveFilters) || (!value && !!kbdHint)) && (
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
- {!value && kbdHint && !hasFilterCategories && <Kbd>{kbdHint}</Kbd>}
112
- {hasFilterCategories && !hasActiveFilters && (
114
+ {!value && kbdHint && (!hasFilterCategories || alwaysShowFilterBar) && <Kbd>{kbdHint}</Kbd>}
115
+ {hasFilterCategories && !hasActiveFilters && !alwaysShowFilterBar && (
113
116
  <>
114
117
  {!value && kbdHint && <Kbd>{kbdHint}</Kbd>}
115
118
  <span className="h-4 w-px bg-[var(--color-border-default)]" />
@@ -130,41 +133,102 @@ function SearchBar({
130
133
  )}
131
134
  </InputGroup>
132
135
 
133
- {/* Filter bar — visible only when filters are active */}
134
- {hasActiveFilters && (
136
+ {/* Filter bar */}
137
+ {showFilterBar && (
135
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">
136
- {Object.entries(activeFilters)
137
- .filter(([, vals]) => vals.length > 0)
138
- .map(([categoryId, values]) => {
139
- const cat = filterCategories.find(c => c.id === categoryId)
140
- return (
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 && (
141
188
  <FilterChip
142
- key={categoryId}
143
- label={cat?.label ?? categoryId}
144
- values={values.map(v => ({
145
- label: filterValueLabel(categoryId, v),
146
- onRemove: () => onRemoveFilter?.(categoryId, v),
147
- }))}
148
- onLabelClick={() => {}}
189
+ variant="clear-all"
190
+ className="ml-auto"
191
+ onClear={onClearFilters ?? (() => {})}
149
192
  />
150
- )
151
- })}
152
- <FilterCombobox
153
- categories={filterCategories}
154
- onApply={onApplyFilter ?? (() => {})}
155
- activeFilters={activeFilters}
156
- trigger={({ onClick }) => (
157
- <Button variant="default" size="sm" className="h-7 text-caption gap-1.5 px-2.5" onClick={onClick}>
158
- <PlusIcon className="size-3.5" />
159
- Add
160
- </Button>
161
- )}
162
- />
163
- <FilterChip
164
- variant="clear-all"
165
- className="ml-auto"
166
- onClear={onClearFilters ?? (() => {})}
167
- />
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
+ )}
168
232
  </div>
169
233
  )}
170
234
 
@@ -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.94 0.24 112);
62
- --primitive-lulo-100: oklch(0.92 0.23 112);
63
- --primitive-lulo-200: oklch(0.87 0.22 112);
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);