daisyui 1.1.0 → 1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/LICENSE.txt +21 -0
- data/README.md +327 -0
- data/app/javascript/daisy_ui/controllers/daisy_dropdown_controller.js +170 -0
- data/config/importmap.rb +12 -0
- data/lib/daisy_ui/accordion.rb +14 -8
- data/lib/daisy_ui/aura.rb +93 -0
- data/lib/daisy_ui/base.rb +1 -1
- data/lib/daisy_ui/breadcrumbs.rb +1 -1
- data/lib/daisy_ui/button.rb +6 -1
- data/lib/daisy_ui/carousel.rb +6 -6
- data/lib/daisy_ui/chat.rb +2 -6
- data/lib/daisy_ui/collapsible_sub_menu.rb +1 -1
- data/lib/daisy_ui/dropdown.rb +130 -13
- data/lib/daisy_ui/engine.rb +36 -0
- data/lib/daisy_ui/label.rb +9 -0
- data/lib/daisy_ui/link.rb +55 -10
- data/lib/daisy_ui/megamenu.rb +79 -0
- data/lib/daisy_ui/menu_item.rb +18 -3
- data/lib/daisy_ui/modal.rb +1 -2
- data/lib/daisy_ui/otp.rb +135 -0
- data/lib/daisy_ui/pagination.rb +1 -1
- data/lib/daisy_ui/radial_progress.rb +2 -2
- data/lib/daisy_ui/range.rb +8 -1
- data/lib/daisy_ui/steps.rb +4 -6
- data/lib/daisy_ui/sub_menu.rb +1 -1
- data/lib/daisy_ui/tab_with_content.rb +1 -1
- data/lib/daisy_ui/tab_without_content.rb +1 -1
- data/lib/daisy_ui/theme_controller.rb +13 -1
- data/lib/daisy_ui/toast.rb +6 -6
- data/lib/daisy_ui/tooltip.rb +30 -3
- data/lib/daisy_ui/updated_at.rb +1 -1
- data/lib/daisy_ui/version.rb +1 -1
- data/lib/daisy_ui.rb +7 -0
- metadata +10 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d82c1b4f52e8a5b62fb723e83557c1def70805b24c855aced7381c8c2f80dc0e
|
|
4
|
+
data.tar.gz: 831d1acb486a33fb1b779643d273a58925a921a456d20be7374cf4a5ed6c3cf4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 061d1ff9eb34a488db5ab1aff21491e9c4f8b39912c7749637e3941d9738edd5cbef993b5d52bd6e55620f4b6f4207f9ad7285df0f672507c3552f16be775d1a
|
|
7
|
+
data.tar.gz: 598db62e9eefc346e30b1d42d449bc49c54c6a80c5d917c21360fb1511ea0ed17380a2c9b2be8b4f1edd647d4281b5b134e18995be66ff1d884de17428a9d127
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- Aura component (DaisyUI 5.6) with style variants (dual, rainbow, holo, gold, silver, glow) and sizes
|
|
6
|
+
- Megamenu component (DaisyUI 5.6) with wide, full, vertical modifiers, sizes, and active_indicator sub-component
|
|
7
|
+
- OTP component (DaisyUI 5.6) for one-time password inputs with configurable digit count, joined modifier, sizes, and color variants
|
|
8
|
+
- `Dropdown` `:popover` modifier — renders the menu via the native Popover API
|
|
9
|
+
and CSS anchor positioning, so it opens in the top layer and escapes
|
|
10
|
+
`overflow` clipping (e.g. inside a table's `overflow-x-auto`). Accepts a
|
|
11
|
+
`popover_id:` for a stable id. Wires `aria-controls`/`aria-expanded` on the
|
|
12
|
+
trigger and `role="menu"` on the menu popover (the latter overridable, and
|
|
13
|
+
omitted for non-menu `content` panels). Default and `:tap_to_close` dropdowns
|
|
14
|
+
are unchanged. See the README "Dropdown `:popover` positioning" note for the
|
|
15
|
+
optional older-browser polyfill.
|
|
16
|
+
- Opt-in `daisy-dropdown` Stimulus controller for `:popover` dropdowns
|
|
17
|
+
(`Dropdown(:popover, stimulus: true)`), delivered to importmap-rails apps via
|
|
18
|
+
a gated Rails engine that auto-pins it. Provides a feature-detected JS
|
|
19
|
+
positioning fallback (Safari < 26, Firefox < 147) and optional roving
|
|
20
|
+
keyboard navigation. The default `:popover` dropdown remains zero-JS, and the
|
|
21
|
+
gem stays a plain Phlex library when Rails is absent.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Range component: added range-vertical modifier for vertical orientation (DaisyUI 5.6)
|
|
26
|
+
- Tooltip component: added alignment modifiers (tooltip-start, tooltip-center, tooltip-end) and tooltip-content sub-component (DaisyUI 5.6)
|
|
27
|
+
|
|
28
|
+
## [0.1.0] - 2024-08-02
|
|
29
|
+
|
|
30
|
+
- Initial release
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 David Alejandro
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
<picture>
|
|
2
|
+
<source srcset="https://github.com/user-attachments/assets/2c4d7fdb-abe7-4f71-a6d0-ef4d41b5625a" media="(prefers-color-scheme: dark)">
|
|
3
|
+
<img src="https://github.com/user-attachments/assets/9afa9755-4aab-412a-9dc9-5eb2f76c12d6" width="350" alt="DaisyUI logo"><br>
|
|
4
|
+
</picture>
|
|
5
|
+
|
|
6
|
+
A Ruby UI component library for DaisyUI using Phlex
|
|
7
|
+
|
|
8
|
+
# Installation
|
|
9
|
+
|
|
10
|
+
## 1. Install CSS dependencies
|
|
11
|
+
|
|
12
|
+
You can install TailwindCSS and DaisyUI either via a JS bundler or via importmaps.
|
|
13
|
+
|
|
14
|
+
### JS Bundler
|
|
15
|
+
|
|
16
|
+
**TailwindCSS**
|
|
17
|
+
|
|
18
|
+
Install TailwindCSS by following the instructions in the TailwindCSS documentation, using either the Tailwind CLI or PostCSS.
|
|
19
|
+
|
|
20
|
+
**DaisyUI**
|
|
21
|
+
|
|
22
|
+
Install DaisyUI by following the instructions in the DaisyUI documentation as a Node package.
|
|
23
|
+
|
|
24
|
+
### Importmaps
|
|
25
|
+
|
|
26
|
+
**TailwindCSS with DaisyUI**
|
|
27
|
+
|
|
28
|
+
You'll need to download a TailwindCSS standalone CLI that comes bundled with DaisyUI by following the instructions in the [tailwind-cli-extra repo](https://github.com/dobicinaitis/tailwind-cli-extra).
|
|
29
|
+
|
|
30
|
+
Afterwards, place it somewhere in your project, e.g. in the bin directory.
|
|
31
|
+
|
|
32
|
+
If you want to compile the standalone TailwindCSS CLI with DaisyUI yourself, you can follow the instructions here.
|
|
33
|
+
|
|
34
|
+
**tailwindcss-rails gem**
|
|
35
|
+
|
|
36
|
+
Install tailwindcss-rails gem for Rails to automatically include your TailwindCSS stylesheets when the asset pipeline compiles your assets.
|
|
37
|
+
|
|
38
|
+
For this, you'll need to install the gem by following the instructions in the [tailwindcss-rails repo](https://github.com/rails/tailwindcss-rails).
|
|
39
|
+
|
|
40
|
+
Finally, you'll need to set the `TAILWINDCSS_INSTALL_DIR` environment variable in your Rails app pointing to the directory where you placed the binary from the tailwind-cli-extra repo mentioned above. e.g. `TAILWINDCSS_INSTALL_DIR=bin`
|
|
41
|
+
|
|
42
|
+
## 2. Install Ruby dependencies
|
|
43
|
+
|
|
44
|
+
### Install Phlex
|
|
45
|
+
|
|
46
|
+
Install Phlex by following the instructions in the [Phlex documentation](https://www.phlex.fun/#rails-introduction).
|
|
47
|
+
|
|
48
|
+
### Install DaisyUI gem
|
|
49
|
+
|
|
50
|
+
1. Add the DaisyUI gem to your Gemfile:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
bundle add daisyui
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
2. (Optional) Include the `DaisyUI` module in `ApplicationComponent`:
|
|
57
|
+
|
|
58
|
+
```rb
|
|
59
|
+
class ApplicationComponent < Phlex::HTML
|
|
60
|
+
include DaisyUI
|
|
61
|
+
end
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This will allow you to use DaisyUI components using the short-form syntax. For example:
|
|
65
|
+
|
|
66
|
+
```rb
|
|
67
|
+
class SomeView < ApplicationView
|
|
68
|
+
def view_template
|
|
69
|
+
Button :primary do
|
|
70
|
+
"Hello, world!"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If you don't include DaisyUI, you can still use the namespaced syntax:
|
|
77
|
+
|
|
78
|
+
```rb
|
|
79
|
+
class SomeView < ApplicationView
|
|
80
|
+
def view_template
|
|
81
|
+
render DaisyUI::Button.new(:primary) do
|
|
82
|
+
"Hello, world!"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Consider not including DaisyUI in ApplicationComponent if:
|
|
89
|
+
|
|
90
|
+
- You have your own component library with the same component names as DaisyUI.
|
|
91
|
+
- You're including your own components module in `ApplicationComponent`.
|
|
92
|
+
|
|
93
|
+
In this scenario, including both DaisyUI and your own component library in `ApplicationComponent` will lead to naming conflicts.
|
|
94
|
+
|
|
95
|
+
3. Update your `tailwind.config.js` file to include DaisyUI component styles:
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
const execSync = require("child_process").execSync;
|
|
99
|
+
const outputDaisyUI = execSync("bundle show daisyui", { encoding: "utf-8" });
|
|
100
|
+
const daisyUIPath = outputDaisyUI.trim() + "/**/*.rb";
|
|
101
|
+
module.exports = {
|
|
102
|
+
content: [
|
|
103
|
+
// ... other paths
|
|
104
|
+
daisyUIPath,
|
|
105
|
+
],
|
|
106
|
+
};
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
4. Update your tailwind.config.js file to detect TailwindCSS classes in Ruby files.
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
module.exports = {
|
|
113
|
+
content: [
|
|
114
|
+
// ... other paths
|
|
115
|
+
//
|
|
116
|
+
// Note the "rb" extension at the end
|
|
117
|
+
"./app/views/**/*.{erb,haml,html,slim,rb}",
|
|
118
|
+
],
|
|
119
|
+
};
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
# Compatibility Notes
|
|
123
|
+
|
|
124
|
+
## @tailwindcss/forms plugin
|
|
125
|
+
|
|
126
|
+
If you're using the `@tailwindcss/forms` plugin alongside DaisyUI, you may encounter styling conflicts with form components like Toggle, Checkbox, and Radio. The forms plugin adds default checkbox/radio styling that can interfere with DaisyUI's custom styling.
|
|
127
|
+
|
|
128
|
+
**Solution:** Add the following CSS to your stylesheet to override the forms plugin styling for DaisyUI components:
|
|
129
|
+
|
|
130
|
+
```css
|
|
131
|
+
/* Override @tailwindcss/forms checkbox styles for DaisyUI components */
|
|
132
|
+
.toggle,
|
|
133
|
+
.checkbox,
|
|
134
|
+
.radio {
|
|
135
|
+
background-image: none !important;
|
|
136
|
+
}
|
|
137
|
+
.toggle:checked,
|
|
138
|
+
.checkbox:checked,
|
|
139
|
+
.radio:checked {
|
|
140
|
+
background-image: none !important;
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Alternatively, you can configure `@tailwindcss/forms` to use the `class` strategy instead of `base`, which only applies styles when you explicitly add form classes:
|
|
145
|
+
|
|
146
|
+
```js
|
|
147
|
+
// tailwind.config.js
|
|
148
|
+
plugins: [
|
|
149
|
+
require('@tailwindcss/forms')({
|
|
150
|
+
strategy: 'class', // only apply form styles to elements with form-* classes
|
|
151
|
+
}),
|
|
152
|
+
],
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Dropdown `:popover` positioning on older browsers
|
|
156
|
+
|
|
157
|
+
The `:popover` Dropdown modifier renders the menu in the browser top layer (so it
|
|
158
|
+
escapes `overflow` clipping) and positions it next to the trigger using **CSS
|
|
159
|
+
anchor positioning** (`anchor-name` / `position-anchor`). This works with **zero
|
|
160
|
+
JavaScript** on Chrome/Edge 125+, Safari 26+, and Firefox 147+.
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
Dropdown(:popover, :end) do |dropdown|
|
|
164
|
+
dropdown.button(:ghost, :sm) { "Actions" }
|
|
165
|
+
dropdown.menu(:sm, class: "w-52") do |menu|
|
|
166
|
+
menu.item { a(href: "#") { "Edit" } }
|
|
167
|
+
menu.item { a(href: "#") { "Delete" } }
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
On **older engines (Safari < 26, Firefox < 147)** the popover still opens in the
|
|
173
|
+
top layer, but without CSS anchor positioning it falls back to the viewport
|
|
174
|
+
default (DaisyUI centers it via `@supports not (position-area)`), so it is not
|
|
175
|
+
positioned next to the trigger.
|
|
176
|
+
|
|
177
|
+
To position correctly on those browsers with no application code, pin the
|
|
178
|
+
[OddBird CSS anchor positioning polyfill](https://github.com/oddbird/css-anchor-positioning)
|
|
179
|
+
and load it lazily:
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
# config/importmap.rb
|
|
183
|
+
pin "@oddbird/css-anchor-positioning", to: "https://ga.jspm.io/npm:@oddbird/css-anchor-positioning@1/dist/css-anchor-positioning.fn.js", preload: false
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
```js
|
|
187
|
+
// app/javascript/application.js — load only when the browser lacks native support
|
|
188
|
+
if (!CSS.supports("anchor-name: --x")) {
|
|
189
|
+
import("@oddbird/css-anchor-positioning").then(({ default: polyfill }) => polyfill())
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Modern browsers fetch nothing extra; the polyfill loads only where it is needed.
|
|
194
|
+
For WAI-ARIA roving keyboard navigation inside `role="menu"` menus (which a CSS
|
|
195
|
+
polyfill cannot provide), see the optional Stimulus controller below.
|
|
196
|
+
|
|
197
|
+
## Optional `daisy-dropdown` Stimulus controller
|
|
198
|
+
|
|
199
|
+
The `:popover` dropdown needs **no JavaScript** on modern browsers. For two
|
|
200
|
+
specific cases — a JS positioning fallback on browsers without CSS anchor
|
|
201
|
+
positioning, and roving keyboard navigation over `role="menu"` items — the gem
|
|
202
|
+
ships an **opt-in** Stimulus controller. It deliberately does **not** re-implement
|
|
203
|
+
open/toggle, light-dismiss, or Escape; the native Popover API already handles
|
|
204
|
+
those.
|
|
205
|
+
|
|
206
|
+
Under Rails with importmap-rails, the gem auto-pins the controller (no manual
|
|
207
|
+
pin). Register it once, lazily:
|
|
208
|
+
|
|
209
|
+
```js
|
|
210
|
+
// app/javascript/controllers/index.js
|
|
211
|
+
import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
|
|
212
|
+
lazyLoadControllersFrom("daisy_ui/controllers", application)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Then opt in per call site:
|
|
216
|
+
|
|
217
|
+
```ruby
|
|
218
|
+
Dropdown(:popover, :end, stimulus: true) do |dropdown|
|
|
219
|
+
dropdown.button(:ghost, :sm) { "Actions" }
|
|
220
|
+
dropdown.menu(:sm, class: "w-52") do |menu|
|
|
221
|
+
menu.item { a(href: "#", role: "menuitem", tabindex: "-1") { "Edit" } }
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
- `stimulus: true` wires the `daisy-dropdown` controller (namespaced to avoid
|
|
227
|
+
colliding with your own `dropdown` controller).
|
|
228
|
+
- `stimulus: "your-id"` overrides the identifier.
|
|
229
|
+
- The positioning fallback lazily imports `@floating-ui/dom` **only** when CSS
|
|
230
|
+
anchor positioning is unavailable (Safari < 26, Firefox < 147), so modern
|
|
231
|
+
browsers fetch nothing. The gem does **not** bundle it — if you enable the
|
|
232
|
+
controller **and** need to support those browsers, pin it yourself (lazy, so
|
|
233
|
+
modern browsers still skip it):
|
|
234
|
+
|
|
235
|
+
```ruby
|
|
236
|
+
# config/importmap.rb
|
|
237
|
+
pin "@floating-ui/dom", to: "https://ga.jspm.io/npm:@floating-ui/dom@1.7.6/dist/floating-ui.dom.mjs", preload: false
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
If the pin is missing the menu still opens (native popover) — it just won't be
|
|
241
|
+
repositioned on those legacy browsers, and the controller logs a console
|
|
242
|
+
warning. Evergreen-only apps can skip the pin entirely.
|
|
243
|
+
- Enable keyboard navigation with
|
|
244
|
+
`data: { daisy_dropdown_keyboard_value: true }` on the dropdown. Roving focus
|
|
245
|
+
targets `role="menuitem"` items, falling back to links/buttons in the menu.
|
|
246
|
+
|
|
247
|
+
JS-bundler (esbuild/vite/webpack) consumers: import the controller from the
|
|
248
|
+
gem's `app/javascript/daisy_ui/controllers/daisy_dropdown_controller.js` and
|
|
249
|
+
register it manually.
|
|
250
|
+
|
|
251
|
+
# MCP Server (Claude Code Integration)
|
|
252
|
+
|
|
253
|
+
This gem includes an MCP (Model Context Protocol) server that provides component information to AI assistants like Claude Code.
|
|
254
|
+
|
|
255
|
+
## Setup
|
|
256
|
+
|
|
257
|
+
Add to your Claude Code MCP settings (`~/.claude.json` or project `.claude.json`):
|
|
258
|
+
|
|
259
|
+
```json
|
|
260
|
+
{
|
|
261
|
+
"mcpServers": {
|
|
262
|
+
"daisyui": {
|
|
263
|
+
"command": "bundle",
|
|
264
|
+
"args": ["exec", "daisyui-mcp"]
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Available Tools
|
|
271
|
+
|
|
272
|
+
- **list_components** - List all available DaisyUI components
|
|
273
|
+
- **get_component** - Get detailed info about a specific component (modifiers, usage examples)
|
|
274
|
+
- **search_components** - Search components by name or modifier
|
|
275
|
+
|
|
276
|
+
# Usage
|
|
277
|
+
|
|
278
|
+
Refer to [the docs](https://daisyui.phlex.fun) to see how to use components. Here's an example:
|
|
279
|
+
|
|
280
|
+
```rb
|
|
281
|
+
Card :base_100 do |card|
|
|
282
|
+
figure do
|
|
283
|
+
img(src:)
|
|
284
|
+
end
|
|
285
|
+
card.body do
|
|
286
|
+
card.title do
|
|
287
|
+
"Shoes!"
|
|
288
|
+
end
|
|
289
|
+
p do
|
|
290
|
+
"If a dog chews shoes whose shoes does he choose?"
|
|
291
|
+
end
|
|
292
|
+
card.actions class: "justify-end" do
|
|
293
|
+
Button :primary do
|
|
294
|
+
"Buy Now"
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Which produces:
|
|
302
|
+
|
|
303
|
+
<img width="544" alt="Card example" src="https://github.com/user-attachments/assets/fad06a89-85fa-43cd-8c8f-7ed23b4ad77b">
|
|
304
|
+
|
|
305
|
+
# Development
|
|
306
|
+
|
|
307
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
308
|
+
|
|
309
|
+
# Contributing
|
|
310
|
+
|
|
311
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/mhenrixon/daisyui. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/mhenrixon/daisyui/blob/main/CODE_OF_CONDUCT.md).
|
|
312
|
+
|
|
313
|
+
1. Visit [the docs](https://daisyui.phlex.fun/) to see which components are still not implemented or not yet added to the docs.
|
|
314
|
+
|
|
315
|
+
2. Implement it.
|
|
316
|
+
|
|
317
|
+
3. After your PR is merged, [add it to the docs](https://github.com/mhenrixon/daisyui-docs).
|
|
318
|
+
|
|
319
|
+
4. Celebrate!
|
|
320
|
+
|
|
321
|
+
# License
|
|
322
|
+
|
|
323
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
324
|
+
|
|
325
|
+
# Code of Conduct
|
|
326
|
+
|
|
327
|
+
Everyone interacting in the DaisyUI project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/mhenrixon/daisyui/blob/main/CODE_OF_CONDUCT.md).
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
// daisy-dropdown — OPT-IN enhancement for the :popover Dropdown variant.
|
|
4
|
+
//
|
|
5
|
+
// The native Popover API already handles open/toggle, light-dismiss (outside
|
|
6
|
+
// click), Escape, and focus return — so this controller deliberately does NOT
|
|
7
|
+
// re-implement any of that. Its only jobs are:
|
|
8
|
+
//
|
|
9
|
+
// 1. A positioning fallback for browsers WITHOUT CSS anchor positioning
|
|
10
|
+
// (Safari < 26, Firefox < 147). On modern browsers it does nothing and
|
|
11
|
+
// fetches no extra JavaScript. The fallback lazily imports @floating-ui/dom,
|
|
12
|
+
// which the HOST app must pin (see README) — if it is missing the menu still
|
|
13
|
+
// opens (native popover), just unpositioned, and a console warning is logged.
|
|
14
|
+
// 2. Optional roving keyboard navigation over the menu's focusable items
|
|
15
|
+
// (enable with data-daisy-dropdown-keyboard-value="true").
|
|
16
|
+
//
|
|
17
|
+
// State is driven entirely by the popover's native `toggle` event; there is no
|
|
18
|
+
// internal open flag, no document-level listeners, and no display toggling.
|
|
19
|
+
const OFFSET = 4
|
|
20
|
+
|
|
21
|
+
// Focusable, interactive menu items. Prefers explicit role="menuitem", but falls
|
|
22
|
+
// back to the links/buttons DaisyUI menus actually render (<li><a>…</a></li>), so
|
|
23
|
+
// keyboard nav works on stock markup without forcing roles onto caller content.
|
|
24
|
+
const ITEM_SELECTOR = '[role="menuitem"]:not([aria-disabled="true"]), a[href]:not([aria-disabled="true"]), button:not([disabled])'
|
|
25
|
+
|
|
26
|
+
export default class extends Controller {
|
|
27
|
+
static targets = ["trigger", "menu"]
|
|
28
|
+
static values = { keyboard: { type: Boolean, default: false } }
|
|
29
|
+
|
|
30
|
+
connect() {
|
|
31
|
+
this.menuEl = this.hasMenuTarget ? this.menuTarget : this.element.querySelector("[popover]")
|
|
32
|
+
this.triggerEl = this.hasTriggerTarget ? this.triggerTarget : this.element.querySelector("[popovertarget]")
|
|
33
|
+
if (!this.menuEl) return
|
|
34
|
+
|
|
35
|
+
this.needsJsPositioning = !this.#supportsAnchorPositioning()
|
|
36
|
+
this.onToggle = (event) => this.#handleToggle(event)
|
|
37
|
+
this.menuEl.addEventListener("toggle", this.onToggle)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
disconnect() {
|
|
41
|
+
this.menuEl?.removeEventListener("toggle", this.onToggle)
|
|
42
|
+
this.#teardownOpen()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#handleToggle(event) {
|
|
46
|
+
if (event.newState === "open") this.#onOpen()
|
|
47
|
+
else this.#onClose()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async #onOpen() {
|
|
51
|
+
// The native Popover API does not mirror open state onto the invoker, so
|
|
52
|
+
// sync aria-expanded ourselves for assistive tech.
|
|
53
|
+
this.triggerEl?.setAttribute("aria-expanded", "true")
|
|
54
|
+
if (this.needsJsPositioning) await this.#position()
|
|
55
|
+
if (this.keyboardValue) this.#bindKeyboard()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#onClose() {
|
|
59
|
+
this.triggerEl?.setAttribute("aria-expanded", "false")
|
|
60
|
+
this.#teardownOpen()
|
|
61
|
+
// Do NOT restore focus — the native popover already returns focus to the trigger.
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#isOpen() {
|
|
65
|
+
return this.menuEl?.matches(":popover-open") ?? false
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
#teardownOpen() {
|
|
69
|
+
this.stopAutoUpdate?.()
|
|
70
|
+
this.stopAutoUpdate = null
|
|
71
|
+
if (this.keyHandler) {
|
|
72
|
+
this.menuEl?.removeEventListener("keydown", this.keyHandler)
|
|
73
|
+
this.keyHandler = null
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// --- positioning fallback (only when CSS anchor positioning is unavailable) ---
|
|
78
|
+
|
|
79
|
+
async #position() {
|
|
80
|
+
if (!this.triggerEl) return
|
|
81
|
+
|
|
82
|
+
// Lazy + feature-gated, so modern browsers never fetch the positioning lib.
|
|
83
|
+
// The host app is responsible for pinning @floating-ui/dom; if it is absent
|
|
84
|
+
// we degrade gracefully (native popover still opens, just unpositioned).
|
|
85
|
+
let floatingUi
|
|
86
|
+
try {
|
|
87
|
+
floatingUi = await import("@floating-ui/dom")
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.warn(
|
|
90
|
+
"[daisy-dropdown] @floating-ui/dom is not available; the popover will open unpositioned. " +
|
|
91
|
+
"Pin it in your import map to enable the legacy-browser positioning fallback.",
|
|
92
|
+
error,
|
|
93
|
+
)
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// A fast open→close during the dynamic import may have already closed us;
|
|
98
|
+
// bail rather than position (and leak an autoUpdate on) a closed menu.
|
|
99
|
+
if (!this.#isOpen()) return
|
|
100
|
+
|
|
101
|
+
const { computePosition, autoUpdate, flip, shift, offset } = floatingUi
|
|
102
|
+
|
|
103
|
+
this.menuEl.style.position = "fixed"
|
|
104
|
+
this.menuEl.style.margin = "0"
|
|
105
|
+
|
|
106
|
+
this.stopAutoUpdate = autoUpdate(this.triggerEl, this.menuEl, () => {
|
|
107
|
+
computePosition(this.triggerEl, this.menuEl, {
|
|
108
|
+
placement: this.#placement(),
|
|
109
|
+
middleware: [offset(OFFSET), flip(), shift({ padding: 8 })],
|
|
110
|
+
}).then(({ x, y }) => {
|
|
111
|
+
Object.assign(this.menuEl.style, { left: `${x}px`, top: `${y}px` })
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Derive Floating UI placement from DaisyUI's dropdown-* classes on the menu.
|
|
117
|
+
#placement() {
|
|
118
|
+
const cl = this.menuEl.classList
|
|
119
|
+
const side = cl.contains("dropdown-top")
|
|
120
|
+
? "top"
|
|
121
|
+
: cl.contains("dropdown-left")
|
|
122
|
+
? "left"
|
|
123
|
+
: cl.contains("dropdown-right")
|
|
124
|
+
? "right"
|
|
125
|
+
: "bottom"
|
|
126
|
+
const align = cl.contains("dropdown-end") ? "-end" : cl.contains("dropdown-center") ? "" : "-start"
|
|
127
|
+
return (side === "left" || side === "right") ? side : `${side}${align}`
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// --- optional roving keyboard navigation ---
|
|
131
|
+
|
|
132
|
+
#bindKeyboard() {
|
|
133
|
+
this.keyHandler = (event) => this.#onKeydown(event)
|
|
134
|
+
this.menuEl.addEventListener("keydown", this.keyHandler)
|
|
135
|
+
this.#items()[0]?.focus()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#onKeydown(event) {
|
|
139
|
+
const items = this.#items()
|
|
140
|
+
if (items.length === 0) return
|
|
141
|
+
const index = items.indexOf(document.activeElement)
|
|
142
|
+
|
|
143
|
+
switch (event.key) {
|
|
144
|
+
case "ArrowDown":
|
|
145
|
+
event.preventDefault()
|
|
146
|
+
items[(index + 1 + items.length) % items.length].focus()
|
|
147
|
+
break
|
|
148
|
+
case "ArrowUp":
|
|
149
|
+
event.preventDefault()
|
|
150
|
+
items[(index - 1 + items.length) % items.length].focus()
|
|
151
|
+
break
|
|
152
|
+
case "Home":
|
|
153
|
+
event.preventDefault()
|
|
154
|
+
items[0].focus()
|
|
155
|
+
break
|
|
156
|
+
case "End":
|
|
157
|
+
event.preventDefault()
|
|
158
|
+
items[items.length - 1].focus()
|
|
159
|
+
break
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#items() {
|
|
164
|
+
return Array.from(this.menuEl.querySelectorAll(ITEM_SELECTOR))
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#supportsAnchorPositioning() {
|
|
168
|
+
return typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("anchor-name: --x")
|
|
169
|
+
}
|
|
170
|
+
}
|
data/config/importmap.rb
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Auto-pins the gem's bundled Stimulus controllers for importmap-rails consumers.
|
|
4
|
+
# Exposes `daisy_ui/controllers/daisy_dropdown_controller`. Register it in the
|
|
5
|
+
# host app (lazily recommended):
|
|
6
|
+
#
|
|
7
|
+
# // app/javascript/controllers/index.js
|
|
8
|
+
# import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
|
|
9
|
+
# lazyLoadControllersFrom("daisy_ui/controllers", application)
|
|
10
|
+
pin_all_from DaisyUI::Engine.root.join("app/javascript/daisy_ui/controllers"),
|
|
11
|
+
under: "daisy_ui/controllers",
|
|
12
|
+
to: "daisy_ui/controllers"
|
data/lib/daisy_ui/accordion.rb
CHANGED
|
@@ -14,8 +14,8 @@ module DaisyUI
|
|
|
14
14
|
def view_template(&block)
|
|
15
15
|
public_send(as, class: classes, **attributes) do
|
|
16
16
|
input(type: :radio, name:, checked:)
|
|
17
|
-
div(class:
|
|
18
|
-
div(class: "collapse-content", &block) if block
|
|
17
|
+
div(class: component_classes("collapse-title", options: title_options || {}), &title_block) if title_block
|
|
18
|
+
div(class: component_classes("collapse-content", options: {}), &block) if block
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
@@ -28,28 +28,34 @@ module DaisyUI
|
|
|
28
28
|
|
|
29
29
|
attr_reader :name, :checked, :title_block, :title_options
|
|
30
30
|
|
|
31
|
-
def build_title_classes
|
|
32
|
-
classes = ["collapse-title"]
|
|
33
|
-
classes << title_options.delete(:class) if title_options&.dig(:class)
|
|
34
|
-
classes.compact.join(" ")
|
|
35
|
-
end
|
|
36
|
-
|
|
37
31
|
register_modifiers(
|
|
38
32
|
# "sm:collapse-arrow"
|
|
33
|
+
# "@sm:collapse-arrow"
|
|
39
34
|
# "md:collapse-arrow"
|
|
35
|
+
# "@md:collapse-arrow"
|
|
40
36
|
# "lg:collapse-arrow"
|
|
37
|
+
# "@lg:collapse-arrow"
|
|
41
38
|
arrow: "collapse-arrow",
|
|
42
39
|
# "sm:collapse-plus"
|
|
40
|
+
# "@sm:collapse-plus"
|
|
43
41
|
# "md:collapse-plus"
|
|
42
|
+
# "@md:collapse-plus"
|
|
44
43
|
# "lg:collapse-plus"
|
|
44
|
+
# "@lg:collapse-plus"
|
|
45
45
|
plus: "collapse-plus",
|
|
46
46
|
# "sm:collapse-open"
|
|
47
|
+
# "@sm:collapse-open"
|
|
47
48
|
# "md:collapse-open"
|
|
49
|
+
# "@md:collapse-open"
|
|
48
50
|
# "lg:collapse-open"
|
|
51
|
+
# "@lg:collapse-open"
|
|
49
52
|
open: "collapse-open",
|
|
50
53
|
# "sm:collapse-close"
|
|
54
|
+
# "@sm:collapse-close"
|
|
51
55
|
# "md:collapse-close"
|
|
56
|
+
# "@md:collapse-close"
|
|
52
57
|
# "lg:collapse-close"
|
|
58
|
+
# "@lg:collapse-close"
|
|
53
59
|
close: "collapse-close"
|
|
54
60
|
)
|
|
55
61
|
end
|