phlex_kit 0.5.1 → 0.6.1
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/README.md +21 -8
- data/app/components/phlex_kit/accordion/accordion.css +2 -0
- data/app/components/phlex_kit/accordion/accordion.rb +1 -1
- data/app/components/phlex_kit/accordion/accordion_content.rb +3 -1
- data/app/components/phlex_kit/accordion/accordion_controller.js +26 -2
- data/app/components/phlex_kit/accordion/accordion_default_trigger.rb +20 -4
- data/app/components/phlex_kit/accordion/accordion_trigger.rb +18 -2
- data/app/components/phlex_kit/alert/alert.rb +1 -1
- data/app/components/phlex_kit/alert_dialog/alert_dialog_cancel.rb +1 -1
- data/app/components/phlex_kit/alert_dialog/alert_dialog_content.rb +1 -1
- data/app/components/phlex_kit/attachment/attachment.rb +2 -2
- data/app/components/phlex_kit/attachment/attachment_media.rb +1 -1
- data/app/components/phlex_kit/avatar/avatar.rb +1 -1
- data/app/components/phlex_kit/badge/badge.rb +1 -1
- data/app/components/phlex_kit/button/button.rb +1 -1
- data/app/components/phlex_kit/button_group/button_group.rb +1 -1
- data/app/components/phlex_kit/calendar/calendar_controller.js +12 -8
- data/app/components/phlex_kit/card/card.rb +1 -1
- data/app/components/phlex_kit/carousel/carousel.rb +1 -1
- data/app/components/phlex_kit/clipboard/clipboard_popover.rb +1 -1
- data/app/components/phlex_kit/combobox/combobox.css +21 -12
- data/app/components/phlex_kit/combobox/combobox.rb +19 -3
- data/app/components/phlex_kit/combobox/combobox_badge_trigger.rb +7 -2
- data/app/components/phlex_kit/combobox/combobox_checkbox.rb +3 -0
- data/app/components/phlex_kit/combobox/combobox_controller.js +85 -20
- data/app/components/phlex_kit/combobox/combobox_input_trigger.rb +11 -2
- data/app/components/phlex_kit/combobox/combobox_item.rb +4 -1
- data/app/components/phlex_kit/combobox/combobox_list.rb +9 -2
- data/app/components/phlex_kit/combobox/combobox_popover.rb +8 -7
- data/app/components/phlex_kit/combobox/combobox_radio.rb +3 -0
- data/app/components/phlex_kit/combobox/combobox_search_input.rb +8 -3
- data/app/components/phlex_kit/command/command.rb +17 -2
- data/app/components/phlex_kit/command/command_controller.js +93 -3
- data/app/components/phlex_kit/command/command_dialog_content.rb +16 -4
- data/app/components/phlex_kit/command/command_input.rb +7 -2
- data/app/components/phlex_kit/command/command_item.rb +12 -3
- data/app/components/phlex_kit/command/command_list.rb +9 -2
- data/app/components/phlex_kit/context_menu/context_menu.css +25 -13
- data/app/components/phlex_kit/context_menu/context_menu_content.rb +1 -1
- data/app/components/phlex_kit/context_menu/context_menu_controller.js +71 -12
- data/app/components/phlex_kit/context_menu/context_menu_item.rb +7 -3
- data/app/components/phlex_kit/context_menu/context_menu_label.rb +1 -1
- data/app/components/phlex_kit/context_menu/context_menu_trigger.rb +3 -1
- data/app/components/phlex_kit/data_table/data_table_column_visibility_controller.js +1 -1
- data/app/components/phlex_kit/data_table/data_table_controller.js +12 -1
- data/app/components/phlex_kit/data_table/data_table_pagination.rb +12 -10
- data/app/components/phlex_kit/data_table/data_table_selection_summary.rb +1 -0
- data/app/components/phlex_kit/data_table/data_table_sort_head.rb +5 -1
- data/app/components/phlex_kit/dialog/dialog_content.rb +12 -4
- data/app/components/phlex_kit/dialog/dialog_controller.js +13 -0
- data/app/components/phlex_kit/drawer/drawer_content.rb +2 -2
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu.css +36 -15
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_content.rb +4 -3
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_controller.js +8 -11
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_item.rb +1 -1
- data/app/components/phlex_kit/empty/empty_media.rb +1 -1
- data/app/components/phlex_kit/field/field.rb +1 -1
- data/app/components/phlex_kit/field/field_legend.rb +1 -1
- data/app/components/phlex_kit/hover_card/hover_card.css +27 -8
- data/app/components/phlex_kit/hover_card/hover_card.rb +2 -1
- data/app/components/phlex_kit/hover_card/hover_card_content.rb +2 -2
- data/app/components/phlex_kit/hover_card/hover_card_controller.js +19 -5
- data/app/components/phlex_kit/input_group/input_group_addon.rb +1 -1
- data/app/components/phlex_kit/input_otp/input_otp.rb +4 -1
- data/app/components/phlex_kit/input_otp/input_otp_controller.js +12 -1
- data/app/components/phlex_kit/item/item.rb +1 -1
- data/app/components/phlex_kit/item/item_media.rb +1 -1
- data/app/components/phlex_kit/link/link.rb +1 -1
- data/app/components/phlex_kit/marker/marker.rb +1 -1
- data/app/components/phlex_kit/menubar/menubar.css +36 -14
- data/app/components/phlex_kit/menubar/menubar.rb +1 -1
- data/app/components/phlex_kit/menubar/menubar_checkbox_item.rb +5 -2
- data/app/components/phlex_kit/menubar/menubar_content.rb +1 -1
- data/app/components/phlex_kit/menubar/menubar_controller.js +100 -12
- data/app/components/phlex_kit/menubar/menubar_item.rb +8 -5
- data/app/components/phlex_kit/menubar/menubar_radio_item.rb +5 -2
- data/app/components/phlex_kit/menubar/menubar_trigger.rb +1 -0
- data/app/components/phlex_kit/native_select/native_select.rb +1 -1
- data/app/components/phlex_kit/navigation_menu/navigation_menu.css +24 -7
- data/app/components/phlex_kit/navigation_menu/navigation_menu_content.rb +3 -1
- data/app/components/phlex_kit/popover/popover.css +24 -9
- data/app/components/phlex_kit/popover/popover.rb +2 -1
- data/app/components/phlex_kit/popover/popover_content.rb +6 -4
- data/app/components/phlex_kit/popover/popover_controller.js +12 -8
- data/app/components/phlex_kit/popover/popover_trigger.rb +1 -1
- data/app/components/phlex_kit/resizable/resizable_panel.rb +4 -2
- data/app/components/phlex_kit/select/select.css +24 -12
- data/app/components/phlex_kit/select/select_content.rb +6 -4
- data/app/components/phlex_kit/select/select_controller.js +21 -17
- data/app/components/phlex_kit/select/select_trigger.rb +1 -1
- data/app/components/phlex_kit/sheet/sheet_content.rb +2 -2
- data/app/components/phlex_kit/sheet/sheet_content_controller.js +68 -2
- data/app/components/phlex_kit/sheet/sheet_controller.js +28 -3
- data/app/components/phlex_kit/sidebar/sidebar.rb +1 -1
- data/app/components/phlex_kit/sidebar/sidebar_wrapper.rb +1 -1
- data/app/components/phlex_kit/spinner/spinner.rb +1 -1
- data/app/components/phlex_kit/switch/switch.rb +7 -4
- data/app/components/phlex_kit/tabs/tabs.rb +1 -1
- data/app/components/phlex_kit/tabs/tabs_content.rb +4 -1
- data/app/components/phlex_kit/tabs/tabs_controller.js +60 -7
- data/app/components/phlex_kit/tabs/tabs_list.rb +2 -2
- data/app/components/phlex_kit/tabs/tabs_trigger.rb +14 -2
- data/app/components/phlex_kit/theme_toggle/theme_toggle.rb +11 -3
- data/app/components/phlex_kit/toast/toast_controller.js +17 -0
- data/app/components/phlex_kit/toast/toast_icon.rb +1 -1
- data/app/components/phlex_kit/toast/toast_item.rb +1 -1
- data/app/components/phlex_kit/toggle/toggle.rb +2 -2
- data/app/components/phlex_kit/toggle_group/toggle_group.rb +2 -2
- data/app/components/phlex_kit/toggle_group/toggle_group_item.rb +1 -1
- data/app/components/phlex_kit/tooltip/tooltip.css +40 -22
- data/app/components/phlex_kit/tooltip/tooltip.rb +3 -1
- data/app/components/phlex_kit/tooltip/tooltip_content.rb +1 -1
- data/app/components/phlex_kit/tooltip/tooltip_controller.js +32 -0
- data/app/components/phlex_kit/tooltip/tooltip_trigger.rb +8 -3
- data/app/components/phlex_kit/typography/text.rb +1 -1
- data/app/javascript/phlex_kit/controllers/index.js +2 -0
- data/lib/generators/phlex_kit/component/component_generator.rb +28 -0
- data/lib/generators/phlex_kit/install/templates/phlex_kit.rb +3 -0
- data/lib/phlex_kit/base_component.rb +12 -0
- data/lib/phlex_kit/engine.rb +11 -1
- data/lib/phlex_kit/icons/lucide.rb +1 -1
- data/lib/phlex_kit/icons/phosphor.rb +1 -1
- data/lib/phlex_kit/icons/remix.rb +1 -1
- data/lib/phlex_kit/icons/tabler.rb +1 -1
- data/lib/phlex_kit/version.rb +1 -1
- data/lib/phlex_kit.rb +5 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 74f3d49a1a6adfab950d193db75d0cce12c02308c10fe69566d7a62ea0a835e3
|
|
4
|
+
data.tar.gz: 0b673ba98ab594e4f2783a32c83b792e849605cd43fb1033d2bd258dece4c789
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d0e680e98ac74f15fa085a2828e7aa86041d67fa58ec82dd07d9f2912bc06c063ef58b5fcdcba5c44b037cc7b4070b01869b891da449ad8bcbb9ce4eb62facf4
|
|
7
|
+
data.tar.gz: ddea978e8b26eaf08dcb373df2c59bec15405f0c043ca349c8c6f6e1c20194f75712281d526f70bc0fc234fcb2aeb41a79f42386182494a6a597af3bb9868fe3
|
data/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
A [shadcn/ui](https://ui.shadcn.com)-grade component kit for
|
|
7
7
|
[Phlex](https://phlex.fun) + Rails, styled with **vanilla CSS + design tokens
|
|
8
8
|
instead of Tailwind**. The full [ruby_ui](https://ruby-ui.com) catalog plus
|
|
9
|
-
shadcn's own additions —
|
|
9
|
+
shadcn's own additions — 72 component families, 40 Stimulus controllers —
|
|
10
10
|
with the default theme lifted verbatim from shadcn/ui's current token system.
|
|
11
11
|
|
|
12
12
|
<picture>
|
|
@@ -21,7 +21,9 @@ switches, nav menu, command palette — at `/create` in [the docs site](#the-doc
|
|
|
21
21
|
|
|
22
22
|
- **No build step.** Components are plain Ruby classes with co-located vanilla
|
|
23
23
|
CSS, served precompiled-static through Propshaft. No Tailwind, no Node, no
|
|
24
|
-
PostCSS.
|
|
24
|
+
PostCSS. (Outside Rails there's no Zeitwerk to autoload them — after
|
|
25
|
+
`require "phlex_kit"`, require the component files you use, e.g.
|
|
26
|
+
`Dir[File.join(gem_dir, "app/components/phlex_kit/**/*.rb")].sort.each { require it }`.)
|
|
25
27
|
- **The shadcn look, themeable.** Every component reads `--pk-*` custom
|
|
26
28
|
properties. The default palette, radii, and control geometry match
|
|
27
29
|
ui.shadcn.com; redefine `:root` and the whole kit re-themes live — dark,
|
|
@@ -119,8 +121,8 @@ PhlexKit.configure { |c| c.define_ui_alias = true } # UI == PhlexKit
|
|
|
119
121
|
|
|
120
122
|
The gem ships shadcn/ui's default (neutral) token set — dark by default, light
|
|
121
123
|
via `<html data-theme="light">`, OS-following via `data-theme="system"`.
|
|
122
|
-
Override any token in your own stylesheet
|
|
123
|
-
gem's,
|
|
124
|
+
Override any token in your own stylesheet — the installer prepends the gem's
|
|
125
|
+
`@import`, so the gem's rules come first and yours, coming later, win:
|
|
124
126
|
|
|
125
127
|
```css
|
|
126
128
|
@import url("phlex_kit/phlex_kit.css");
|
|
@@ -137,8 +139,9 @@ The full token surface: `--pk-bg`, `--pk-surface`, `--pk-surface-2`,
|
|
|
137
139
|
`--pk-brand`, `--pk-brand-ink`, `--pk-green`, `--pk-amber`, `--pk-red`,
|
|
138
140
|
`--pk-chart-1..5`, `--pk-radius`, plus `--pk-font-sans` / `--pk-font-serif` /
|
|
139
141
|
`--pk-font-mono` (system stacks by default — override them after an @font-face to adopt Geist
|
|
140
|
-
like shadcn, as the docs site does). Want a fully custom palette?
|
|
141
|
-
|
|
142
|
+
like shadcn, as the docs site does). Want a fully custom palette? Override
|
|
143
|
+
every token in your stylesheet (the `_tokens.css` import lives inside the
|
|
144
|
+
gem's manifest, so you re-declare rather than remove). The complete reference —
|
|
142
145
|
defaults, shadcn equivalents, the optional `--pk-sidebar-*` overrides, and
|
|
143
146
|
every per-component knob (`--pk-card-spacing`, `--pk-cell-size`, …) — is in
|
|
144
147
|
[docs/07-TOKENS.md](docs/07-TOKENS.md).
|
|
@@ -248,7 +251,16 @@ for another component reuses the same wiring — it's injected only once.
|
|
|
248
251
|
The dummy Rails app is a full shadcn-style docs site: a sidebar menu with one
|
|
249
252
|
page per component, every use case demoed with a Preview/Code toggle (the code
|
|
250
253
|
shown is extracted from the running example source, so it can never drift).
|
|
251
|
-
A one-page kitchen-sink gallery lives at /gallery
|
|
254
|
+
A one-page kitchen-sink gallery lives at /gallery, and /examples holds six
|
|
255
|
+
full-page admin screens (dashboard, orders, inbox, profile, reports, settings)
|
|
256
|
+
for a fictional commerce app — every visible element a kit component:
|
|
257
|
+
|
|
258
|
+
<picture>
|
|
259
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/images/examples-dashboard-dark.png">
|
|
260
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/images/examples-dashboard-light.png">
|
|
261
|
+
<img alt="The /examples admin dashboard — KPI cards, a revenue chart and a live activity feed inside the kit's sidebar shell" src="docs/images/examples-dashboard-light.png">
|
|
262
|
+
</picture>
|
|
263
|
+
|
|
252
264
|
|
|
253
265
|
```bash
|
|
254
266
|
git clone https://github.com/MatthewKennedy/phlex_kit && cd phlex_kit
|
|
@@ -284,7 +296,8 @@ alert dialog, aspect ratio, attachment, avatar (+ group), badge, breadcrumb,
|
|
|
284
296
|
bubble, button, button group, calendar, card, carousel, chart, checkbox,
|
|
285
297
|
clipboard, codeblock, collapsible, combobox (button/input/badge-chip
|
|
286
298
|
triggers), command palette, context menu, data table, date picker, dialog,
|
|
287
|
-
drawer, dropdown menu, empty,
|
|
299
|
+
drawer, dropdown menu, empty, field (set / group / label / description /
|
|
300
|
+
error), form + form field, hover card, input, input
|
|
288
301
|
group, input OTP, item, kbd, label, link, masked input, menubar, message,
|
|
289
302
|
message scroller, native select, navigation menu, pagination, popover,
|
|
290
303
|
marker, progress, radio button + radio group, resizable, scroll area, select,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/* Co-located with accordion.rb. Theme tokens from the global stylesheet. */
|
|
2
2
|
.pk-accordion { width: 100%; }
|
|
3
3
|
.pk-accordion-item { border-bottom: 1px solid var(--pk-border); }
|
|
4
|
+
/* APG: triggers live inside headings — the heading is a neutral wrapper. */
|
|
5
|
+
.pk-accordion-heading { margin: 0; display: flex; font-size: inherit; font-weight: inherit; line-height: inherit; }
|
|
4
6
|
.pk-accordion-trigger {
|
|
5
7
|
width: 100%; display: flex; flex: 1; align-items: center; justify-content: space-between;
|
|
6
8
|
padding-block: 1rem; font-size: .875rem; font-weight: 500; text-align: left;
|
|
@@ -13,7 +13,7 @@ module PhlexKit
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def view_template(&)
|
|
16
|
-
div(**mix({ class: "pk-accordion", data: { type: TYPES
|
|
16
|
+
div(**mix({ class: "pk-accordion", data: { type: fetch_option(TYPES, @type, :type) } }, @attrs), &)
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
end
|
|
@@ -2,7 +2,9 @@ module PhlexKit
|
|
|
2
2
|
class AccordionContent < BaseComponent
|
|
3
3
|
def initialize(**attrs) = (@attrs = attrs)
|
|
4
4
|
def view_template(&)
|
|
5
|
-
|
|
5
|
+
# role="region" + aria-labelledby → trigger id (wired by the controller
|
|
6
|
+
# in connect(), which also assigns the ids), per the APG accordion pattern.
|
|
7
|
+
div(**mix({ class: "pk-accordion-content", role: "region", data: { phlex_kit__accordion_target: "content", state: "closed" }, style: "height: 0px;", hidden: true }, @attrs), &)
|
|
6
8
|
end
|
|
7
9
|
end
|
|
8
10
|
end
|
|
@@ -3,8 +3,10 @@ import { Controller } from "@hotwired/stimulus"
|
|
|
3
3
|
// Connects to data-controller="phlex-kit--accordion"
|
|
4
4
|
// Ported from ruby_ui's ruby-ui--accordion controller, reworked to use the native
|
|
5
5
|
// Web Animations API instead of the `motion` package (no extra JS dependency).
|
|
6
|
+
let uid = 0
|
|
7
|
+
|
|
6
8
|
export default class extends Controller {
|
|
7
|
-
static targets = ["icon", "content"]
|
|
9
|
+
static targets = ["icon", "content", "trigger"]
|
|
8
10
|
static values = {
|
|
9
11
|
open: { type: Boolean, default: false },
|
|
10
12
|
animationDuration: { type: Number, default: 150 },
|
|
@@ -12,6 +14,7 @@ export default class extends Controller {
|
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
connect() {
|
|
17
|
+
this.wireAria()
|
|
15
18
|
const d = this.animationDurationValue
|
|
16
19
|
this.animationDurationValue = 0
|
|
17
20
|
this.openValue ? this.open() : this.close()
|
|
@@ -35,7 +38,28 @@ export default class extends Controller {
|
|
|
35
38
|
if (ctrl && ctrl.openValue) ctrl.openValue = false
|
|
36
39
|
})
|
|
37
40
|
}
|
|
38
|
-
openValueChanged(isOpen) {
|
|
41
|
+
openValueChanged(isOpen) {
|
|
42
|
+
if (this.hasTriggerTarget) this.triggerTarget.setAttribute("aria-expanded", isOpen ? "true" : "false")
|
|
43
|
+
isOpen ? this.open() : this.close()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Pair the trigger with its content: assign per-instance ids (render time
|
|
47
|
+
// has no shared item value, so the parts can't derive them), wire
|
|
48
|
+
// aria-controls/aria-labelledby, and propagate a disabled item onto the
|
|
49
|
+
// actual button so it drops out of the tab order.
|
|
50
|
+
wireAria() {
|
|
51
|
+
if (!this.hasTriggerTarget) return
|
|
52
|
+
const id = ++uid
|
|
53
|
+
const trigger = this.triggerTarget
|
|
54
|
+
if (!trigger.id) trigger.id = `pk-accordion-trigger-${id}`
|
|
55
|
+
if (this.hasContentTarget) {
|
|
56
|
+
const content = this.contentTarget
|
|
57
|
+
if (!content.id) content.id = `pk-accordion-panel-${id}`
|
|
58
|
+
trigger.setAttribute("aria-controls", content.id)
|
|
59
|
+
content.setAttribute("aria-labelledby", trigger.id)
|
|
60
|
+
}
|
|
61
|
+
if ("disabled" in this.element.dataset) trigger.disabled = true
|
|
62
|
+
}
|
|
39
63
|
|
|
40
64
|
open() {
|
|
41
65
|
if (!this.hasContentTarget) return
|
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
module PhlexKit
|
|
2
2
|
# Convenience trigger: label on the left, chevron icon on the right.
|
|
3
3
|
class AccordionDefaultTrigger < BaseComponent
|
|
4
|
-
def initialize(
|
|
4
|
+
def initialize(open: false, disabled: false, heading_level: 3, **attrs)
|
|
5
|
+
raise ArgumentError, "heading_level must be 1..6" unless (1..6).cover?(heading_level)
|
|
6
|
+
@open = open
|
|
7
|
+
@disabled = disabled
|
|
8
|
+
@heading_level = heading_level
|
|
9
|
+
@attrs = attrs
|
|
10
|
+
end
|
|
5
11
|
def view_template(&block)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
12
|
+
# APG accordion: each trigger is a button wrapped in a heading.
|
|
13
|
+
# aria-expanded reflects the render-time open state; the controller keeps
|
|
14
|
+
# it in sync and wires aria-controls to the content's id in connect().
|
|
15
|
+
base = {
|
|
16
|
+
type: "button", class: "pk-accordion-trigger",
|
|
17
|
+
aria_expanded: @open ? "true" : "false", disabled: @disabled,
|
|
18
|
+
data: { phlex_kit__accordion_target: "trigger", action: "click->phlex-kit--accordion#toggle" }
|
|
19
|
+
}
|
|
20
|
+
send(:"h#{@heading_level}", class: "pk-accordion-heading") do
|
|
21
|
+
button(**mix(base, @attrs)) do
|
|
22
|
+
span(class: "pk-accordion-trigger-label", &block)
|
|
23
|
+
render PhlexKit::AccordionIcon.new
|
|
24
|
+
end
|
|
9
25
|
end
|
|
10
26
|
end
|
|
11
27
|
end
|
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
module PhlexKit
|
|
2
2
|
class AccordionTrigger < BaseComponent
|
|
3
|
-
def initialize(
|
|
3
|
+
def initialize(open: false, disabled: false, heading_level: 3, **attrs)
|
|
4
|
+
raise ArgumentError, "heading_level must be 1..6" unless (1..6).cover?(heading_level)
|
|
5
|
+
@open = open
|
|
6
|
+
@disabled = disabled
|
|
7
|
+
@heading_level = heading_level
|
|
8
|
+
@attrs = attrs
|
|
9
|
+
end
|
|
4
10
|
def view_template(&)
|
|
5
|
-
|
|
11
|
+
# APG accordion: each trigger is a button wrapped in a heading.
|
|
12
|
+
# aria-expanded reflects the render-time open state; the controller keeps
|
|
13
|
+
# it in sync and wires aria-controls to the content's id in connect().
|
|
14
|
+
base = {
|
|
15
|
+
type: "button", class: "pk-accordion-trigger",
|
|
16
|
+
aria_expanded: @open ? "true" : "false", disabled: @disabled,
|
|
17
|
+
data: { phlex_kit__accordion_target: "trigger", action: "click->phlex-kit--accordion#toggle" }
|
|
18
|
+
}
|
|
19
|
+
send(:"h#{@heading_level}", class: "pk-accordion-heading") do
|
|
20
|
+
button(**mix(base, @attrs), &)
|
|
21
|
+
end
|
|
6
22
|
end
|
|
7
23
|
end
|
|
8
24
|
end
|
|
@@ -7,7 +7,7 @@ module PhlexKit
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def view_template(&block)
|
|
10
|
-
render PhlexKit::Button.new(variant: :outline, data: { action: "click->phlex-kit--alert-dialog#dismiss" },
|
|
10
|
+
render PhlexKit::Button.new(variant: :outline, **mix({ data: { action: "click->phlex-kit--alert-dialog#dismiss" } }, @attrs), &block)
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
end
|
|
@@ -15,7 +15,7 @@ module PhlexKit
|
|
|
15
15
|
template(**mix({ data: { phlex_kit__alert_dialog_target: "content" } }, @attrs)) do
|
|
16
16
|
div(data: { controller: "phlex-kit--alert-dialog" }) do
|
|
17
17
|
div(class: "pk-alert-dialog-overlay", "aria-hidden": "true")
|
|
18
|
-
div(role: "alertdialog", "aria-modal": "true", class: [ "pk-alert-dialog-panel", SIZES
|
|
18
|
+
div(role: "alertdialog", "aria-modal": "true", class: [ "pk-alert-dialog-panel", fetch_option(SIZES, @size, :size) ].compact.join(" "), &block)
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
end
|
|
@@ -20,8 +20,8 @@ module PhlexKit
|
|
|
20
20
|
|
|
21
21
|
def view_template(&)
|
|
22
22
|
div(**mix({
|
|
23
|
-
class: [ "pk-attachment", SIZES
|
|
24
|
-
data: { state: STATES
|
|
23
|
+
class: [ "pk-attachment", fetch_option(SIZES, @size, :size), fetch_option(ORIENTATIONS, @orientation, :orientation) ].compact.join(" "),
|
|
24
|
+
data: { state: fetch_option(STATES, @state, :state) }
|
|
25
25
|
}, @attrs), &)
|
|
26
26
|
end
|
|
27
27
|
end
|
|
@@ -11,7 +11,7 @@ module PhlexKit
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def view_template(&)
|
|
14
|
-
div(**mix({ class: [ "pk-attachment-media", VARIANTS
|
|
14
|
+
div(**mix({ class: [ "pk-attachment-media", fetch_option(VARIANTS, @variant, :variant) ].compact.join(" ") }, @attrs), &)
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
end
|
|
@@ -50,7 +50,7 @@ module PhlexKit
|
|
|
50
50
|
private
|
|
51
51
|
|
|
52
52
|
def classes
|
|
53
|
-
[ "pk-badge", VARIANTS
|
|
53
|
+
[ "pk-badge", fetch_option(VARIANTS, @variant, :variant), fetch_option(SIZES, @size, :size) ].compact.join(" ")
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
end
|
|
@@ -52,7 +52,7 @@ module PhlexKit
|
|
|
52
52
|
private
|
|
53
53
|
|
|
54
54
|
def classes
|
|
55
|
-
[ "pk-button", VARIANTS
|
|
55
|
+
[ "pk-button", fetch_option(VARIANTS, @variant, :variant), fetch_option(SIZES, @size, :size), ("icon" if @icon) ].compact.join(" ")
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
58
|
end
|
|
@@ -13,7 +13,7 @@ module PhlexKit
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def view_template(&)
|
|
16
|
-
classes = [ "pk-button-group", ORIENTATIONS
|
|
16
|
+
classes = [ "pk-button-group", fetch_option(ORIENTATIONS, @orientation, :orientation) ].compact.join(" ")
|
|
17
17
|
div(**mix({ class: classes, role: "group" }, @attrs), &)
|
|
18
18
|
end
|
|
19
19
|
end
|
|
@@ -34,10 +34,10 @@ export default class extends Controller {
|
|
|
34
34
|
maxDate: { type: String, default: null },
|
|
35
35
|
disabledDates: { type: Array, default: [] },
|
|
36
36
|
weekNumbers: { type: Boolean, default: false },
|
|
37
|
-
viewDate
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
},
|
|
37
|
+
// Resolved lazily by viewDate() — a `new Date()` default here would be
|
|
38
|
+
// frozen at module load (stale "today" in long-lived tabs) and
|
|
39
|
+
// toISOString() is UTC (wrong day for non-UTC users near midnight).
|
|
40
|
+
viewDate: { type: String, default: "" },
|
|
41
41
|
format: { type: String, default: "yyyy-MM-dd" },
|
|
42
42
|
};
|
|
43
43
|
static outlets = ["phlex-kit--calendar-input"];
|
|
@@ -61,14 +61,14 @@ export default class extends Controller {
|
|
|
61
61
|
const date = this.viewDate();
|
|
62
62
|
date.setDate(2);
|
|
63
63
|
date.setMonth(Number(e.target.value));
|
|
64
|
-
this.viewDateValue =
|
|
64
|
+
this.viewDateValue = this.isoDate(date);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
setYear(e) {
|
|
68
68
|
const date = this.viewDate();
|
|
69
69
|
date.setDate(2);
|
|
70
70
|
date.setFullYear(Number(e.target.value));
|
|
71
|
-
this.viewDateValue =
|
|
71
|
+
this.viewDateValue = this.isoDate(date);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
selectDay(e) {
|
|
@@ -132,9 +132,13 @@ export default class extends Controller {
|
|
|
132
132
|
// update the viewDateValue to the selected date's month (this re-renders)
|
|
133
133
|
const newViewDate = new Date(selectedDate);
|
|
134
134
|
newViewDate.setDate(2); // avoid month-length/timezone edges
|
|
135
|
-
this.viewDateValue =
|
|
135
|
+
this.viewDateValue = this.isoDate(newViewDate);
|
|
136
136
|
|
|
137
137
|
this.updateCalendar();
|
|
138
|
+
|
|
139
|
+
// Stimulus fires valueChanged during value initialization too — a
|
|
140
|
+
// server-rendered selected_date must not dispatch a change event.
|
|
141
|
+
if (prevValue === undefined) return;
|
|
138
142
|
this.pushToOutlets();
|
|
139
143
|
this.dispatchChange();
|
|
140
144
|
}
|
|
@@ -180,7 +184,7 @@ export default class extends Controller {
|
|
|
180
184
|
const date = this.viewDate();
|
|
181
185
|
date.setDate(2); // avoid month-length/timezone edges
|
|
182
186
|
date.setMonth(date.getMonth() + adjustment);
|
|
183
|
-
return
|
|
187
|
+
return this.isoDate(date);
|
|
184
188
|
}
|
|
185
189
|
|
|
186
190
|
updateCalendar() {
|
|
@@ -27,7 +27,7 @@ module PhlexKit
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def view_template(&block)
|
|
30
|
-
div(**mix({ class: [ "pk-card", SIZES
|
|
30
|
+
div(**mix({ class: [ "pk-card", fetch_option(SIZES, @size, :size) ].compact.join(" ") }, @attrs), &block)
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
end
|
|
@@ -17,7 +17,7 @@ module PhlexKit
|
|
|
17
17
|
|
|
18
18
|
def view_template(&)
|
|
19
19
|
div(**mix({
|
|
20
|
-
class: [ "pk-carousel", ORIENTATIONS
|
|
20
|
+
class: [ "pk-carousel", fetch_option(ORIENTATIONS, @orientation, :orientation) ].join(" "),
|
|
21
21
|
role: "region",
|
|
22
22
|
aria: { roledescription: "carousel" },
|
|
23
23
|
data: {
|
|
@@ -6,7 +6,7 @@ module PhlexKit
|
|
|
6
6
|
@attrs = attrs
|
|
7
7
|
end
|
|
8
8
|
def view_template(&block)
|
|
9
|
-
div(class: "pk-clipboard-popover pk-hidden", data: { phlex_kit__clipboard_target: TARGETS
|
|
9
|
+
div(class: "pk-clipboard-popover pk-hidden", data: { phlex_kit__clipboard_target: fetch_option(TARGETS, @type, :target) }) do
|
|
10
10
|
div(**mix({ class: "pk-clipboard-popover-inner" }, @attrs), &block)
|
|
11
11
|
end
|
|
12
12
|
end
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/* Co-located with combobox.rb — UI::Combobox. ruby_ui's structure + Stimulus
|
|
2
|
-
kept; the
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
kept; like upstream the panel is a native [popover] (manual), and CSS
|
|
3
|
+
anchor positioning replaces @floating-ui (Baseline 2026): the panel anchors
|
|
4
|
+
to the combobox wrapper (all three trigger layouts fill it), gets the
|
|
5
|
+
trigger width as a floor via anchor-size(), and flips above at the
|
|
6
|
+
viewport bottom via position-try-fallbacks. Tailwind → vanilla CSS on the
|
|
7
|
+
palette tokens. Geometry from ruby_ui (h-9 trigger, rounded-lg panel, p-1
|
|
8
|
+
list, px-2 py-1 label rows, group labels via ::before content(attr(label))).
|
|
6
9
|
Theme tokens come from the global stylesheet. */
|
|
7
|
-
.pk-combobox {
|
|
10
|
+
.pk-combobox { width: 100%; anchor-name: --pk-combobox; anchor-scope: --pk-combobox; }
|
|
8
11
|
|
|
9
12
|
/* Closed-state trigger (mirrors .pk-select-trigger geometry). */
|
|
10
13
|
.pk-combobox-trigger {
|
|
@@ -40,14 +43,17 @@
|
|
|
40
43
|
.pk-combobox-trigger-content { overflow: hidden; text-overflow: ellipsis; }
|
|
41
44
|
.pk-combobox-trigger-icon { flex: none; width: 1rem; height: 1rem; margin-left: .5rem; opacity: .5; }
|
|
42
45
|
|
|
43
|
-
/* Dropdown panel —
|
|
44
|
-
|
|
46
|
+
/* Dropdown panel — anchored below the combobox, flips above at the viewport
|
|
47
|
+
bottom. Width: trigger width with a 15rem floor (was JS
|
|
48
|
+
updatePopoverWidth, now pure CSS). */
|
|
45
49
|
.pk-combobox-popover {
|
|
46
|
-
position:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
margin
|
|
50
|
+
position: fixed;
|
|
51
|
+
position-anchor: --pk-combobox;
|
|
52
|
+
inset: auto;
|
|
53
|
+
position-area: block-end span-inline-end;
|
|
54
|
+
margin: .25rem 0 0;
|
|
55
|
+
position-try-fallbacks: flip-block;
|
|
56
|
+
width: max(anchor-size(width), 15rem);
|
|
51
57
|
border: 1px solid var(--pk-border);
|
|
52
58
|
border-radius: var(--pk-radius);
|
|
53
59
|
background: var(--pk-surface);
|
|
@@ -342,3 +348,6 @@
|
|
|
342
348
|
box-shadow: 0 0 0 3px color-mix(in oklab, var(--pk-red) 20%, transparent);
|
|
343
349
|
}
|
|
344
350
|
}
|
|
351
|
+
/* Hiding must survive author display values (a host class like
|
|
352
|
+
display:grid would beat the UA [popover] { display: none }). */
|
|
353
|
+
.pk-combobox-popover:not(:popover-open) { display: none; }
|
|
@@ -19,9 +19,10 @@ module PhlexKit
|
|
|
19
19
|
@attrs = attrs
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def view_template
|
|
22
|
+
def view_template
|
|
23
|
+
# role="combobox" lives on the interactive element (the trigger button /
|
|
24
|
+
# filter input), not this wrapper — see ComboboxTrigger and friends.
|
|
23
25
|
div(**mix({
|
|
24
|
-
role: "combobox",
|
|
25
26
|
class: "pk-combobox",
|
|
26
27
|
data: {
|
|
27
28
|
controller: "phlex-kit--combobox",
|
|
@@ -29,7 +30,22 @@ module PhlexKit
|
|
|
29
30
|
phlex_kit__combobox_auto_highlight_value: (@auto_highlight ? "true" : nil),
|
|
30
31
|
action: "turbo:morph@window->phlex-kit--combobox#updateTriggerContent click@window->phlex-kit--combobox#onClickOutside"
|
|
31
32
|
}
|
|
32
|
-
}, @attrs)
|
|
33
|
+
}, @attrs)) do
|
|
34
|
+
live_region
|
|
35
|
+
yield
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
# Screen-reader announcement of the filtered result count — the controller
|
|
42
|
+
# writes "N results" / "No results" into it from applyFilter.
|
|
43
|
+
def live_region
|
|
44
|
+
div(
|
|
45
|
+
class: "pk-sr-only",
|
|
46
|
+
aria: { live: "polite" },
|
|
47
|
+
data: { phlex_kit__combobox_target: "liveRegion" }
|
|
48
|
+
)
|
|
33
49
|
end
|
|
34
50
|
end
|
|
35
51
|
end
|
|
@@ -8,16 +8,19 @@ module PhlexKit
|
|
|
8
8
|
# PhlexKit's completion of it. Keyboard actions live here because focus stays
|
|
9
9
|
# in this field (see ComboboxInputTrigger). See combobox.rb.
|
|
10
10
|
class ComboboxBadgeTrigger < BaseComponent
|
|
11
|
-
def initialize(placeholder: "", clear_button: false, **attrs)
|
|
11
|
+
def initialize(placeholder: "", clear_button: false, list_id: nil, **attrs)
|
|
12
12
|
@placeholder = placeholder
|
|
13
13
|
@clear_button = clear_button
|
|
14
|
+
@list_id = list_id
|
|
14
15
|
@attrs = attrs
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
def view_template
|
|
19
|
+
# The ARIA combobox role sits on the field itself (the interactive
|
|
20
|
+
# element), not this wrapper. Pass `list_id:` matching the ComboboxList
|
|
21
|
+
# id to wire aria-controls statically — otherwise the controller wires it.
|
|
18
22
|
div(**mix({
|
|
19
23
|
class: "pk-combobox-badge-trigger",
|
|
20
|
-
aria: { haspopup: "listbox", expanded: "false" },
|
|
21
24
|
data: {
|
|
22
25
|
placeholder: @placeholder,
|
|
23
26
|
phlex_kit__combobox_target: "trigger",
|
|
@@ -34,6 +37,8 @@ module PhlexKit
|
|
|
34
37
|
div(class: "pk-combobox-badge-container pk-hidden", data: { phlex_kit__combobox_target: "badgeContainer" })
|
|
35
38
|
input(
|
|
36
39
|
type: :text,
|
|
40
|
+
role: "combobox",
|
|
41
|
+
aria: { haspopup: "listbox", expanded: "false", autocomplete: "list", controls: @list_id },
|
|
37
42
|
placeholder: @placeholder,
|
|
38
43
|
autocomplete: "off",
|
|
39
44
|
autocorrect: "off",
|
|
@@ -10,6 +10,9 @@ module PhlexKit
|
|
|
10
10
|
def view_template
|
|
11
11
|
input(**mix({
|
|
12
12
|
type: :checkbox,
|
|
13
|
+
# The option label is the interactive surface (ARIA option); the real
|
|
14
|
+
# input must not be a second tab stop inside it.
|
|
15
|
+
tabindex: "-1",
|
|
13
16
|
class: "pk-combobox-checkbox",
|
|
14
17
|
data: {
|
|
15
18
|
phlex_kit__combobox_target: "input",
|