@synerise/ds-dropdown 1.3.19 → 1.3.21

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 +8 -0
  2. package/CLAUDE.md +231 -0
  3. package/package.json +13 -12
package/CHANGELOG.md CHANGED
@@ -3,6 +3,14 @@
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.3.21](https://github.com/synerise/synerise-design/compare/@synerise/ds-dropdown@1.3.20...@synerise/ds-dropdown@1.3.21) (2026-07-24)
7
+
8
+ **Note:** Version bump only for package @synerise/ds-dropdown
9
+
10
+ ## [1.3.20](https://github.com/synerise/synerise-design/compare/@synerise/ds-dropdown@1.3.19...@synerise/ds-dropdown@1.3.20) (2026-07-23)
11
+
12
+ **Note:** Version bump only for package @synerise/ds-dropdown
13
+
6
14
  ## [1.3.19](https://github.com/synerise/synerise-design/compare/@synerise/ds-dropdown@1.3.18...@synerise/ds-dropdown@1.3.19) (2026-07-16)
7
15
 
8
16
  **Note:** Version bump only for package @synerise/ds-dropdown
package/CLAUDE.md ADDED
@@ -0,0 +1,231 @@
1
+ # Dropdown (`@synerise/ds-dropdown`)
2
+
3
+ > Floating dropdown built on `@synerise/ds-popover` (floating-ui) — supports click/hover triggers, custom overlay content, built-in search+list via `DropdownMenu`, and optional footer.
4
+
5
+ ## Package structure
6
+
7
+ ```
8
+ src/
9
+ Dropdown.tsx — main component + sub-component registry
10
+ Dropdown.types.ts — DropdownProps, DropdownSharedProps, DropdownSize, DropdownPlacement
11
+ Dropdown.styles.ts — Wrapper styled-component
12
+ Dropdown.const.ts — PLACEMENT_MAP, size constants, popover configs
13
+ index.ts — public exports
14
+ hooks/
15
+ useDropdownVisibility.ts — controlled/uncontrolled open state
16
+ useDefaultTexts.tsx — i18n fallback texts for DropdownMenu
17
+ utils/
18
+ getDropdownWidth.ts — maps DropdownSize to pixel widths
19
+ getTransitionConfig.ts — scaleY transition config for floating-ui
20
+ isSplitFooter.ts — type guard for split footer shape
21
+ components/
22
+ DropdownPopover/ — floating-ui Popover wrapper + keyboard navigation context
23
+ DropdownMenu/ — searchable list dropdown (data-driven)
24
+ DropdownMenuList/ — virtualisable list renderer with empty state
25
+ DropdownOverlay/ — forwardRef overlay wrapper with size management
26
+ DropdownFooter/ — footer with optional left/right split
27
+ TextTrigger/ — text + rotating-arrow trigger element
28
+ BackAction/ — back-navigation row with left arrow
29
+ BottomAction/ — bottom call-to-action row
30
+ ```
31
+
32
+ ## Public exports
33
+
34
+ ### `Dropdown` (default + named export)
35
+
36
+ Compound component. `children` is the trigger element.
37
+
38
+ | Prop | Type | Default | Description |
39
+ |------|------|---------|-------------|
40
+ | `children` | `ReactNode` | — | **Required.** Trigger element |
41
+ | `overlay` | `ReactNode` | `undefined` | Dropdown panel content |
42
+ | `open` | `boolean` | `undefined` | Controlled open state |
43
+ | `onOpenChange` | `(isOpen: boolean) => void` | `undefined` | Fires on open/close |
44
+ | `onDismiss` | `(event?: Event, reason?: OpenChangeReason) => void` | `undefined` | Fires when closed by clicking outside |
45
+ | `trigger` | `PopoverTriggerType \| PopoverTriggerType[]` | `'click'` | `'click'` \| `'hover'` \| `'focus'` |
46
+ | `placement` | `DropdownPlacement` | `undefined` | Floating-ui placement (legacy names supported) |
47
+ | `size` | `DropdownSize \| number` | `undefined` | Overlay width: `'small'`(216px) \| `'medium'`(282px) \| `'large'`(588px) \| `'auto'` \| `'match-trigger'` \| `'min-match-trigger'` \| px number |
48
+ | `disabled` | `boolean` | `false` | Renders children without popover wrapper |
49
+ | `asChild` | `boolean` | `undefined` | Passes trigger props to the first child (Radix-style) |
50
+ | `hideOnItemClick` | `string \| boolean` | `undefined` | CSS selector or `true` → use `[role="menuitem"]` to auto-close on item click |
51
+ | `footer` | `ReactNode \| { left?: ReactNode; right?: ReactNode }` | `undefined` | Footer below overlay; object shape renders split layout |
52
+ | `overlayStyle` | `CSSProperties` | `undefined` | Inline style on the overlay container |
53
+ | `overlayClassName` | `string` | `undefined` | Class on the overlay container |
54
+ | `overlayHTMLAttributes` | `HTMLAttributes<HTMLDivElement>` | `undefined` | HTML attributes forwarded to overlay div |
55
+ | `getPopupContainer` | `(trigger: HTMLElement) => HTMLElement` | `getPopupContainerDefault` | Popover mount container |
56
+ | `popoverProps` | `Partial<PopoverOptions>` | `undefined` | Pass-through to `@synerise/ds-popover` |
57
+ | `popoverTriggerProps` | `Omit<HTMLAttributes<HTMLSpanElement>, 'onClick'>` | `undefined` | HTML attributes on the trigger span |
58
+ | `dropdownRender` | `() => ReactNode` | — | **Deprecated** — use `overlay` |
59
+ | `destroyPopupOnHide` | `boolean` | — | **Deprecated** — all dropdowns are destroyed when hidden |
60
+ | `align` | `any` | — | **Deprecated** |
61
+
62
+ #### Sub-components attached to `Dropdown`
63
+
64
+ | Name | Description |
65
+ |------|-------------|
66
+ | `Dropdown.Wrapper` | Styled `div` — white background, clips `ListItemWrapper` width |
67
+ | `Dropdown.MenuWrapper` | Styled `div` — 8px padding, for manual menu item lists |
68
+ | `Dropdown.SearchInput` | Re-export of `@synerise/ds-search-bar` |
69
+ | `Dropdown.BottomAction` | See `BottomAction` below |
70
+ | `Dropdown.BackAction` | See `BackAction` below |
71
+ | `Dropdown.TextTrigger` | See `TextTrigger` below |
72
+
73
+ ---
74
+
75
+ ### `DropdownMenu<ItemType extends ListItemProps>`
76
+
77
+ Data-driven dropdown with optional search. Renders a filtered list from `dataSource` — no manual overlay needed.
78
+
79
+ | Prop | Type | Default | Description |
80
+ |------|------|---------|-------------|
81
+ | `dataSource` | `ItemType[]` | — | **Required.** Items to render (must satisfy `ListItemProps`) |
82
+ | `children` | `ReactNode` | — | **Required.** Trigger element |
83
+ | `withSearch` | `boolean` | `false` | Show search bar |
84
+ | `virtualised` | `boolean` | `false` | Use `react-window` virtualised list |
85
+ | `maxVisibleItems` | `number` | `7` | Max items before scroll |
86
+ | `texts` | `Partial<DropdownMenuTexts>` | built-in i18n | Translation overrides |
87
+ | `itemMatchesSearchQuery` | `(item: ItemType, query: string) => boolean` | case-insensitive text match | Custom search predicate |
88
+ | `onSearchQueryChange` | `(query: string) => void` | `undefined` | Fires on search input change |
89
+ | `hideOnItemClick` | `boolean` | `undefined` | Close on item click |
90
+ | + all `DropdownSharedProps` except `overlay`, `dropdownRender`, `align`, `hideOnItemClick` (string) | | | |
91
+
92
+ `DropdownMenuTexts` keys: `searchPlaceholder`, `noSearchResults`, `searchClearTooltip`.
93
+
94
+ ---
95
+
96
+ ### `DropdownOverlay`
97
+
98
+ `forwardRef` wrapper for custom overlay content. Handles size calculation (`match-trigger`, `min-match-trigger`, static px) and `hideOnItemClick` event delegation.
99
+
100
+ ---
101
+
102
+ ### `TextTrigger`
103
+
104
+ Text label with rotating chevron — intended as a dropdown trigger.
105
+
106
+ | Prop | Type | Default | Description |
107
+ |------|------|---------|-------------|
108
+ | `value` | `string \| ReactNode \| undefined` | — | Label text |
109
+ | `size` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | — | **Required.** Typography size level |
110
+ | `expanded` | `boolean` | `undefined` | Controls arrow rotation (open state) |
111
+ | `isDisabled` | `boolean` | `undefined` | Disables interactions |
112
+ | `inactiveColor` | `string` | `grey-800` | Color when not hovered/focused |
113
+ | `onClick` | `() => void` | `undefined` | Click handler |
114
+ | `onFocus` | `() => void` | `undefined` | Focus handler |
115
+
116
+ ---
117
+
118
+ ### `BackAction`
119
+
120
+ Back-navigation row rendered inside a dropdown overlay.
121
+
122
+ | Prop | Type | Default | Description |
123
+ |------|------|---------|-------------|
124
+ | `label` | `ReactNode` | — | **Required.** Button label |
125
+ | `onClick` | `() => void` | — | **Required.** Click handler |
126
+ | `tooltip` | `ReactNode` | `undefined` | Tooltip content |
127
+ | `tooltipProps` | `Partial<TooltipProps>` | `undefined` | Pass-through to `@synerise/ds-tooltip` |
128
+
129
+ ---
130
+
131
+ ### `BottomAction`
132
+
133
+ Bottom call-to-action row (grey-050 background, 52px height).
134
+
135
+ | Prop | Type | Default | Description |
136
+ |------|------|---------|-------------|
137
+ | `onClickAction` | `() => void` | — | **Required.** Click handler |
138
+ | `icon` | `ReactElement` | `undefined` | Icon before text |
139
+ | `children` | `ReactNode` | `undefined` | Label text |
140
+ | + all `HTMLDivElement` attributes | | | |
141
+
142
+ ---
143
+
144
+ ### Type exports
145
+
146
+ `DropdownPlacement`, `DropdownProps`, `DropdownSharedProps`, `DropdownSize`, `DropdownMenuProps`, `DropdownMenuListItemProps`.
147
+
148
+ ## Usage patterns
149
+
150
+ ```tsx
151
+ import Dropdown, { DropdownMenu, TextTrigger, BackAction, BottomAction } from '@synerise/ds-dropdown';
152
+
153
+ // Basic overlay dropdown
154
+ <Dropdown overlay={<Menu>...</Menu>} trigger="click">
155
+ <Button>Open</Button>
156
+ </Dropdown>
157
+
158
+ // Data-driven with search
159
+ <DropdownMenu
160
+ dataSource={items}
161
+ withSearch
162
+ trigger="click"
163
+ texts={{ searchPlaceholder: 'Search...' }}
164
+ >
165
+ <Button>Open</Button>
166
+ </DropdownMenu>
167
+
168
+ // Custom overlay with sub-components
169
+ <Dropdown overlay={
170
+ <>
171
+ <Dropdown.BackAction label="Back" onClick={handleBack} />
172
+ <Dropdown.Wrapper>
173
+ <Menu>...</Menu>
174
+ </Dropdown.Wrapper>
175
+ <Dropdown.BottomAction onClickAction={handleAdd} icon={<PlusM />}>
176
+ Add item
177
+ </Dropdown.BottomAction>
178
+ </>
179
+ }>
180
+ <Dropdown.TextTrigger value="Select" size={2} />
181
+ </Dropdown>
182
+
183
+ // Controlled
184
+ const [open, setOpen] = useState(false);
185
+ <Dropdown open={open} onOpenChange={setOpen} overlay={...}>
186
+ <Button>Toggle</Button>
187
+ </Dropdown>
188
+ ```
189
+
190
+ ## Controlled vs. uncontrolled
191
+
192
+ `useDropdownVisibility` handles both modes:
193
+ - **Uncontrolled:** omit `open` — internal state manages visibility.
194
+ - **Controlled:** pass `open` + `onOpenChange` — component stays in sync with external state.
195
+
196
+ A `skipDuplicates` ref prevents `onOpenChange` from firing twice when both the trigger's `onClick` and the popover's `onOpenChange` fire for the same event.
197
+
198
+ ## Styling
199
+
200
+ - Overlay: `DropdownOverlay.styles.ts` — white background, `box-shadow-2`, `3px` border-radius, dynamic width.
201
+ - Footer: `grey-100` background, `48px` height; split layout via `DropdownFooterLeft`/`DropdownFooterRight`.
202
+ - `Dropdown.Wrapper` clips `ListItemWrapper` to prevent overflow in narrow overlays.
203
+ - All colours and shadows come from `@synerise/ds-core` tokens.
204
+
205
+ ## Custom hooks
206
+
207
+ ### `useDropdownVisibility`
208
+
209
+ Manages controlled/uncontrolled open state. Returns `{ open, toggleOpen }`. Deduplicates `onOpenChange` calls that would otherwise fire twice from concurrent click handlers.
210
+
211
+ ### `useDefaultTexts`
212
+
213
+ Calls `useIntl()` to return default English strings for `DropdownMenu` (`searchPlaceholder`, `noSearchResults`, `searchClearTooltip`). Requires a `react-intl` `IntlProvider` ancestor.
214
+
215
+ ## Key dependencies
216
+
217
+ - `@synerise/ds-popover` — floating-ui wrapper providing `Popover`, `PopoverContent`, `PopoverTrigger`
218
+ - `@synerise/ds-search-bar` — `Dropdown.SearchInput` is a direct re-export
219
+ - `@synerise/ds-list-item` — `ListItemProps` is the item type constraint for `DropdownMenu`
220
+ - `react-window` — `VariableSizeList` for virtualised `DropdownMenuList`
221
+ - `@floating-ui/react` — `FloatingList`, `useListNavigation` for keyboard arrow-key navigation inside `DropdownPopover`
222
+
223
+ ## Implementation notes
224
+
225
+ - When `disabled=true`, `Dropdown` renders `children` directly (no span wrapper, no popover).
226
+ - `asChild` mode forwards all trigger props onto the first child element (Radix `Slot` pattern) — do not wrap children in an extra element when using this.
227
+ - `hideOnItemClick` on `DropdownMenu` accepts only `boolean`; on `Dropdown` it also accepts a CSS selector string.
228
+ - `DropdownMenu` sets `disabled` automatically when `dataSource` is empty.
229
+ - The overlay is always unmounted when hidden (`destroyPopupOnHide` is a no-op legacy prop).
230
+ - `DropdownPopover` provides `DropdownContext` (active index + close handler) to list items — do not skip it when building a custom overlay with keyboard navigation.
231
+ - `PLACEMENT_MAP` in `Dropdown.const.ts` maps legacy Ant Design placement strings (e.g. `bottomCenter`) to floating-ui equivalents — no consumer migration needed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synerise/ds-dropdown",
3
- "version": "1.3.19",
3
+ "version": "1.3.21",
4
4
  "description": "Dropdown 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"
@@ -42,16 +43,16 @@
42
43
  "types": "dist/index.d.ts",
43
44
  "dependencies": {
44
45
  "@floating-ui/react": "^0.27.16",
45
- "@synerise/ds-divider": "^1.3.15",
46
- "@synerise/ds-empty-states": "^1.0.48",
47
- "@synerise/ds-icon": "^1.18.4",
48
- "@synerise/ds-list-item": "^1.6.1",
49
- "@synerise/ds-popover": "^1.6.1",
50
- "@synerise/ds-scrollbar": "^1.5.1",
51
- "@synerise/ds-search-bar": "^1.4.34",
52
- "@synerise/ds-tooltip": "^1.5.3",
53
- "@synerise/ds-typography": "^1.1.26",
54
- "@synerise/ds-utils": "^1.10.1",
46
+ "@synerise/ds-divider": "^1.3.16",
47
+ "@synerise/ds-empty-states": "^1.0.49",
48
+ "@synerise/ds-icon": "^1.18.5",
49
+ "@synerise/ds-list-item": "^1.6.2",
50
+ "@synerise/ds-popover": "^1.6.2",
51
+ "@synerise/ds-scrollbar": "^1.5.2",
52
+ "@synerise/ds-search-bar": "^1.4.36",
53
+ "@synerise/ds-tooltip": "^1.5.4",
54
+ "@synerise/ds-typography": "^1.1.27",
55
+ "@synerise/ds-utils": "^1.10.2",
55
56
  "classnames": "^2.5.1",
56
57
  "react-window": "^1.8.11"
57
58
  },
@@ -64,5 +65,5 @@
64
65
  "react-intl": "^6.8.7",
65
66
  "styled-components": "^5.3.3"
66
67
  },
67
- "gitHead": "a81ab6519d49a3dea9c0cfebcdc9104cbb4f4226"
68
+ "gitHead": "018414e0d7ffd102a07f8aa35fa2e2d3d4ed19e2"
68
69
  }