solid-tom-ui 1.0.11 → 1.0.14
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/README.md +246 -246
- package/dist/README.md +246 -246
- package/dist/components/avatar/avatar.js.map +1 -1
- package/dist/components/badge/badge.js.map +1 -1
- package/dist/components/breadcrumb/breadcrumb.js.map +1 -1
- package/dist/components/button/button.js.map +1 -1
- package/dist/components/carousel/carousel.js.map +1 -1
- package/dist/components/chat-bubble/chatBubble.js.map +1 -1
- package/dist/components/checkbox/checkbox.js.map +1 -1
- package/dist/components/collapse/collapse.js.map +1 -1
- package/dist/components/context-menu/context-menu.js.map +1 -1
- package/dist/components/context-menu/context-menu.store.js.map +1 -1
- package/dist/components/divider/divider.js.map +1 -1
- package/dist/components/dropdown/dropdown.js.map +1 -1
- package/dist/components/dropdown/dropdown.store.js.map +1 -1
- package/dist/components/float-button/float-button.js.map +1 -1
- package/dist/components/hover-3d-image/hover-3d-image.js.map +1 -1
- package/dist/components/image-preview/image-preview.js.map +1 -1
- package/dist/components/input/input.js.map +1 -1
- package/dist/components/input/input.utils.js.map +1 -1
- package/dist/components/input/variants/input-color.js.map +1 -1
- package/dist/components/input/variants/input-date.js.map +1 -1
- package/dist/components/input/variants/input-number.d.ts.map +1 -1
- package/dist/components/input/variants/input-number.js +1 -1
- package/dist/components/input/variants/input-number.js.map +1 -1
- package/dist/components/input/variants/input-otp.js.map +1 -1
- package/dist/components/input/variants/input-password.js.map +1 -1
- package/dist/components/input/variants/input-radio.js.map +1 -1
- package/dist/components/input/variants/input-range.js.map +1 -1
- package/dist/components/input/variants/input-text.d.ts.map +1 -1
- package/dist/components/input/variants/input-text.js +1 -1
- package/dist/components/input/variants/input-text.js.map +1 -1
- package/dist/components/input/variants/input-textarea.js.map +1 -1
- package/dist/components/loading/loading.js.map +1 -1
- package/dist/components/mansory/mansory.js.map +1 -1
- package/dist/components/menu/menu.js.map +1 -1
- package/dist/components/modal/modal.js.map +1 -1
- package/dist/components/modal/modalContext.js.map +1 -1
- package/dist/components/pagination/pagination.js.map +1 -1
- package/dist/components/progress-bar/progress-bar.js.map +1 -1
- package/dist/components/qr-code/qr-code.js.map +1 -1
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/select-zone/select-zone.js.map +1 -1
- package/dist/components/skeleton/skeleton.js.map +1 -1
- package/dist/components/slider/slider.js.map +1 -1
- package/dist/components/splitter/splitter.js.map +1 -1
- package/dist/components/steps/steps.js.map +1 -1
- package/dist/components/swap/swap.js.map +1 -1
- package/dist/components/switch/switch.js.map +1 -1
- package/dist/components/tab/tab.js.map +1 -1
- package/dist/components/table/table.js.map +1 -1
- package/dist/components/timeline/timeline.js.map +1 -1
- package/dist/components/toast/icons/ErrorIcon.js.map +1 -1
- package/dist/components/toast/icons/IconCircle.js.map +1 -1
- package/dist/components/toast/icons/InfoIcon.js.map +1 -1
- package/dist/components/toast/icons/LoaderIcon.js.map +1 -1
- package/dist/components/toast/icons/SuccessIcon.js.map +1 -1
- package/dist/components/toast/icons/WarningIcon.js.map +1 -1
- package/dist/components/toast/toast.js.map +1 -1
- package/dist/components/toast/toast.store.js.map +1 -1
- package/dist/components/tooltip/tooltip.js.map +1 -1
- package/dist/components/tour/tour.js.map +1 -1
- package/dist/components/upload/upload.js.map +1 -1
- package/dist/components/z-index/z-index.context.js.map +1 -1
- package/dist/components/z-index/z-index.js.map +1 -1
- package/dist/components/z-index/z-index.store.js.map +1 -1
- package/dist/components/z-index/z-index.types.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/skill/avatar.skill.md.txt +255 -255
- package/dist/skill/badge.skill.md.txt +223 -223
- package/dist/skill/breadcrumb.skill.md.txt +177 -177
- package/dist/skill/button.skill.md.txt +198 -198
- package/dist/skill/carousel.skill.md.txt +406 -406
- package/dist/skill/chat-bubble.skill.md.txt +342 -342
- package/dist/skill/checkbox.skill.md.txt +326 -326
- package/dist/skill/code-preview.skill.md.txt +240 -240
- package/dist/skill/collapse.skill.md.txt +329 -329
- package/dist/skill/context-menu.skill.md.txt +233 -233
- package/dist/skill/diff.skill.md.txt +244 -244
- package/dist/skill/divider.skill.md.txt +151 -151
- package/dist/skill/doc.skill.md.txt +191 -191
- package/dist/skill/drawer.skill.md.txt +157 -157
- package/dist/skill/dropdown.skill.md.txt +198 -198
- package/dist/skill/float-button.skill.md.txt +315 -315
- package/dist/skill/hover-3d-image.skill.md.txt +120 -120
- package/dist/skill/iframe.skill.md.txt +114 -114
- package/dist/skill/image-preview.skill.md.txt +162 -162
- package/dist/skill/indicator.skill.md.txt +60 -60
- package/dist/skill/input.skill.md.txt +489 -489
- package/dist/skill/loading.skill.md.txt +127 -127
- package/dist/skill/menu.skill.md.txt +476 -476
- package/dist/skill/modal.skill.md.txt +359 -359
- package/dist/skill/pagination.skill.md.txt +405 -405
- package/dist/skill/progress-bar.skill.md.txt +207 -207
- package/dist/skill/qr-code.skill.md.txt +136 -136
- package/dist/skill/rating.skill.md.txt +167 -167
- package/dist/skill/select-zone.skill.md.txt +93 -93
- package/dist/skill/select.skill.md.txt +663 -663
- package/dist/skill/skeleton.skill.md.txt +192 -192
- package/dist/skill/slider.skill.md.txt +404 -404
- package/dist/skill/splitter.skill.md.txt +411 -411
- package/dist/skill/steps.skill.md.txt +264 -264
- package/dist/skill/swap.skill.md.txt +139 -139
- package/dist/skill/switch.skill.md.txt +191 -191
- package/dist/skill/tab.skill.md.txt +484 -484
- package/dist/skill/table.example.header.md.txt +666 -666
- package/dist/skill/table.skill.md.txt +1407 -1407
- package/dist/skill/text-rotate.skill.md.txt +186 -186
- package/dist/skill/timeline.skill.md.txt +247 -247
- package/dist/skill/toast.skill.md.txt +531 -531
- package/dist/skill/tooltip.skill.md.txt +222 -222
- package/dist/skill/tour.skill.md.txt +156 -156
- package/dist/skill/upload.skill.md.txt +358 -358
- package/dist/utils/cn.js.map +1 -1
- package/dist/utils/element-tracker.js.map +1 -1
- package/dist/utils/helper.js.map +1 -1
- package/dist/utils/hoc.js.map +1 -1
- package/package.json +132 -133
|
@@ -1,198 +1,198 @@
|
|
|
1
|
-
## COMPONENT IDENTITY
|
|
2
|
-
- **Import**: `import { Dropdown } from 'solid-tom-ui';`
|
|
3
|
-
- **Export**: `Dropdown` (named export)
|
|
4
|
-
- **Framework**: SolidJS
|
|
5
|
-
- **Purpose**: Dropdown that renders content into `document.body` via Portal; always visible above overflow-hidden containers; singleton behavior (only one open at a time)
|
|
6
|
-
|
|
7
|
-
## Props
|
|
8
|
-
|
|
9
|
-
| Prop | Type | Default | Description |
|
|
10
|
-
|------|------|---------|-------------|
|
|
11
|
-
| `triggerElement` | `SolidComponent` | — | Element rendered as the clickable trigger |
|
|
12
|
-
| `open` | `boolean` | `undefined` | Controlled mode: pass `true`/`false` to manage state externally |
|
|
13
|
-
| `position` | `'top' \| 'bottom' \| 'left' \| 'right'` | `'bottom'` | Which side the content appears on |
|
|
14
|
-
| `align` | `'start' \| 'center' \| 'end'` | `'start'` | Alignment along the cross axis |
|
|
15
|
-
| `blockScroll` | `boolean` | `false` | Lock page scroll while dropdown is open |
|
|
16
|
-
| `zIndex` | `number` | — | Override the automatic z-index |
|
|
17
|
-
| `class.anchor` | `string` | — | Class on the content anchor wrapper |
|
|
18
|
-
| `class.trigger` | `string` | — | Class on the trigger wrapper div |
|
|
19
|
-
| `class.content` | `string` | — | Class on the content wrapper div |
|
|
20
|
-
| `ref` | `HTMLDivElement \| ((el) => void)` | — | Ref to the trigger div |
|
|
21
|
-
|
|
22
|
-
## Basic Usage
|
|
23
|
-
|
|
24
|
-
```tsx
|
|
25
|
-
<Dropdown triggerElement={<button class="btn btn-sm">Open</button>}>
|
|
26
|
-
<div class="bg-base-100 rounded-lg shadow-lg px-4 py-3 min-w-40">
|
|
27
|
-
<p>Dropdown content</p>
|
|
28
|
-
</div>
|
|
29
|
-
</Dropdown>
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### With position and alignment
|
|
33
|
-
|
|
34
|
-
```tsx
|
|
35
|
-
<Dropdown
|
|
36
|
-
position="top"
|
|
37
|
-
align="end"
|
|
38
|
-
triggerElement={<button class="btn btn-sm">Open ↑</button>}
|
|
39
|
-
>
|
|
40
|
-
<div class="bg-base-100 rounded-lg shadow-lg px-4 py-3">
|
|
41
|
-
Top-end positioned
|
|
42
|
-
</div>
|
|
43
|
-
</Dropdown>
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### Controlled mode
|
|
47
|
-
|
|
48
|
-
```tsx
|
|
49
|
-
const [open, setOpen] = createSignal(false);
|
|
50
|
-
|
|
51
|
-
<button class="btn btn-sm" onClick={() => setOpen(v => !v)}>Toggle</button>
|
|
52
|
-
<Dropdown
|
|
53
|
-
open={open()}
|
|
54
|
-
triggerElement={<div class="border rounded px-3 py-1">Trigger</div>}
|
|
55
|
-
>
|
|
56
|
-
<div class="bg-base-100 rounded-lg shadow-lg px-4 py-3">
|
|
57
|
-
<button class="btn btn-xs btn-ghost" onClick={() => setOpen(false)}>Close</button>
|
|
58
|
-
</div>
|
|
59
|
-
</Dropdown>
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### Inside `overflow-hidden` container
|
|
63
|
-
|
|
64
|
-
```tsx
|
|
65
|
-
// Portal mode escapes overflow-hidden — content is always visible
|
|
66
|
-
<div class="overflow-hidden h-16 w-48 border rounded flex items-center justify-center">
|
|
67
|
-
<Dropdown triggerElement={<button class="btn btn-sm">Open</button>}>
|
|
68
|
-
<div class="bg-base-100 shadow-lg px-4 py-3">Visible!</div>
|
|
69
|
-
</Dropdown>
|
|
70
|
-
</div>
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### Scroll lock
|
|
74
|
-
|
|
75
|
-
```tsx
|
|
76
|
-
<Dropdown
|
|
77
|
-
blockScroll
|
|
78
|
-
triggerElement={<button class="btn btn-sm btn-warning">Open (scroll locked)</button>}
|
|
79
|
-
>
|
|
80
|
-
<div class="bg-base-100 rounded-lg shadow-lg px-6 py-4">
|
|
81
|
-
Page scroll is locked while this is open.
|
|
82
|
-
</div>
|
|
83
|
-
</Dropdown>
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## Behavior Details
|
|
87
|
-
|
|
88
|
-
### Singleton
|
|
89
|
-
|
|
90
|
-
Only one dropdown can be open at a time. Opening a second dropdown automatically closes the first. This is enforced by the global `dropdownStore`.
|
|
91
|
-
|
|
92
|
-
### Click outside to close
|
|
93
|
-
|
|
94
|
-
A full-screen invisible backdrop (`nd-backdrop`) catches clicks outside the content.
|
|
95
|
-
|
|
96
|
-
### Controlled vs uncontrolled
|
|
97
|
-
|
|
98
|
-
- **Uncontrolled** (no `open` prop): the component manages its own open/close state.
|
|
99
|
-
- **Controlled** (`open` prop provided): the component delegates state to the parent. The trigger click has no effect — the parent must update `open` manually. Controlled state is synced with `dropdownStore`.
|
|
100
|
-
|
|
101
|
-
### Position tracking (`blockScroll={false}`)
|
|
102
|
-
|
|
103
|
-
When open and `blockScroll` is false, the portal content anchor tracks the trigger element's position continuously via `element-tracker`. This ensures the dropdown stays aligned even when the page scrolls.
|
|
104
|
-
|
|
105
|
-
When `blockScroll={true}`, tracking stops (the page cannot scroll anyway, so the position is stable).
|
|
106
|
-
|
|
107
|
-
## Positioning System
|
|
108
|
-
|
|
109
|
-
Content position is computed from `getBoundingClientRect()` of the trigger, stored in CSS variables on the anchor element:
|
|
110
|
-
|
|
111
|
-
| Variable | Value |
|
|
112
|
-
|----------|-------|
|
|
113
|
-
| `--nd-t` | `trigger.top` |
|
|
114
|
-
| `--nd-l` | `trigger.left` |
|
|
115
|
-
| `--nd-w` | `trigger.width` |
|
|
116
|
-
| `--nd-h` | `trigger.height` |
|
|
117
|
-
|
|
118
|
-
`data-pos` and `data-align` attributes on `.nd-content-anchor` drive all position/alignment combinations via pure CSS. Gap between trigger and content is controlled by `--nd-gap` (default: `6px`).
|
|
119
|
-
|
|
120
|
-
## Common Patterns
|
|
121
|
-
|
|
122
|
-
### Menu list inside dropdown
|
|
123
|
-
|
|
124
|
-
```tsx
|
|
125
|
-
<Dropdown triggerElement={<button class="btn btn-sm">Menu</button>}>
|
|
126
|
-
<ul class="bg-base-100 rounded-lg shadow-lg overflow-hidden min-w-40">
|
|
127
|
-
<li>
|
|
128
|
-
<button class="w-full px-4 py-2 text-left text-sm hover:bg-base-200 transition-colors">
|
|
129
|
-
Edit
|
|
130
|
-
</button>
|
|
131
|
-
</li>
|
|
132
|
-
<li>
|
|
133
|
-
<button class="w-full px-4 py-2 text-left text-sm text-error hover:bg-error/10 transition-colors">
|
|
134
|
-
Delete
|
|
135
|
-
</button>
|
|
136
|
-
</li>
|
|
137
|
-
</ul>
|
|
138
|
-
</Dropdown>
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### Icon trigger (three-dot menu)
|
|
142
|
-
|
|
143
|
-
```tsx
|
|
144
|
-
import { MoreHorizontal } from 'lucide-solid';
|
|
145
|
-
|
|
146
|
-
<Dropdown
|
|
147
|
-
align="end"
|
|
148
|
-
triggerElement={
|
|
149
|
-
<button class="btn btn-ghost btn-xs btn-square">
|
|
150
|
-
<MoreHorizontal size={16} />
|
|
151
|
-
</button>
|
|
152
|
-
}
|
|
153
|
-
>
|
|
154
|
-
<ul class="bg-base-100 rounded-lg shadow-lg overflow-hidden min-w-36">
|
|
155
|
-
<li><button class="w-full px-4 py-2 text-left text-sm hover:bg-base-200">Rename</button></li>
|
|
156
|
-
<li><button class="w-full px-4 py-2 text-left text-sm hover:bg-base-200">Duplicate</button></li>
|
|
157
|
-
<li><button class="w-full px-4 py-2 text-left text-sm text-error hover:bg-error/10">Delete</button></li>
|
|
158
|
-
</ul>
|
|
159
|
-
</Dropdown>
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
### Programmatic close from content
|
|
163
|
-
|
|
164
|
-
Use controlled mode to close the dropdown from inside:
|
|
165
|
-
|
|
166
|
-
```tsx
|
|
167
|
-
const [open, setOpen] = createSignal(false);
|
|
168
|
-
|
|
169
|
-
<Dropdown
|
|
170
|
-
open={open()}
|
|
171
|
-
triggerElement={
|
|
172
|
-
<button class="btn btn-sm" onClick={() => setOpen(true)}>Open</button>
|
|
173
|
-
}
|
|
174
|
-
>
|
|
175
|
-
<div class="bg-base-100 rounded-lg shadow-lg px-4 py-3">
|
|
176
|
-
<button class="btn btn-xs" onClick={() => setOpen(false)}>Close me</button>
|
|
177
|
-
</div>
|
|
178
|
-
</Dropdown>
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
## Styling Tips
|
|
182
|
-
|
|
183
|
-
- **Content width**: Portal content has `min-width: max-content` by default. Use `min-w-*` Tailwind classes on the content div to enforce a minimum width.
|
|
184
|
-
- **Custom gap**: Override `--nd-gap` on `.nd-content-anchor` via inline style or a custom CSS class.
|
|
185
|
-
- **Animation origin**: Animation scale origin (`transform-origin`) is automatically set per position/alignment via `--nd-origin` CSS variable — no manual adjustment needed.
|
|
186
|
-
|
|
187
|
-
## Gotchas
|
|
188
|
-
|
|
189
|
-
1. **Singleton**: Only one dropdown can be open at a time — enforced by `dropdownStore`.
|
|
190
|
-
2. **`class.content` vs content children**: `class.content` targets the `.nd-content` wrapper, not the children directly. Apply `rounded`, `shadow`, `bg-*` to the direct child element inside `<Dropdown>` for full visual control.
|
|
191
|
-
|
|
192
|
-
---
|
|
193
|
-
|
|
194
|
-
## Component Conventions
|
|
195
|
-
|
|
196
|
-
> **CSS encoding**: internal CSS classes use short encoded names (e.g. `dp01`, `dp02`) per project convention.
|
|
197
|
-
|
|
198
|
-
> **Unique IDs**: if this component needs to generate HTML `id` attributes, always use `createUniqueId()` from `solid-js` — never `Math.random()` or `Date.now()`.
|
|
1
|
+
## COMPONENT IDENTITY
|
|
2
|
+
- **Import**: `import { Dropdown } from 'solid-tom-ui';`
|
|
3
|
+
- **Export**: `Dropdown` (named export)
|
|
4
|
+
- **Framework**: SolidJS
|
|
5
|
+
- **Purpose**: Dropdown that renders content into `document.body` via Portal; always visible above overflow-hidden containers; singleton behavior (only one open at a time)
|
|
6
|
+
|
|
7
|
+
## Props
|
|
8
|
+
|
|
9
|
+
| Prop | Type | Default | Description |
|
|
10
|
+
|------|------|---------|-------------|
|
|
11
|
+
| `triggerElement` | `SolidComponent` | — | Element rendered as the clickable trigger |
|
|
12
|
+
| `open` | `boolean` | `undefined` | Controlled mode: pass `true`/`false` to manage state externally |
|
|
13
|
+
| `position` | `'top' \| 'bottom' \| 'left' \| 'right'` | `'bottom'` | Which side the content appears on |
|
|
14
|
+
| `align` | `'start' \| 'center' \| 'end'` | `'start'` | Alignment along the cross axis |
|
|
15
|
+
| `blockScroll` | `boolean` | `false` | Lock page scroll while dropdown is open |
|
|
16
|
+
| `zIndex` | `number` | — | Override the automatic z-index |
|
|
17
|
+
| `class.anchor` | `string` | — | Class on the content anchor wrapper |
|
|
18
|
+
| `class.trigger` | `string` | — | Class on the trigger wrapper div |
|
|
19
|
+
| `class.content` | `string` | — | Class on the content wrapper div |
|
|
20
|
+
| `ref` | `HTMLDivElement \| ((el) => void)` | — | Ref to the trigger div |
|
|
21
|
+
|
|
22
|
+
## Basic Usage
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
<Dropdown triggerElement={<button class="btn btn-sm">Open</button>}>
|
|
26
|
+
<div class="bg-base-100 rounded-lg shadow-lg px-4 py-3 min-w-40">
|
|
27
|
+
<p>Dropdown content</p>
|
|
28
|
+
</div>
|
|
29
|
+
</Dropdown>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### With position and alignment
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
<Dropdown
|
|
36
|
+
position="top"
|
|
37
|
+
align="end"
|
|
38
|
+
triggerElement={<button class="btn btn-sm">Open ↑</button>}
|
|
39
|
+
>
|
|
40
|
+
<div class="bg-base-100 rounded-lg shadow-lg px-4 py-3">
|
|
41
|
+
Top-end positioned
|
|
42
|
+
</div>
|
|
43
|
+
</Dropdown>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Controlled mode
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
const [open, setOpen] = createSignal(false);
|
|
50
|
+
|
|
51
|
+
<button class="btn btn-sm" onClick={() => setOpen(v => !v)}>Toggle</button>
|
|
52
|
+
<Dropdown
|
|
53
|
+
open={open()}
|
|
54
|
+
triggerElement={<div class="border rounded px-3 py-1">Trigger</div>}
|
|
55
|
+
>
|
|
56
|
+
<div class="bg-base-100 rounded-lg shadow-lg px-4 py-3">
|
|
57
|
+
<button class="btn btn-xs btn-ghost" onClick={() => setOpen(false)}>Close</button>
|
|
58
|
+
</div>
|
|
59
|
+
</Dropdown>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Inside `overflow-hidden` container
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
// Portal mode escapes overflow-hidden — content is always visible
|
|
66
|
+
<div class="overflow-hidden h-16 w-48 border rounded flex items-center justify-center">
|
|
67
|
+
<Dropdown triggerElement={<button class="btn btn-sm">Open</button>}>
|
|
68
|
+
<div class="bg-base-100 shadow-lg px-4 py-3">Visible!</div>
|
|
69
|
+
</Dropdown>
|
|
70
|
+
</div>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Scroll lock
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
<Dropdown
|
|
77
|
+
blockScroll
|
|
78
|
+
triggerElement={<button class="btn btn-sm btn-warning">Open (scroll locked)</button>}
|
|
79
|
+
>
|
|
80
|
+
<div class="bg-base-100 rounded-lg shadow-lg px-6 py-4">
|
|
81
|
+
Page scroll is locked while this is open.
|
|
82
|
+
</div>
|
|
83
|
+
</Dropdown>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Behavior Details
|
|
87
|
+
|
|
88
|
+
### Singleton
|
|
89
|
+
|
|
90
|
+
Only one dropdown can be open at a time. Opening a second dropdown automatically closes the first. This is enforced by the global `dropdownStore`.
|
|
91
|
+
|
|
92
|
+
### Click outside to close
|
|
93
|
+
|
|
94
|
+
A full-screen invisible backdrop (`nd-backdrop`) catches clicks outside the content.
|
|
95
|
+
|
|
96
|
+
### Controlled vs uncontrolled
|
|
97
|
+
|
|
98
|
+
- **Uncontrolled** (no `open` prop): the component manages its own open/close state.
|
|
99
|
+
- **Controlled** (`open` prop provided): the component delegates state to the parent. The trigger click has no effect — the parent must update `open` manually. Controlled state is synced with `dropdownStore`.
|
|
100
|
+
|
|
101
|
+
### Position tracking (`blockScroll={false}`)
|
|
102
|
+
|
|
103
|
+
When open and `blockScroll` is false, the portal content anchor tracks the trigger element's position continuously via `element-tracker`. This ensures the dropdown stays aligned even when the page scrolls.
|
|
104
|
+
|
|
105
|
+
When `blockScroll={true}`, tracking stops (the page cannot scroll anyway, so the position is stable).
|
|
106
|
+
|
|
107
|
+
## Positioning System
|
|
108
|
+
|
|
109
|
+
Content position is computed from `getBoundingClientRect()` of the trigger, stored in CSS variables on the anchor element:
|
|
110
|
+
|
|
111
|
+
| Variable | Value |
|
|
112
|
+
|----------|-------|
|
|
113
|
+
| `--nd-t` | `trigger.top` |
|
|
114
|
+
| `--nd-l` | `trigger.left` |
|
|
115
|
+
| `--nd-w` | `trigger.width` |
|
|
116
|
+
| `--nd-h` | `trigger.height` |
|
|
117
|
+
|
|
118
|
+
`data-pos` and `data-align` attributes on `.nd-content-anchor` drive all position/alignment combinations via pure CSS. Gap between trigger and content is controlled by `--nd-gap` (default: `6px`).
|
|
119
|
+
|
|
120
|
+
## Common Patterns
|
|
121
|
+
|
|
122
|
+
### Menu list inside dropdown
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
<Dropdown triggerElement={<button class="btn btn-sm">Menu</button>}>
|
|
126
|
+
<ul class="bg-base-100 rounded-lg shadow-lg overflow-hidden min-w-40">
|
|
127
|
+
<li>
|
|
128
|
+
<button class="w-full px-4 py-2 text-left text-sm hover:bg-base-200 transition-colors">
|
|
129
|
+
Edit
|
|
130
|
+
</button>
|
|
131
|
+
</li>
|
|
132
|
+
<li>
|
|
133
|
+
<button class="w-full px-4 py-2 text-left text-sm text-error hover:bg-error/10 transition-colors">
|
|
134
|
+
Delete
|
|
135
|
+
</button>
|
|
136
|
+
</li>
|
|
137
|
+
</ul>
|
|
138
|
+
</Dropdown>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Icon trigger (three-dot menu)
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
import { MoreHorizontal } from 'lucide-solid';
|
|
145
|
+
|
|
146
|
+
<Dropdown
|
|
147
|
+
align="end"
|
|
148
|
+
triggerElement={
|
|
149
|
+
<button class="btn btn-ghost btn-xs btn-square">
|
|
150
|
+
<MoreHorizontal size={16} />
|
|
151
|
+
</button>
|
|
152
|
+
}
|
|
153
|
+
>
|
|
154
|
+
<ul class="bg-base-100 rounded-lg shadow-lg overflow-hidden min-w-36">
|
|
155
|
+
<li><button class="w-full px-4 py-2 text-left text-sm hover:bg-base-200">Rename</button></li>
|
|
156
|
+
<li><button class="w-full px-4 py-2 text-left text-sm hover:bg-base-200">Duplicate</button></li>
|
|
157
|
+
<li><button class="w-full px-4 py-2 text-left text-sm text-error hover:bg-error/10">Delete</button></li>
|
|
158
|
+
</ul>
|
|
159
|
+
</Dropdown>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Programmatic close from content
|
|
163
|
+
|
|
164
|
+
Use controlled mode to close the dropdown from inside:
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
const [open, setOpen] = createSignal(false);
|
|
168
|
+
|
|
169
|
+
<Dropdown
|
|
170
|
+
open={open()}
|
|
171
|
+
triggerElement={
|
|
172
|
+
<button class="btn btn-sm" onClick={() => setOpen(true)}>Open</button>
|
|
173
|
+
}
|
|
174
|
+
>
|
|
175
|
+
<div class="bg-base-100 rounded-lg shadow-lg px-4 py-3">
|
|
176
|
+
<button class="btn btn-xs" onClick={() => setOpen(false)}>Close me</button>
|
|
177
|
+
</div>
|
|
178
|
+
</Dropdown>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Styling Tips
|
|
182
|
+
|
|
183
|
+
- **Content width**: Portal content has `min-width: max-content` by default. Use `min-w-*` Tailwind classes on the content div to enforce a minimum width.
|
|
184
|
+
- **Custom gap**: Override `--nd-gap` on `.nd-content-anchor` via inline style or a custom CSS class.
|
|
185
|
+
- **Animation origin**: Animation scale origin (`transform-origin`) is automatically set per position/alignment via `--nd-origin` CSS variable — no manual adjustment needed.
|
|
186
|
+
|
|
187
|
+
## Gotchas
|
|
188
|
+
|
|
189
|
+
1. **Singleton**: Only one dropdown can be open at a time — enforced by `dropdownStore`.
|
|
190
|
+
2. **`class.content` vs content children**: `class.content` targets the `.nd-content` wrapper, not the children directly. Apply `rounded`, `shadow`, `bg-*` to the direct child element inside `<Dropdown>` for full visual control.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Component Conventions
|
|
195
|
+
|
|
196
|
+
> **CSS encoding**: internal CSS classes use short encoded names (e.g. `dp01`, `dp02`) per project convention.
|
|
197
|
+
|
|
198
|
+
> **Unique IDs**: if this component needs to generate HTML `id` attributes, always use `createUniqueId()` from `solid-js` — never `Math.random()` or `Date.now()`.
|