@kolkrabbi/kol-shell 0.1.3 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolkrabbi/kol-shell",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "private": false,
5
5
  "description": "KOL application shell — fixed 48px NavRail + AppShell layout root, PageShell/PageHeader scaffolds, ContentFilters catalog organism, GridCard, SettingsScaffold, WalkthroughPanel, ShortcutsOverlay. App chrome (kol-framework owns site chrome). Nav items, content, shortcuts and settings are consumer-injected. Sits above @kolkrabbi/kol-{theme,component,framework}.",
6
6
  "license": "MIT",
@@ -1,4 +1,4 @@
1
- import { useState, useMemo, useRef, useEffect } from 'react'
1
+ import { Fragment, useState, useMemo, useRef, useEffect } from 'react'
2
2
  import { Divider } from '@kolkrabbi/kol-component'
3
3
  import { Icon } from '@kolkrabbi/kol-icons'
4
4
  import TabStrip from './TabStrip.jsx'
@@ -27,6 +27,22 @@ import TabStrip from './TabStrip.jsx'
27
27
  * @param {Array} props.mutuallyExclusiveFilters - filter keys that self-clear
28
28
  * @param {Array} props.customFilterKeys - keys renderItem handles, not this organism
29
29
  * @param {ElementType} props.iconComponent - icon seam (defaults to DS Icon; needs `filter` + `search`)
30
+ * @param {Function} props.renderFilterValue - `(value, isActive, toggle) => node`, one filter value
31
+ * @param {string} props.labelClassName - REPLACES the group label's class (never stacks)
32
+ *
33
+ * **The two rendering seams (0.2.0).** How a filter value and a group label look
34
+ * is the consumer's call, not this organism's. Three publishes in one day went
35
+ * into re-ruling that here — outlined pills vs bare strip items vs chips — none
36
+ * of which is a design-system question: it is what a given app's filter bar
37
+ * should look like. `renderFilterValue` and `labelClassName` end that class of
38
+ * change without a publish.
39
+ *
40
+ * `labelClassName` REPLACES rather than stacks, per the 2026-07-30 law: two
41
+ * equal-specificity type classes on one element are decided by sheet order, so
42
+ * a stacked seam produces different renders in different consumers with no
43
+ * version difference. Same contract as ListingCard's `titleClassName`.
44
+ *
45
+ * Defaults reproduce 0.1.3 exactly — a bump moves nothing.
30
46
  */
31
47
  const ContentFilters = ({
32
48
  items,
@@ -47,6 +63,8 @@ const ContentFilters = ({
47
63
  headerActions,
48
64
  showCountOnlyWhenFiltering = false,
49
65
  iconComponent,
66
+ renderFilterValue,
67
+ labelClassName,
50
68
  }) => {
51
69
  const [activeFilters, setActiveFilters] = useState(new Set())
52
70
  const [isExpanded, setIsExpanded] = useState(false)
@@ -132,12 +150,10 @@ const ContentFilters = ({
132
150
  })
133
151
  }, [items, activeFilters, customFilterKeys, searchText, searchKeys])
134
152
 
135
- // A group is a COLUMN: label on top, values beneath it never label-left /
136
- // values-right on one line (user ruling 2026-08-15, second QA round). The
137
- // label wears the full strip idiom too tracked `kol-helper-12` at the
138
- // strip's rest ink so a category header and the LIST/GRID items read as one
139
- // family ("LIST GRID text has style, that style also on TAGS and any other
140
- // filter category"). Rest ink, not active: the label is not interactive.
153
+ // A group is a COLUMN: label on top, values beneath it (user ruling
154
+ // 2026-08-15). How a value and a label RENDER is the consumer's — three
155
+ // publishes in one day went into re-deciding that here, which is the defect
156
+ // the seams below end. Defaults are 0.1.3 exactly, so nothing moves on bump.
141
157
  const renderFilterGroup = (group) => {
142
158
  const prefix = `${group.key}:`
143
159
  const selected = new Set(
@@ -148,17 +164,30 @@ const ContentFilters = ({
148
164
 
149
165
  return (
150
166
  <div key={group.key} className="flex flex-col gap-3">
151
- <h4 className="kol-helper-12 text-fg-32" style={{ letterSpacing: 1 }}>
167
+ <h4
168
+ className={labelClassName || 'kol-helper-12 text-fg-32'}
169
+ style={labelClassName ? undefined : { letterSpacing: 1 }}
170
+ >
152
171
  {group.label}
153
172
  </h4>
154
- <TabStrip
155
- options={group.values.map((v) => ({ value: v, label: v }))}
156
- value={selected}
157
- onChange={(v) => toggleFilter(group.key, v)}
158
- size={12}
159
- tracked
160
- className="gap-4 flex-wrap"
161
- />
173
+ {renderFilterValue ? (
174
+ <div className="flex flex-wrap items-center gap-4">
175
+ {group.values.map((v) => (
176
+ <Fragment key={v}>
177
+ {renderFilterValue(v, selected.has(v), () => toggleFilter(group.key, v))}
178
+ </Fragment>
179
+ ))}
180
+ </div>
181
+ ) : (
182
+ <TabStrip
183
+ options={group.values.map((v) => ({ value: v, label: v }))}
184
+ value={selected}
185
+ onChange={(v) => toggleFilter(group.key, v)}
186
+ size={12}
187
+ tracked
188
+ className="gap-4 flex-wrap"
189
+ />
190
+ )}
162
191
  </div>
163
192
  )
164
193
  }