@kolkrabbi/kol-component 0.59.0 → 0.60.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-component",
3
- "version": "0.59.0",
3
+ "version": "0.60.0",
4
4
  "description": "KOL design-system components — atoms through organisms, emitting canonical kol-* classes. Pairs with @kolkrabbi/kol-theme for styling.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -43,8 +43,29 @@ import { glyphSize } from '../hooks/glyphLadders.js'
43
43
  * @param {string} className extra classes on the shell
44
44
  */
45
45
 
46
+ /* ONE type system and ONE glyph system for the whole component.
47
+ *
48
+ * It had two of each. The chromed path typed on `kol-mono-*` while the
49
+ * expanding path typed on `kol-helper-*`; the chromed path drew a FLAT 14px
50
+ * glyph at every size (`{ sm: 14, md: 14 }` — a size table that does not size)
51
+ * while the expanding path read the SOLO ladder. So the same component
52
+ * rendered two different fields depending on which branch you hit, and neither
53
+ * matched the IconFrame sitting next to it.
54
+ *
55
+ * Type is the mono ramp — a search field holds a query that can wrap, and
56
+ * `kol-helper-*` is line-height-1 chrome.
57
+ *
58
+ * The GLYPH READS BOTH LADDERS, because this component has both cases and they
59
+ * are the exact split `glyphLadders.js` exists for:
60
+ *
61
+ * SOLO the collapsed `expanding` trigger — a glyph ALONE in a pinned
62
+ * square, nothing beside it. 16 / 20 / 24.
63
+ * ADJACENT the leading glyph inside an open field, sitting in the input's
64
+ * line box beside the query text. 14 / 16 / 18.
65
+ *
66
+ * The flat `{ sm: 14, md: 14 }` it used to carry was the ADJACENT sm rung
67
+ * frozen for both sizes — right ladder, no size. */
46
68
  const SIZE_TYPE = { sm: 'kol-mono-12', md: 'kol-mono-14' }
47
- const ICON_SIZE = { sm: 14, md: 14 }
48
69
 
49
70
  export default function SearchInput({
50
71
  value = '',
@@ -115,7 +136,7 @@ export default function SearchInput({
115
136
  readOnly={!onChange || undefined}
116
137
  placeholder={placeholder}
117
138
  spellCheck={false}
118
- className="bg-transparent outline-none kol-helper-14 flex-1 text-fg-80 caret-current pr-4 min-w-0 appearance-none [&::-webkit-search-cancel-button]:hidden"
139
+ className={`bg-transparent outline-none ${SIZE_TYPE[size] ?? SIZE_TYPE.md} flex-1 text-oq-80 caret-current pr-4 min-w-0 appearance-none [&::-webkit-search-cancel-button]:hidden`}
119
140
  onKeyDown={(e) => { if (e.key === 'Escape') setOpen(false) }}
120
141
  {...inputProps}
121
142
  />
@@ -149,7 +170,8 @@ export default function SearchInput({
149
170
  aria-hidden="true"
150
171
  className={`flex items-center shrink-0 ${bare ? 'text-fg-48' : 'text-auto opacity-50'}`}
151
172
  >
152
- <Icon name="search" size={ICON_SIZE[size] ?? 14} />
173
+ {/* ADJACENT this glyph sits in the field's line box beside the query */}
174
+ <Icon name="search" size={glyphSize(size)} />
153
175
  </span>
154
176
  <input
155
177
  type="search"
@@ -21,7 +21,7 @@ import IconFrame from '../atoms/IconFrame.jsx'
21
21
  * @param {Array} props.filterGroups — [{label, key, values}, ...]
22
22
  * @param {Function} props.renderItem — (filteredItems, viewMode, layout) => ReactNode
23
23
  * @param {Array} props.viewModeOptions — optional view mode options for the view strip
24
- * @param {string} props.defaultViewMode — default view mode (default: 'list')
24
+ * @param {string} props.defaultViewMode — default view mode (falls back to the FIRST option)
25
25
  * @param {Function} props.onFilterChange — optional callback when filters change
26
26
  * @param {Array} props.mutuallyExclusiveFilters — filter keys that should be mutually exclusive
27
27
  * @param {Array} props.customFilterKeys — filter keys handled by renderItem, not by ContentFilters
@@ -52,9 +52,14 @@ const ContentFilters = ({
52
52
  viewModeOptions,
53
53
  viewMode: viewModeProp,
54
54
  onViewModeChange,
55
- defaultViewMode = 'list',
55
+ /* NO literal default. A strip always has exactly one active item, so the
56
+ * fallback must be the FIRST OPTION, not a guess — `'list'` matched nothing
57
+ * in a RECENT/SAVED strip, so any consumer that passed options without also
58
+ * passing a default rendered BOTH items in the rest state and the strip
59
+ * looked broken rather than unset. Same for `defaultLayout`. */
60
+ defaultViewMode,
56
61
  layoutOptions,
57
- defaultLayout = 'grid',
62
+ defaultLayout,
58
63
  /* WHERE the LIST/GRID strip sits — the two arrangements kol-monitor and
59
64
  * kol-website each settled on, made interchangeable (user ruling 2026-08-15).
60
65
  *
@@ -95,9 +100,9 @@ const ContentFilters = ({
95
100
  const IconSeam = iconComponent || Icon
96
101
  const [activeFilters, setActiveFilters] = useState(new Set())
97
102
  const [isExpanded, setIsExpanded] = useState(false)
98
- const [internalViewMode, setInternalViewMode] = useState(defaultViewMode)
103
+ const [internalViewMode, setInternalViewMode] = useState(defaultViewMode ?? viewModeOptions?.[0]?.value)
99
104
  const viewMode = viewModeProp !== undefined ? viewModeProp : internalViewMode
100
- const [layout, setLayout] = useState(defaultLayout)
105
+ const [layout, setLayout] = useState(defaultLayout ?? layoutOptions?.[0]?.value ?? 'grid')
101
106
  const [searchOpen, setSearchOpen] = useState(false)
102
107
  const [searchText, setSearchText] = useState('')
103
108
 
@@ -294,6 +299,12 @@ const ContentFilters = ({
294
299
  onChange={(e) => setSearchText(e.target.value)}
295
300
  expandedWidth={200}
296
301
  triggerLabel="Search"
302
+ /* NO placeholder. The field opens from a glyph you just clicked —
303
+ * the caret is the affordance, and a greyed "Search…" sitting in
304
+ * a 200px pill is the widest thing in the header saying the least.
305
+ * The organism's own field never had one. */
306
+ placeholder=""
307
+ size="md"
297
308
  />
298
309
  {headerActions}
299
310
  </div>