phlex_kit 0.8.0 → 0.8.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/app/components/phlex_kit/accordion/accordion_content.rb +11 -2
- data/app/components/phlex_kit/accordion/accordion_controller.js +15 -2
- data/app/components/phlex_kit/alert_dialog/alert_dialog_content.rb +5 -2
- data/app/components/phlex_kit/alert_dialog/alert_dialog_controller.js +80 -6
- data/app/components/phlex_kit/aspect_ratio/aspect_ratio.rb +8 -4
- data/app/components/phlex_kit/calendar/calendar_body.rb +9 -1
- data/app/components/phlex_kit/calendar/calendar_controller.js +63 -1
- data/app/components/phlex_kit/chart/chart_controller.js +5 -1
- data/app/components/phlex_kit/clipboard/clipboard_popover.rb +3 -1
- data/app/components/phlex_kit/combobox/combobox_controller.js +15 -2
- data/app/components/phlex_kit/context_menu/context_menu_checkbox_item.rb +6 -2
- data/app/components/phlex_kit/context_menu/context_menu_controller.js +51 -1
- data/app/components/phlex_kit/context_menu/context_menu_radio_item.rb +6 -2
- data/app/components/phlex_kit/context_menu/context_menu_sub_trigger.rb +5 -2
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu.css +3 -2
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_checkbox_item.rb +6 -2
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_content.rb +3 -1
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_controller.js +68 -47
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_radio_item.rb +6 -2
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_sub_trigger.rb +5 -2
- data/app/components/phlex_kit/form_field/form_field_controller.js +14 -1
- data/app/components/phlex_kit/form_field/form_field_error.rb +3 -0
- data/app/components/phlex_kit/input_otp/input_otp_controller.js +30 -4
- data/app/components/phlex_kit/masked_input/masked_input.rb +1 -1
- data/app/components/phlex_kit/masked_input/masked_input_controller.js +24 -5
- data/app/components/phlex_kit/menubar/menubar_controller.js +51 -7
- data/app/components/phlex_kit/menubar/menubar_sub_trigger.rb +3 -1
- data/app/components/phlex_kit/navigation_menu/navigation_menu.css +4 -1
- data/app/components/phlex_kit/navigation_menu/navigation_menu_trigger.rb +1 -1
- data/app/components/phlex_kit/resizable/resizable_controller.js +65 -3
- data/app/components/phlex_kit/resizable/resizable_handle.rb +3 -1
- data/app/components/phlex_kit/select/select.rb +8 -7
- data/app/components/phlex_kit/select/select_controller.js +10 -4
- data/app/components/phlex_kit/select/select_item.rb +0 -1
- data/app/components/phlex_kit/sidebar/sidebar.css +19 -7
- data/app/components/phlex_kit/slider/slider.css +2 -2
- data/app/components/phlex_kit/theme_toggle/theme_toggle.rb +11 -0
- data/app/components/phlex_kit/theme_toggle/theme_toggle_controller.js +6 -0
- data/app/components/phlex_kit/toast/toast_region.rb +6 -2
- data/app/components/phlex_kit/toast/toaster_controller.js +7 -3
- data/app/components/phlex_kit/toggle/toggle.rb +3 -1
- data/app/javascript/phlex_kit/controllers/index.js +0 -2
- data/lib/phlex_kit/version.rb +1 -1
- metadata +1 -2
- data/app/components/phlex_kit/select/select_item_controller.js +0 -14
|
@@ -169,14 +169,23 @@
|
|
|
169
169
|
.pk-sidebar-wrapper.collapsible-offcanvas .pk-sidebar,
|
|
170
170
|
.pk-sidebar-wrapper.collapsible-icon .pk-sidebar {
|
|
171
171
|
position: relative;
|
|
172
|
-
transition:
|
|
172
|
+
/* visibility rides the transition: it stays visible while sliding out and
|
|
173
|
+
flips hidden at the end — hidden also drops the off-screen links from
|
|
174
|
+
the tab order (they stayed focusable while invisible before). */
|
|
175
|
+
transition: margin-left .2s ease, transform .2s ease, width .2s ease, visibility .2s;
|
|
173
176
|
}
|
|
174
|
-
.pk-sidebar-wrapper.collapsible-offcanvas[data-collapsed] .pk-sidebar { margin-left: -16rem; }
|
|
177
|
+
.pk-sidebar-wrapper.collapsible-offcanvas[data-collapsed] .pk-sidebar { margin-left: -16rem; visibility: hidden; }
|
|
175
178
|
|
|
176
179
|
/* Icon mode: the rail shrinks to a 3rem strip — labels and text hide, menu
|
|
177
180
|
buttons become centered icon squares, hover shows a data-tooltip bubble. */
|
|
178
181
|
.pk-sidebar-wrapper.collapsible-icon[data-collapsed] .pk-sidebar { width: 3rem; }
|
|
179
|
-
|
|
182
|
+
/* Desktop-only: at mobile widths icon mode becomes the full drawer, and the
|
|
183
|
+
labels/subs must keep their AUTHOR display values (flex). The old approach
|
|
184
|
+
un-hid them with `display: revert`, but revert rolls back to the USER-AGENT
|
|
185
|
+
origin — group labels lost their flex centering, sub lists their gap. */
|
|
186
|
+
@media (min-width: 768px) {
|
|
187
|
+
.pk-sidebar-wrapper.collapsible-icon[data-collapsed] :is(.pk-sidebar-group-label, .pk-sidebar-group-action, .pk-sidebar-menu-badge, .pk-sidebar-menu-action, .pk-sidebar-menu-sub, .pk-sidebar-input, .pk-sidebar-separator) { display: none; }
|
|
188
|
+
}
|
|
180
189
|
.pk-sidebar-wrapper.collapsible-icon[data-collapsed] :is(.pk-sidebar-header, .pk-sidebar-content, .pk-sidebar-footer) {
|
|
181
190
|
padding-inline: .5rem;
|
|
182
191
|
align-items: center;
|
|
@@ -187,7 +196,9 @@
|
|
|
187
196
|
padding: .5rem;
|
|
188
197
|
justify-content: center;
|
|
189
198
|
}
|
|
190
|
-
|
|
199
|
+
@media (min-width: 768px) {
|
|
200
|
+
.pk-sidebar-wrapper.collapsible-icon[data-collapsed] .pk-sidebar-menu-button > :not(svg) { display: none; }
|
|
201
|
+
}
|
|
191
202
|
.pk-sidebar-wrapper.collapsible-icon[data-collapsed] .pk-sidebar-menu-button[data-tooltip] { position: relative; }
|
|
192
203
|
.pk-sidebar-wrapper.collapsible-icon[data-collapsed] .pk-sidebar-menu-button[data-tooltip]:hover::after {
|
|
193
204
|
content: attr(data-tooltip);
|
|
@@ -252,9 +263,8 @@
|
|
|
252
263
|
@media (max-width: 767px) {
|
|
253
264
|
/* Below the cutoff both collapsible modes become the overlay drawer. */
|
|
254
265
|
.pk-sidebar-wrapper.collapsible-icon[data-collapsed] .pk-sidebar { width: 16rem; }
|
|
255
|
-
|
|
266
|
+
/* No un-hiding needed: the icon-mode hide rules are scoped to >=768px. */
|
|
256
267
|
.pk-sidebar-wrapper.collapsible-icon[data-collapsed] .pk-sidebar-menu-button { width: 100%; height: auto; padding: .5rem; justify-content: flex-start; }
|
|
257
|
-
.pk-sidebar-wrapper.collapsible-icon[data-collapsed] .pk-sidebar-menu-button > :not(svg) { display: revert; }
|
|
258
268
|
.pk-sidebar-wrapper.collapsible-icon .pk-sidebar-rail { display: none; }
|
|
259
269
|
.pk-sidebar-wrapper:is(.collapsible-offcanvas, .collapsible-icon) .pk-sidebar {
|
|
260
270
|
position: fixed;
|
|
@@ -264,9 +274,11 @@
|
|
|
264
274
|
height: 100dvh;
|
|
265
275
|
margin-left: 0;
|
|
266
276
|
transform: translateX(-100%);
|
|
277
|
+
/* closed drawer: invisible AND out of the tab order */
|
|
278
|
+
visibility: hidden;
|
|
267
279
|
box-shadow: 0 8px 30px rgb(0 0 0 / .35);
|
|
268
280
|
}
|
|
269
|
-
.pk-sidebar-wrapper:is(.collapsible-offcanvas, .collapsible-icon)[data-open] .pk-sidebar { transform: none; }
|
|
281
|
+
.pk-sidebar-wrapper:is(.collapsible-offcanvas, .collapsible-icon)[data-open] .pk-sidebar { transform: none; visibility: visible; }
|
|
270
282
|
.pk-sidebar-wrapper:is(.collapsible-offcanvas, .collapsible-icon)[data-open] .pk-sidebar-scrim {
|
|
271
283
|
display: block;
|
|
272
284
|
position: fixed;
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
margin-top: -0.25rem;
|
|
26
26
|
border-radius: 9999px;
|
|
27
27
|
border: 1px solid var(--pk-ring);
|
|
28
|
-
background:
|
|
28
|
+
background: var(--pk-bg);
|
|
29
29
|
box-shadow: 0 1px 2px rgb(0 0 0 / .25);
|
|
30
30
|
transition: box-shadow .15s ease;
|
|
31
31
|
}
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
height: .75rem;
|
|
52
52
|
border-radius: 9999px;
|
|
53
53
|
border: 1px solid var(--pk-ring);
|
|
54
|
-
background:
|
|
54
|
+
background: var(--pk-bg);
|
|
55
55
|
}
|
|
56
56
|
.pk-slider:hover::-moz-range-thumb,
|
|
57
57
|
.pk-slider:active::-moz-range-thumb {
|
|
@@ -2,6 +2,17 @@ module PhlexKit
|
|
|
2
2
|
# Light/dark theme switch built on Toggle. Ported from ruby_ui's RubyUI::
|
|
3
3
|
# ThemeToggle. The sibling phlex-kit--theme-toggle controller listens for the
|
|
4
4
|
# toggle's change event and flips :root[data-theme] (matching the token system).
|
|
5
|
+
# Only an actual user toggle is persisted to localStorage — with no stored
|
|
6
|
+
# choice the OS preference is followed live on every load.
|
|
7
|
+
#
|
|
8
|
+
# The controller applies the stored theme at Stimulus connect, which is late
|
|
9
|
+
# enough to flash light-mode for returning dark-theme users. To avoid the
|
|
10
|
+
# FOUC, inline this in <head> before any stylesheets:
|
|
11
|
+
#
|
|
12
|
+
# <script>
|
|
13
|
+
# const t = localStorage.theme;
|
|
14
|
+
# if (t === "dark" || t === "light") document.documentElement.dataset.theme = t;
|
|
15
|
+
# </script>
|
|
5
16
|
class ThemeToggle < BaseComponent
|
|
6
17
|
def initialize(wrapper: {}, aria: {}, **attrs)
|
|
7
18
|
@wrapper = wrapper
|
|
@@ -7,6 +7,12 @@ export default class extends Controller {
|
|
|
7
7
|
connect() { this.applyTheme(this.currentTheme()) }
|
|
8
8
|
apply(e) {
|
|
9
9
|
const theme = e.detail?.pressed ? "dark" : "light"
|
|
10
|
+
// connect()'s own pressed-state sync re-enters here via the toggle's
|
|
11
|
+
// change event (Stimulus value observation is async, so a flag can't
|
|
12
|
+
// gate it). If the incoming theme already matches the applied one this
|
|
13
|
+
// is that echo, not a user toggle — persisting it would silently pin
|
|
14
|
+
// the visitor's OS preference in localStorage on first visit.
|
|
15
|
+
if (theme === document.documentElement.getAttribute("data-theme")) return
|
|
10
16
|
localStorage.theme = theme
|
|
11
17
|
this.applyTheme(theme)
|
|
12
18
|
}
|
|
@@ -68,7 +68,9 @@ module PhlexKit
|
|
|
68
68
|
@flash.each do |key, message|
|
|
69
69
|
next if message.nil? || message.to_s.empty?
|
|
70
70
|
variant = Toast.flash_variant(key)
|
|
71
|
-
|
|
71
|
+
# Stamp the region's duration — without it the toast controller's
|
|
72
|
+
# 4000ms default wins and ToastRegion(duration:) is dead config.
|
|
73
|
+
render ToastItem.new(variant: variant, id: "flash-#{key}", duration: @duration) do
|
|
72
74
|
render ToastIcon.new(variant: variant)
|
|
73
75
|
render ToastTitle.new { message.to_s }
|
|
74
76
|
end
|
|
@@ -77,7 +79,9 @@ module PhlexKit
|
|
|
77
79
|
|
|
78
80
|
def skeleton(variant)
|
|
79
81
|
template(data: { phlex_kit__toaster_target: "skeleton", variant: variant.to_s }) do
|
|
80
|
-
|
|
82
|
+
# Region duration baked into the skeleton; _spawn overrides it only
|
|
83
|
+
# when the caller passes a per-toast duration.
|
|
84
|
+
render ToastItem.new(variant: variant, duration: @duration) do
|
|
81
85
|
render ToastIcon.new(variant: variant)
|
|
82
86
|
div(class: "pk-toast-body") do
|
|
83
87
|
render ToastTitle.new
|
|
@@ -189,7 +189,10 @@ export default class extends Controller {
|
|
|
189
189
|
if (!this._listEl) return
|
|
190
190
|
const isBottom = this.positionValue.startsWith("bottom")
|
|
191
191
|
const items = this.toastTargets
|
|
192
|
-
|
|
192
|
+
// Newest toast fronts the stack at BOTH edges (index 0 = front); only
|
|
193
|
+
// the offset direction flips with the position. Using DOM order at
|
|
194
|
+
// top-* put the oldest toast in front and stacked new ones behind it.
|
|
195
|
+
const order = items.slice().reverse()
|
|
193
196
|
const heights = order.map(el => this._heights.get(el) || el.offsetHeight || 64)
|
|
194
197
|
const gap = this.gapValue
|
|
195
198
|
const peekOffset = 16
|
|
@@ -237,9 +240,10 @@ export default class extends Controller {
|
|
|
237
240
|
|
|
238
241
|
_enforceMax(items) {
|
|
239
242
|
if (items.length <= this.maxValue) return
|
|
240
|
-
const isBottom = this.positionValue.startsWith("bottom")
|
|
241
243
|
const dropping = items.length - this.maxValue
|
|
242
|
-
|
|
244
|
+
// Evict the OLDEST toasts (DOM order = append order) regardless of edge;
|
|
245
|
+
// slicing from the tail at top-* dropped the newest ones instead.
|
|
246
|
+
const candidates = items.slice(0, dropping)
|
|
243
247
|
candidates.forEach(el => {
|
|
244
248
|
if (el.dataset.state !== "closing") {
|
|
245
249
|
el.dispatchEvent(new CustomEvent("phlex-kit:toast:force-dismiss", { bubbles: true }))
|
|
@@ -7,7 +7,9 @@ module PhlexKit
|
|
|
7
7
|
SIZES = { sm: "sm", default: nil, lg: "lg" }.freeze
|
|
8
8
|
|
|
9
9
|
def self.modifier_classes(variant:, size:)
|
|
10
|
-
|
|
10
|
+
# Plain fetch — an unknown variant/size raises KeyError (kit-wide
|
|
11
|
+
# fail-loud rule); the nil-default form silently rendered unstyled.
|
|
12
|
+
[ VARIANTS.fetch(variant), SIZES.fetch(size) ].compact
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
def initialize(pressed: false, name: nil, value: "1", unpressed_value: nil,
|
|
@@ -36,7 +36,6 @@ import PopoverController from "phlex_kit/controllers/popover_controller"
|
|
|
36
36
|
import ResizableController from "phlex_kit/controllers/resizable_controller"
|
|
37
37
|
import ScrollFadeController from "phlex_kit/controllers/scroll_fade_controller"
|
|
38
38
|
import SelectController from "phlex_kit/controllers/select_controller"
|
|
39
|
-
import SelectItemController from "phlex_kit/controllers/select_item_controller"
|
|
40
39
|
import SheetController from "phlex_kit/controllers/sheet_controller"
|
|
41
40
|
import SheetContentController from "phlex_kit/controllers/sheet_content_controller"
|
|
42
41
|
import SidebarController from "phlex_kit/controllers/sidebar_controller"
|
|
@@ -78,7 +77,6 @@ export function registerPhlexKitControllers(application) {
|
|
|
78
77
|
application.register("phlex-kit--resizable", ResizableController)
|
|
79
78
|
application.register("phlex-kit--scroll-fade", ScrollFadeController)
|
|
80
79
|
application.register("phlex-kit--select", SelectController)
|
|
81
|
-
application.register("phlex-kit--select-item", SelectItemController)
|
|
82
80
|
application.register("phlex-kit--sheet", SheetController)
|
|
83
81
|
application.register("phlex-kit--sheet-content", SheetContentController)
|
|
84
82
|
application.register("phlex-kit--sidebar", SidebarController)
|
data/lib/phlex_kit/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: phlex_kit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.
|
|
4
|
+
version: 0.8.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matt Kennedy
|
|
@@ -486,7 +486,6 @@ files:
|
|
|
486
486
|
- app/components/phlex_kit/select/select_group.rb
|
|
487
487
|
- app/components/phlex_kit/select/select_input.rb
|
|
488
488
|
- app/components/phlex_kit/select/select_item.rb
|
|
489
|
-
- app/components/phlex_kit/select/select_item_controller.js
|
|
490
489
|
- app/components/phlex_kit/select/select_label.rb
|
|
491
490
|
- app/components/phlex_kit/select/select_separator.rb
|
|
492
491
|
- app/components/phlex_kit/select/select_trigger.rb
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { Controller } from "@hotwired/stimulus";
|
|
2
|
-
|
|
3
|
-
// Ported verbatim from ruby_ui's phlex-kit--select-item controller. Flips its own
|
|
4
|
-
// element's aria-selected when an item is chosen (the select controller invokes
|
|
5
|
-
// this on every item via the outlet), which drives the checkmark in select.css.
|
|
6
|
-
export default class extends Controller {
|
|
7
|
-
handleSelectItem({ target }) {
|
|
8
|
-
if (this.element.dataset.value == target.dataset.value) {
|
|
9
|
-
this.element.setAttribute("aria-selected", true);
|
|
10
|
-
} else {
|
|
11
|
-
this.element.removeAttribute("aria-selected");
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
}
|