@synerise/ds-icon-picker 1.7.8 → 1.7.9

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/CLAUDE.md +155 -0
  3. package/package.json +11 -10
package/CHANGELOG.md CHANGED
@@ -3,6 +3,10 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [1.7.9](https://github.com/Synerise/synerise-design/compare/@synerise/ds-icon-picker@1.7.8...@synerise/ds-icon-picker@1.7.9) (2026-07-23)
7
+
8
+ **Note:** Version bump only for package @synerise/ds-icon-picker
9
+
6
10
  ## [1.7.8](https://github.com/Synerise/synerise-design/compare/@synerise/ds-icon-picker@1.7.7...@synerise/ds-icon-picker@1.7.8) (2026-07-16)
7
11
 
8
12
  **Note:** Version bump only for package @synerise/ds-icon-picker
package/CLAUDE.md ADDED
@@ -0,0 +1,155 @@
1
+ # IconPicker (`@synerise/ds-icon-picker`)
2
+
3
+ > A dropdown icon picker that supports three icon sources: DS icons (`'design-system'`), Font Awesome icons (`'font-awesome'`), or a custom `DataSource[]`. Returns a strongly-typed value depending on the source.
4
+
5
+ ## Package structure
6
+
7
+ ```
8
+ src/
9
+ IconPicker.tsx — root component; trigger rendering, search filter, dropdown
10
+ IconPicker.types.ts — all types including generics; exported from index
11
+ IconPicker.styles.tsx — styled-components for overlay, list, items
12
+ IconPicker.const.ts — ITEMS_PER_ROW (6), ELEMENT_HEIGHT (32px)
13
+ index.ts — default export + type exports
14
+ components/
15
+ Overlay/
16
+ Overlay.tsx — search input (Dropdown.SearchInput) + List
17
+ Overlay.types.ts — OverlayType (internal)
18
+ List/
19
+ List.tsx — virtualised list with Scrollbar and empty state
20
+ ListItem/
21
+ ListItem.tsx — single icon cell with tooltip + button
22
+ ListItem.types.ts — ListItemProps (internal)
23
+ RowItem.tsx — react-window row renderer (memo + areEqual)
24
+ hooks/
25
+ useIconSourceLoader.tsx — loads icons based on source type; async for FA
26
+ useGroupItems.ts — flattens grouped data into virtual list rows
27
+ utils/
28
+ createItemData.ts — memoized factory for react-window itemData
29
+ loadFontAwesome.ts — dynamic import() of all FA icon packs
30
+ matchesSearchQuery.ts — keyword matching for search filter
31
+ prepareItems.ts — normalises DataSource[] to FilterElement[]
32
+ typeguards.utils.ts — isFASourceType, isDSSourceType, isData, isCategories
33
+ __specs__/
34
+ IconPicker.spec.tsx — render, selection, DS/FA source tests (Vitest)
35
+ ```
36
+
37
+ ## Public exports
38
+
39
+ ### `IconPicker` (default)
40
+
41
+ Generic component: `IconPicker<Source extends SourceType>`. The type of `onSelect`'s argument depends on `data`.
42
+
43
+ | Prop | Type | Default | Description |
44
+ |------|------|---------|-------------|
45
+ | `data` | `'design-system' \| 'font-awesome' \| DataSource[]` | — | Icon source. Determines `onSelect` value type. |
46
+ | `onSelect` | `(value: ValueTypeForSource<Source>) => void` | — | Called when an icon is clicked. Value is `[IconPrefix, IconName]` for FA; `ReactNode` for DS/custom. |
47
+ | `trigger` | `PopoverTriggerType[]` | — | Dropdown trigger types, e.g. `['click']`. |
48
+ | `placeholder` | `string` | — | Search input placeholder text. |
49
+ | `button` | `ReactElement` | — | Custom trigger element. **Typed as required but has a fallback** — if omitted, renders a default "Choose icon" button or the selected-icon button-group (see implementation notes). |
50
+ | `selectedIcon` | `ReactNode` | `undefined` | The currently-selected icon to display. When truthy, renders a `ButtonGroup` with the icon + a red clear button instead of the default trigger. |
51
+ | `onClear` | `() => void` | `undefined` | Called when the clear button is clicked (only shown when `selectedIcon` is set). |
52
+ | `clearTooltip` | `ReactNode` | `undefined` | Tooltip text on the clear button. |
53
+ | `noResultMsg` | `ReactNode` | `<FormattedMessage id="DS.ICON-PICKER.NO-RESULTS" />` | Message shown when search returns no results. |
54
+
55
+ ### Exported types
56
+
57
+ | Type | Description |
58
+ |------|-------------|
59
+ | `IconPickerProps<Source>` | Full props interface (generic) |
60
+ | `SourceType` | `'font-awesome' \| 'design-system' \| DataSource[]` |
61
+ | `FASource` | `'font-awesome'` |
62
+ | `DSSource` | `'design-system'` |
63
+ | `FAValue` | `[IconPrefix, IconName]` — value returned by FA `onSelect` |
64
+ | `DataSource` | Custom data source shape: `{ category, items: [{ item, value?, keywords? }] }` |
65
+ | `FilterElement<V>` | Normalised group: `{ category, items: FilterItem<V>[] }` |
66
+ | `FilterItem<V>` | `{ keywords?, value, item }` |
67
+
68
+ ## Usage patterns
69
+
70
+ ```tsx
71
+ import IconPicker from '@synerise/ds-icon-picker';
72
+ import type { DSSource, FASource, FAValue, DataSource } from '@synerise/ds-icon-picker';
73
+
74
+ // DS Icons — onSelect receives ReactNode
75
+ const [icon, setIcon] = useState<ReactNode>(null);
76
+ <IconPicker<DSSource>
77
+ data="design-system"
78
+ onSelect={setIcon}
79
+ selectedIcon={icon}
80
+ onClear={() => setIcon(null)}
81
+ trigger={['click']}
82
+ placeholder="Search"
83
+ clearTooltip="Remove"
84
+ />
85
+
86
+ // Font Awesome — onSelect receives [IconPrefix, IconName]
87
+ const [fa, setFa] = useState<FAValue | null>(null);
88
+ <IconPicker<FASource>
89
+ data="font-awesome"
90
+ onSelect={setFa}
91
+ selectedIcon={fa ? <FontAwesomeIcon icon={fa} /> : null}
92
+ onClear={() => setFa(null)}
93
+ trigger={['click']}
94
+ placeholder="Search"
95
+ />
96
+
97
+ // Custom DataSource[]
98
+ const myIcons: DataSource[] = [
99
+ {
100
+ category: 'Emoji',
101
+ items: [
102
+ { item: '😀', value: 'grinning', keywords: 'grinning smile happy' },
103
+ ],
104
+ },
105
+ ];
106
+ <IconPicker
107
+ data={myIcons}
108
+ onSelect={(value) => console.log(value)}
109
+ trigger={['click']}
110
+ placeholder="Search"
111
+ />
112
+
113
+ // Custom trigger button
114
+ <IconPicker
115
+ data="design-system"
116
+ button={<Button type="primary">Select icon</Button>}
117
+ onSelect={setIcon}
118
+ trigger={['click']}
119
+ placeholder="Search"
120
+ />
121
+ ```
122
+
123
+ ## Custom hooks
124
+
125
+ ### `useIconSourceLoader`
126
+
127
+ Loads icons based on the `data` prop and returns `FilterElement[]`.
128
+ - `'design-system'` → synchronously imports `@synerise/ds-icon/dist/icons/M` (deep import) and sets state
129
+ - `'font-awesome'` → dynamically imports all FA packs via `Promise.all` (async, icons appear after load)
130
+ - `DataSource[]` → runs `prepareItems()` synchronously
131
+
132
+ > **Anti-pattern:** `loadedItems()` is called **directly in the hook body on every render** (not inside `useEffect`). The `!items.length` guard prevents repeated state updates, but the callback itself runs every render cycle.
133
+
134
+ ### `useGroupItems`
135
+
136
+ Flattens `FilterElement[]` into `GroupedFilterElement[]` for react-window. Each entry is either a `[Category]` header row or a `FilterItem[]` row (up to `ITEMS_PER_ROW` items). Memoized with `useMemo`.
137
+
138
+ ## Key dependencies
139
+
140
+ - `react-window` (`FixedSizeList`) — virtualises the icon grid (item height = 32px, max visible = 330px)
141
+ - `@synerise/ds-scrollbar` — wraps virtual list; scroll events forwarded manually via `listRef.current.scrollTo()`
142
+ - `@fortawesome/*` — **peer dependencies**, loaded lazily via `loadFontAwesome()` only when `data='font-awesome'`
143
+ - `react-intl` — **peer dependency** — default `noResultMsg` uses `<FormattedMessage>`; `IntlProvider` must be in the tree
144
+ - `memoize-one` — memoizes `createItemData` for react-window
145
+
146
+ ## Implementation notes
147
+
148
+ - **`button` is required in types but optional in practice** — TypeScript will error if omitted, but the component renders a default "Choose icon" button when `button` is falsy. If you want to suppress the TS error, pass `button={undefined as unknown as ReactElement}` or use the default.
149
+ - **Font Awesome icons load asynchronously** — the picker renders empty until `loadFontAwesome()` resolves. No loading state is shown.
150
+ - **Selection state is fully controlled** — the component has no internal selection state. Pass `selectedIcon` and handle `onClear` to control the selected-state UI.
151
+ - **`selectedIcon` toggles the trigger entirely** — when truthy, the default trigger button is replaced with `ButtonGroup` (selected icon + clear button). The `button` prop is ignored in this state.
152
+ - **`ListItem` has a fragile Avatar check** — `(element.item as any).type.name === 'Avatar'` detects Avatar items by component name. Minification or renaming will break this.
153
+ - **Deep import for DS icons** — `import * as medium from '@synerise/ds-icon/dist/icons/M'` is a fragile path that may break if `@synerise/ds-icon` restructures its dist output.
154
+ - **Uses Vitest** — `package.json` has `"test": "jest"`. Spec also uses `vi.fn()`. Not yet migrated.
155
+ - **`react-intl` is required** — `IntlProvider` must be present for the default `noResultMsg`. Tests use `renderWithProvider` from `@synerise/ds-core`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synerise/ds-icon-picker",
3
- "version": "1.7.8",
3
+ "version": "1.7.9",
4
4
  "description": "IconPicker UI Component for the Synerise Design System",
5
5
  "license": "ISC",
6
6
  "repository": "Synerise/synerise-design",
@@ -17,6 +17,7 @@
17
17
  "files": [
18
18
  "/dist",
19
19
  "CHANGELOG.md",
20
+ "CLAUDE.md",
20
21
  "README.md",
21
22
  "package.json",
22
23
  "LICENSE.md"
@@ -38,14 +39,14 @@
38
39
  "sideEffects": false,
39
40
  "types": "dist/index.d.ts",
40
41
  "dependencies": {
41
- "@synerise/ds-button": "^1.5.34",
42
- "@synerise/ds-button-group": "^1.1.59",
43
- "@synerise/ds-dropdown": "^1.3.19",
44
- "@synerise/ds-icon": "^1.18.4",
45
- "@synerise/ds-popover": "^1.6.1",
46
- "@synerise/ds-scrollbar": "^1.5.1",
47
- "@synerise/ds-tooltip": "^1.5.3",
48
- "@synerise/ds-utils": "^1.10.1",
42
+ "@synerise/ds-button": "^1.5.35",
43
+ "@synerise/ds-button-group": "^1.1.60",
44
+ "@synerise/ds-dropdown": "^1.3.20",
45
+ "@synerise/ds-icon": "^1.18.5",
46
+ "@synerise/ds-popover": "^1.6.2",
47
+ "@synerise/ds-scrollbar": "^1.5.2",
48
+ "@synerise/ds-tooltip": "^1.5.4",
49
+ "@synerise/ds-utils": "^1.10.2",
49
50
  "memoize-one": "^5.1.1",
50
51
  "react-window": "^1.8.11"
51
52
  },
@@ -61,5 +62,5 @@
61
62
  "styled-components": "^5.3.3",
62
63
  "vitest": "4"
63
64
  },
64
- "gitHead": "a81ab6519d49a3dea9c0cfebcdc9104cbb4f4226"
65
+ "gitHead": "d0a43cc43d8528a36f105aceea52ab470edb71d9"
65
66
  }