phlex_kit 0.4.0 → 0.5.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/app/assets/stylesheets/phlex_kit/_tokens.css +16 -0
- data/app/assets/stylesheets/phlex_kit/phlex_kit.css +1 -0
- data/app/components/phlex_kit/alert/alert.css +60 -22
- data/app/components/phlex_kit/alert/alert.rb +11 -7
- data/app/components/phlex_kit/alert/alert_action.rb +14 -0
- data/app/components/phlex_kit/alert/alert_title.rb +3 -2
- data/app/components/phlex_kit/alert_dialog/alert_dialog.css +5 -2
- data/app/components/phlex_kit/avatar/avatar.css +80 -9
- data/app/components/phlex_kit/avatar/avatar.rb +6 -4
- data/app/components/phlex_kit/avatar/avatar_badge.rb +15 -0
- data/app/components/phlex_kit/avatar/avatar_group_count.rb +14 -0
- data/app/components/phlex_kit/badge/badge.css +61 -39
- data/app/components/phlex_kit/badge/badge.rb +24 -7
- data/app/components/phlex_kit/breadcrumb/breadcrumb.css +2 -2
- data/app/components/phlex_kit/bubble/bubble.css +49 -8
- data/app/components/phlex_kit/button/button.css +71 -26
- data/app/components/phlex_kit/button/button.rb +20 -13
- data/app/components/phlex_kit/button_group/button_group.css +59 -8
- data/app/components/phlex_kit/button_group/button_group.rb +11 -5
- data/app/components/phlex_kit/button_group/button_group_separator.rb +14 -0
- data/app/components/phlex_kit/button_group/button_group_text.rb +14 -0
- data/app/components/phlex_kit/calendar/calendar.css +66 -15
- data/app/components/phlex_kit/calendar/calendar.rb +87 -19
- data/app/components/phlex_kit/calendar/calendar_controller.js +226 -75
- data/app/components/phlex_kit/calendar/calendar_days.rb +7 -6
- data/app/components/phlex_kit/calendar/calendar_weekdays.rb +3 -1
- data/app/components/phlex_kit/checkbox/checkbox.css +52 -7
- data/app/components/phlex_kit/collapsible/collapsible.css +10 -1
- data/app/components/phlex_kit/collapsible/collapsible_controller.js +9 -2
- data/app/components/phlex_kit/combobox/combobox.css +37 -3
- data/app/components/phlex_kit/combobox/combobox.rb +3 -1
- data/app/components/phlex_kit/combobox/combobox_controller.js +9 -1
- data/app/components/phlex_kit/combobox/combobox_input_trigger.rb +7 -2
- data/app/components/phlex_kit/command/command.css +58 -22
- data/app/components/phlex_kit/command/command_controller.js +3 -1
- data/app/components/phlex_kit/command/command_input.rb +29 -25
- data/app/components/phlex_kit/command/command_separator.rb +18 -0
- data/app/components/phlex_kit/command/command_shortcut.rb +13 -0
- data/app/components/phlex_kit/context_menu/context_menu.css +78 -6
- data/app/components/phlex_kit/context_menu/context_menu_checkbox_item.rb +27 -0
- data/app/components/phlex_kit/context_menu/context_menu_group.rb +10 -0
- data/app/components/phlex_kit/context_menu/context_menu_item.rb +13 -3
- data/app/components/phlex_kit/context_menu/context_menu_radio_group.rb +10 -0
- data/app/components/phlex_kit/context_menu/context_menu_radio_item.rb +30 -0
- data/app/components/phlex_kit/context_menu/context_menu_shortcut.rb +11 -0
- data/app/components/phlex_kit/context_menu/context_menu_sub.rb +11 -0
- data/app/components/phlex_kit/context_menu/context_menu_sub_content.rb +12 -0
- data/app/components/phlex_kit/context_menu/context_menu_sub_trigger.rb +17 -0
- data/app/components/phlex_kit/dialog/dialog.css +34 -16
- data/app/components/phlex_kit/dialog/dialog_close.rb +13 -0
- data/app/components/phlex_kit/dialog/dialog_content.rb +13 -5
- data/app/components/phlex_kit/drawer/drawer.css +56 -17
- data/app/components/phlex_kit/drawer/drawer_content.rb +8 -4
- data/app/components/phlex_kit/empty/empty.css +13 -8
- data/app/components/phlex_kit/field/field.css +152 -0
- data/app/components/phlex_kit/field/field.rb +35 -0
- data/app/components/phlex_kit/field/field_content.rb +11 -0
- data/app/components/phlex_kit/field/field_description.rb +10 -0
- data/app/components/phlex_kit/field/field_error.rb +29 -0
- data/app/components/phlex_kit/field/field_group.rb +12 -0
- data/app/components/phlex_kit/field/field_label.rb +12 -0
- data/app/components/phlex_kit/field/field_legend.rb +17 -0
- data/app/components/phlex_kit/field/field_separator.rb +15 -0
- data/app/components/phlex_kit/field/field_set.rb +10 -0
- data/app/components/phlex_kit/field/field_title.rb +11 -0
- data/app/components/phlex_kit/hover_card/hover_card.css +12 -4
- data/app/components/phlex_kit/hover_card/hover_card_content.rb +11 -2
- data/app/components/phlex_kit/input/input.css +45 -11
- data/app/components/phlex_kit/input_group/input_group.css +88 -12
- data/app/components/phlex_kit/input_group/input_group_addon.rb +11 -4
- data/app/components/phlex_kit/input_group/input_group_button.rb +18 -0
- data/app/components/phlex_kit/input_otp/input_otp.css +29 -5
- data/app/components/phlex_kit/item/item.css +56 -8
- data/app/components/phlex_kit/item/item.rb +13 -3
- data/app/components/phlex_kit/item/item_footer.rb +8 -0
- data/app/components/phlex_kit/item/item_header.rb +8 -0
- data/app/components/phlex_kit/item/item_media.rb +13 -3
- data/app/components/phlex_kit/item/item_separator.rb +10 -0
- data/app/components/phlex_kit/kbd/kbd.css +10 -0
- data/app/components/phlex_kit/marker/marker.css +29 -9
- data/app/components/phlex_kit/marker/marker.rb +13 -3
- data/app/components/phlex_kit/menubar/menubar.css +88 -17
- data/app/components/phlex_kit/menubar/menubar_checkbox_item.rb +26 -0
- data/app/components/phlex_kit/menubar/menubar_group.rb +10 -0
- data/app/components/phlex_kit/menubar/menubar_item.rb +9 -4
- data/app/components/phlex_kit/menubar/menubar_label.rb +10 -0
- data/app/components/phlex_kit/menubar/menubar_radio_group.rb +10 -0
- data/app/components/phlex_kit/menubar/menubar_radio_item.rb +29 -0
- data/app/components/phlex_kit/menubar/menubar_shortcut.rb +10 -0
- data/app/components/phlex_kit/menubar/menubar_sub.rb +10 -0
- data/app/components/phlex_kit/menubar/menubar_sub_content.rb +11 -0
- data/app/components/phlex_kit/menubar/menubar_sub_trigger.rb +17 -0
- data/app/components/phlex_kit/message/message.css +8 -1
- data/app/components/phlex_kit/message_scroller/message_scroller.css +18 -0
- data/app/components/phlex_kit/native_select/native_select.css +25 -5
- data/app/components/phlex_kit/navigation_menu/navigation_menu.css +9 -6
- data/app/components/phlex_kit/pagination/pagination.css +10 -3
- data/app/components/phlex_kit/pagination/pagination_link.rb +21 -0
- data/app/components/phlex_kit/pagination/pagination_next.rb +20 -0
- data/app/components/phlex_kit/pagination/pagination_previous.rb +21 -0
- data/app/components/phlex_kit/popover/popover.css +15 -4
- data/app/components/phlex_kit/popover/popover_content.rb +11 -2
- data/app/components/phlex_kit/popover/popover_description.rb +10 -0
- data/app/components/phlex_kit/popover/popover_header.rb +10 -0
- data/app/components/phlex_kit/popover/popover_title.rb +10 -0
- data/app/components/phlex_kit/progress/progress.css +5 -5
- data/app/components/phlex_kit/radio_button/radio_button.css +46 -2
- data/app/components/phlex_kit/radio_group/radio_group.css +2 -2
- data/app/components/phlex_kit/resizable/resizable.css +17 -0
- data/app/components/phlex_kit/resizable/resizable_handle.rb +7 -3
- data/app/components/phlex_kit/select/select.css +46 -10
- data/app/components/phlex_kit/select/select_separator.rb +10 -0
- data/app/components/phlex_kit/select/select_trigger.rb +5 -2
- data/app/components/phlex_kit/sheet/sheet.css +18 -9
- data/app/components/phlex_kit/sheet/sheet_close.rb +10 -0
- data/app/components/phlex_kit/sheet/sheet_content.rb +8 -4
- data/app/components/phlex_kit/sidebar/sidebar.css +76 -9
- data/app/components/phlex_kit/sidebar/sidebar_controller.js +22 -7
- data/app/components/phlex_kit/sidebar/sidebar_menu_button.rb +8 -3
- data/app/components/phlex_kit/sidebar/sidebar_rail.rb +21 -0
- data/app/components/phlex_kit/sidebar/sidebar_wrapper.rb +25 -8
- data/app/components/phlex_kit/skeleton/skeleton.css +1 -1
- data/app/components/phlex_kit/slider/slider.css +33 -12
- data/app/components/phlex_kit/switch/switch.css +50 -13
- data/app/components/phlex_kit/switch/switch.rb +5 -2
- data/app/components/phlex_kit/table/table.css +8 -4
- data/app/components/phlex_kit/tabs/tabs.css +72 -8
- data/app/components/phlex_kit/tabs/tabs.rb +7 -2
- data/app/components/phlex_kit/tabs/tabs_list.rb +11 -2
- data/app/components/phlex_kit/textarea/textarea.css +33 -13
- data/app/components/phlex_kit/toast/toast.css +1 -1
- data/app/components/phlex_kit/toggle/toggle.css +25 -10
- data/app/components/phlex_kit/toggle_group/toggle_group.css +7 -6
- data/app/components/phlex_kit/tooltip/tooltip.css +37 -10
- data/app/components/phlex_kit/tooltip/tooltip_content.rb +9 -4
- data/lib/phlex_kit/version.rb +1 -1
- metadata +50 -1
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
module PhlexKit
|
|
2
|
+
# A menu row in a PhlexKit::ContextMenuContent. Matches the dropdown-menu
|
|
3
|
+
# grammar: gap-based layout (no forced inset), `variant: :destructive`,
|
|
4
|
+
# `checked:` renders a leading ✓, `shortcut:` a trailing kbd hint (or
|
|
5
|
+
# compose ContextMenuShortcut yourself). Closes the menu on click.
|
|
6
|
+
# See context_menu.rb.
|
|
2
7
|
class ContextMenuItem < BaseComponent
|
|
3
|
-
|
|
8
|
+
VARIANTS = { default: nil, destructive: "destructive" }.freeze
|
|
9
|
+
|
|
10
|
+
def initialize(href: "#", checked: false, shortcut: nil, disabled: false, variant: :default, **attrs)
|
|
4
11
|
@href = href
|
|
5
12
|
@checked = checked
|
|
6
13
|
@shortcut = shortcut
|
|
7
14
|
@disabled = disabled
|
|
15
|
+
@variant = variant.to_sym
|
|
8
16
|
@attrs = attrs
|
|
9
17
|
end
|
|
18
|
+
|
|
10
19
|
def view_template(&block)
|
|
11
|
-
|
|
20
|
+
classes = [ "pk-context-menu-item", VARIANTS.fetch(@variant) ].compact.join(" ")
|
|
21
|
+
a(**mix({ href: @href, role: "menuitem", tabindex: "-1", class: classes,
|
|
12
22
|
data: { action: "click->phlex-kit--context-menu#close", phlex_kit__context_menu_target: "menuItem", disabled: @disabled } }, @attrs)) do
|
|
13
23
|
if @checked
|
|
14
24
|
span(class: "pk-context-menu-check") do
|
|
15
|
-
render Icon.new(:check, size:
|
|
25
|
+
render Icon.new(:check, size: nil)
|
|
16
26
|
end
|
|
17
27
|
end
|
|
18
28
|
yield
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Exclusive-choice group of ContextMenuRadioItems (share the same `name:`).
|
|
3
|
+
# Mirrors shadcn/ui's ContextMenuRadioGroup. See context_menu.rb.
|
|
4
|
+
class ContextMenuRadioGroup < BaseComponent
|
|
5
|
+
def initialize(**attrs) = (@attrs = attrs)
|
|
6
|
+
def view_template(&)
|
|
7
|
+
div(**mix({ class: "pk-context-menu-group", role: "radiogroup" }, @attrs), &)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# One choice in a ContextMenuRadioGroup — a <label> around a real (hidden)
|
|
3
|
+
# radio; the ● shows via CSS and picking doesn't close the menu (matching
|
|
4
|
+
# Radix). See context_menu.rb.
|
|
5
|
+
class ContextMenuRadioItem < BaseComponent
|
|
6
|
+
def initialize(name:, value:, checked: false, **attrs)
|
|
7
|
+
@name = name
|
|
8
|
+
@value = value
|
|
9
|
+
@checked = checked
|
|
10
|
+
@attrs = attrs
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def view_template(&block)
|
|
14
|
+
label(**mix({
|
|
15
|
+
class: "pk-context-menu-item pk-context-menu-radio-item",
|
|
16
|
+
role: "menuitemradio"
|
|
17
|
+
}, @attrs)) do
|
|
18
|
+
input(type: :radio, class: "pk-context-menu-item-input", name: @name, value: @value, checked: @checked)
|
|
19
|
+
span(class: "pk-context-menu-item-indicator", aria: { hidden: "true" }) do
|
|
20
|
+
# A filled selection dot is geometry, not icon-library vocabulary —
|
|
21
|
+
# identical across icon_library settings (matches the dropdown).
|
|
22
|
+
svg(xmlns: "http://www.w3.org/2000/svg", viewbox: "0 0 24 24", fill: "currentColor") do |s|
|
|
23
|
+
s.circle(cx: "12", cy: "12", r: "5")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
yield if block
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Right-aligned keyboard hint on a ContextMenuItem, mirroring shadcn/ui's
|
|
3
|
+
# ContextMenuShortcut (the item's `shortcut:` param renders the same span).
|
|
4
|
+
# See context_menu.rb.
|
|
5
|
+
class ContextMenuShortcut < BaseComponent
|
|
6
|
+
def initialize(**attrs) = (@attrs = attrs)
|
|
7
|
+
def view_template(&block)
|
|
8
|
+
span(**mix({ class: "pk-context-menu-shortcut" }, @attrs), &block)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Nested submenu wrapper: SubTrigger row + SubContent panel, revealed on
|
|
3
|
+
# hover/focus with pure CSS (no extra controller). Mirrors shadcn/ui's
|
|
4
|
+
# ContextMenuSub. See context_menu.rb.
|
|
5
|
+
class ContextMenuSub < BaseComponent
|
|
6
|
+
def initialize(**attrs) = (@attrs = attrs)
|
|
7
|
+
def view_template(&)
|
|
8
|
+
div(**mix({ class: "pk-context-menu-sub" }, @attrs), &)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# The nested panel of a ContextMenuSub — opens beside the trigger.
|
|
3
|
+
# See context_menu.rb.
|
|
4
|
+
class ContextMenuSubContent < BaseComponent
|
|
5
|
+
def initialize(**attrs) = (@attrs = attrs)
|
|
6
|
+
def view_template(&)
|
|
7
|
+
div(**mix({ class: "pk-context-menu-sub-content", role: "menu" }, @attrs)) do
|
|
8
|
+
div(class: "pk-context-menu-sub-viewport", &)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# The row that opens a ContextMenuSub (trailing ▸ chevron). See context_menu.rb.
|
|
3
|
+
class ContextMenuSubTrigger < BaseComponent
|
|
4
|
+
def initialize(**attrs) = (@attrs = attrs)
|
|
5
|
+
def view_template(&block)
|
|
6
|
+
div(**mix({
|
|
7
|
+
class: "pk-context-menu-item pk-context-menu-sub-trigger",
|
|
8
|
+
role: "menuitem",
|
|
9
|
+
tabindex: "0",
|
|
10
|
+
aria: { haspopup: "menu" }
|
|
11
|
+
}, @attrs)) do
|
|
12
|
+
block&.call
|
|
13
|
+
render Icon.new(:chevron_right, size: nil, class: "pk-context-menu-sub-chevron")
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -1,38 +1,56 @@
|
|
|
1
|
-
/* Co-located with dialog.rb. Native <dialog> centered by the UA; we style the
|
|
2
|
-
+ ::backdrop.
|
|
1
|
+
/* Co-located with dialog.rb. Native <dialog> centered by the UA; we style the
|
|
2
|
+
box + ::backdrop. Matched to shadcn/ui's current dialog (nova): popover
|
|
3
|
+
surface with a foreground-tint ring, rounded-xl, --pk-dialog-spacing: 1rem
|
|
4
|
+
driving padding + the full-bleed footer band, text-sm body, close pinned at
|
|
5
|
+
top-2/right-2. Their default width is max-w-sm (24rem) = our md.
|
|
6
|
+
Overlays are kit-unified at nova's black/10 + blur-xs (darkening to /50
|
|
7
|
+
where backdrop-filter is unsupported) — changed across dialog/
|
|
8
|
+
alert-dialog/sheet/drawer/command in one pass. Tokens global. */
|
|
3
9
|
.pk-dialog-trigger { display: inline-block; }
|
|
4
10
|
.pk-dialog {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
11
|
+
--pk-dialog-spacing: 1rem;
|
|
12
|
+
position: fixed; z-index: 50; width: 100%;
|
|
13
|
+
max-width: min(24rem, calc(100% - 2rem));
|
|
14
|
+
max-height: 100vh;
|
|
15
|
+
overflow-y: auto; gap: 1rem;
|
|
16
|
+
border: 1px solid color-mix(in oklab, var(--pk-text) 10%, transparent);
|
|
17
|
+
border-radius: .75rem; background: var(--pk-surface); color: var(--pk-text);
|
|
18
|
+
padding: var(--pk-dialog-spacing);
|
|
19
|
+
font-size: .875rem;
|
|
20
|
+
box-shadow: 0 10px 30px rgb(0 0 0 / .35);
|
|
9
21
|
}
|
|
10
22
|
.pk-dialog[open] { display: flex; flex-direction: column; }
|
|
11
|
-
.pk-dialog::backdrop { background: rgb(0 0 0 / .
|
|
12
|
-
|
|
13
|
-
.pk-dialog
|
|
14
|
-
|
|
15
|
-
.pk-dialog.
|
|
16
|
-
.pk-dialog.
|
|
17
|
-
.pk-dialog
|
|
23
|
+
.pk-dialog::backdrop { background: rgb(0 0 0 / .1); backdrop-filter: blur(4px); }
|
|
24
|
+
@supports not (backdrop-filter: blur(1px)) {
|
|
25
|
+
.pk-dialog::backdrop { background: rgb(0 0 0 / .5); }
|
|
26
|
+
}
|
|
27
|
+
.pk-dialog.xs { max-width: min(16rem, calc(100% - 2rem)); }
|
|
28
|
+
.pk-dialog.sm { max-width: min(20rem, calc(100% - 2rem)); }
|
|
29
|
+
.pk-dialog.lg { max-width: min(32rem, calc(100% - 2rem)); }
|
|
30
|
+
.pk-dialog.xl { max-width: min(42rem, calc(100% - 2rem)); }
|
|
31
|
+
.pk-dialog.full { max-width: calc(100% - 2rem); }
|
|
32
|
+
.pk-dialog-header { display: flex; flex-direction: column; gap: .5rem; text-align: center; }
|
|
18
33
|
.pk-dialog-footer {
|
|
19
34
|
display: flex; flex-direction: column-reverse; gap: .5rem;
|
|
20
35
|
margin: 0 calc(-1 * var(--pk-dialog-spacing)) calc(-1 * var(--pk-dialog-spacing));
|
|
21
36
|
padding: var(--pk-dialog-spacing);
|
|
22
37
|
border-top: 1px solid var(--pk-border);
|
|
38
|
+
border-radius: 0 0 .75rem .75rem;
|
|
23
39
|
background: color-mix(in oklab, var(--pk-surface-2) 50%, transparent);
|
|
24
40
|
}
|
|
25
|
-
.pk-dialog-title { font-size:
|
|
41
|
+
.pk-dialog-title { font-size: 1rem; font-weight: 500; line-height: 1; }
|
|
26
42
|
.pk-dialog-description { font-size: .875rem; color: var(--pk-muted); }
|
|
43
|
+
.pk-dialog-description a { text-decoration: underline; text-underline-offset: 3px; color: inherit; }
|
|
44
|
+
.pk-dialog-description a:hover { color: var(--pk-text); }
|
|
27
45
|
.pk-dialog-middle { padding-block: 1rem; }
|
|
28
46
|
.pk-overlay-close {
|
|
29
|
-
position: absolute; inset-inline-end:
|
|
47
|
+
position: absolute; inset-inline-end: .5rem; top: .5rem; border: 0; background: none;
|
|
30
48
|
cursor: pointer; border-radius: calc(var(--pk-radius) - 4px); opacity: .7; color: var(--pk-text);
|
|
49
|
+
padding: .25rem; display: inline-flex;
|
|
31
50
|
transition: opacity .15s ease;
|
|
32
51
|
}
|
|
33
52
|
.pk-overlay-close:hover { opacity: 1; }
|
|
34
53
|
@media (min-width: 640px) {
|
|
35
54
|
.pk-dialog-header { text-align: left; }
|
|
36
55
|
.pk-dialog-footer { flex-direction: row; justify-content: flex-end; }
|
|
37
|
-
.pk-dialog-footer > * + * { margin-inline-start: .5rem; }
|
|
38
56
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Wraps any element (typically a Button) so clicking it dismisses the
|
|
3
|
+
# enclosing dialog, mirroring shadcn/ui's DialogClose. See dialog.rb.
|
|
4
|
+
class DialogClose < BaseComponent
|
|
5
|
+
def initialize(**attrs)
|
|
6
|
+
@attrs = attrs
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def view_template(&)
|
|
10
|
+
span(**mix({ style: "display: contents", data: { action: "click->phlex-kit--dialog#dismiss" } }, @attrs), &)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
module PhlexKit
|
|
2
|
+
# The <dialog> box. `size:` picks the width tier (md = shadcn's default
|
|
3
|
+
# max-w-sm); `show_close_button: false` drops the top-right ✕ (their
|
|
4
|
+
# showCloseButton). See dialog.rb.
|
|
2
5
|
class DialogContent < BaseComponent
|
|
3
6
|
SIZES = { xs: "xs", sm: "sm", md: nil, lg: "lg", xl: "xl", full: "full" }.freeze
|
|
4
|
-
|
|
7
|
+
|
|
8
|
+
def initialize(size: :md, show_close_button: true, **attrs)
|
|
5
9
|
@size = size.to_sym
|
|
10
|
+
@show_close_button = show_close_button
|
|
6
11
|
@attrs = attrs
|
|
7
12
|
end
|
|
13
|
+
|
|
8
14
|
def view_template(&block)
|
|
9
|
-
cls = ["pk-dialog", SIZES.fetch(@size)].compact.join(" ")
|
|
15
|
+
cls = [ "pk-dialog", SIZES.fetch(@size) ].compact.join(" ")
|
|
10
16
|
dialog(**mix({ class: cls, data: { phlex_kit__dialog_target: "dialog", action: "click->phlex-kit--dialog#backdropClick" } }, @attrs)) do
|
|
11
17
|
yield
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
if @show_close_button
|
|
19
|
+
button(type: "button", class: "pk-overlay-close", data: { action: "click->phlex-kit--dialog#dismiss" }) do
|
|
20
|
+
render Icon.new(:x, size: 15)
|
|
21
|
+
span(class: "pk-sr-only") { "Close" }
|
|
22
|
+
end
|
|
15
23
|
end
|
|
16
24
|
end
|
|
17
25
|
end
|
|
@@ -1,47 +1,83 @@
|
|
|
1
|
-
/* Co-located with drawer.rb —
|
|
2
|
-
kit's sheet clone machinery)
|
|
3
|
-
|
|
1
|
+
/* Co-located with drawer.rb — PhlexKit::Drawer, shadcn parity (vaul replaced
|
|
2
|
+
by the kit's sheet clone machinery). Matched to nova: popover surface,
|
|
3
|
+
text-sm body, rounded-xl on the attached edge only, all four sides
|
|
4
|
+
(bottom default; top/left/right via DrawerContent side:). nova hides the
|
|
5
|
+
classic grab handle (.cn-drawer-handle is `hidden`) — ours matches, and a
|
|
6
|
+
host can re-show it with `.pk-drawer-handle { display: block }`. */
|
|
4
7
|
.pk-drawer-trigger, .pk-drawer-close { display: inline-block; }
|
|
5
8
|
.pk-drawer-overlay {
|
|
6
9
|
position: fixed;
|
|
7
10
|
inset: 0;
|
|
8
11
|
z-index: 50;
|
|
9
|
-
background: rgb(0 0 0 / .
|
|
10
|
-
backdrop-filter: blur(
|
|
12
|
+
background: rgb(0 0 0 / .1);
|
|
13
|
+
backdrop-filter: blur(4px);
|
|
11
14
|
}
|
|
12
15
|
.pk-drawer {
|
|
13
16
|
position: fixed;
|
|
14
|
-
inset-inline: 0;
|
|
15
|
-
bottom: 0;
|
|
16
17
|
z-index: 50;
|
|
17
18
|
display: flex;
|
|
18
19
|
flex-direction: column;
|
|
19
|
-
max-height: 85vh;
|
|
20
|
-
border: 1px solid var(--pk-border);
|
|
21
|
-
border-bottom: 0;
|
|
22
|
-
border-top-left-radius: calc(var(--pk-radius) + 4px);
|
|
23
|
-
border-top-right-radius: calc(var(--pk-radius) + 4px);
|
|
24
20
|
background: var(--pk-surface);
|
|
25
21
|
color: var(--pk-text);
|
|
22
|
+
font-size: .875rem;
|
|
23
|
+
}
|
|
24
|
+
/* bottom (default) */
|
|
25
|
+
.pk-drawer.bottom {
|
|
26
|
+
inset-inline: 0;
|
|
27
|
+
bottom: 0;
|
|
28
|
+
max-height: 85vh;
|
|
29
|
+
border-top: 1px solid var(--pk-border);
|
|
30
|
+
border-radius: .75rem .75rem 0 0;
|
|
26
31
|
animation: pk-drawer-up .3s ease;
|
|
27
32
|
}
|
|
33
|
+
.pk-drawer.top {
|
|
34
|
+
inset-inline: 0;
|
|
35
|
+
top: 0;
|
|
36
|
+
max-height: 85vh;
|
|
37
|
+
border-bottom: 1px solid var(--pk-border);
|
|
38
|
+
border-radius: 0 0 .75rem .75rem;
|
|
39
|
+
animation: pk-drawer-down .3s ease;
|
|
40
|
+
}
|
|
41
|
+
.pk-drawer.left,
|
|
42
|
+
.pk-drawer.right {
|
|
43
|
+
inset-block: 0;
|
|
44
|
+
width: 75%;
|
|
45
|
+
max-width: 24rem;
|
|
46
|
+
max-height: 100vh;
|
|
47
|
+
}
|
|
48
|
+
.pk-drawer.left {
|
|
49
|
+
left: 0;
|
|
50
|
+
border-right: 1px solid var(--pk-border);
|
|
51
|
+
border-radius: 0 .75rem .75rem 0;
|
|
52
|
+
animation: pk-drawer-from-left .3s ease;
|
|
53
|
+
}
|
|
54
|
+
.pk-drawer.right {
|
|
55
|
+
right: 0;
|
|
56
|
+
border-left: 1px solid var(--pk-border);
|
|
57
|
+
border-radius: .75rem 0 0 .75rem;
|
|
58
|
+
animation: pk-drawer-from-right .3s ease;
|
|
59
|
+
}
|
|
28
60
|
@keyframes pk-drawer-up { from { transform: translateY(100%); } to { transform: translateY(0); } }
|
|
61
|
+
@keyframes pk-drawer-down { from { transform: translateY(-100%); } to { transform: translateY(0); } }
|
|
62
|
+
@keyframes pk-drawer-from-left { from { transform: translateX(-100%); } to { transform: translateX(0); } }
|
|
63
|
+
@keyframes pk-drawer-from-right { from { transform: translateX(100%); } to { transform: translateX(0); } }
|
|
29
64
|
.pk-drawer-handle {
|
|
65
|
+
display: none;
|
|
30
66
|
flex: none;
|
|
31
67
|
width: 100px;
|
|
32
|
-
height: .
|
|
33
|
-
margin:
|
|
68
|
+
height: .25rem;
|
|
69
|
+
margin: 1rem auto 0;
|
|
34
70
|
border-radius: 9999px;
|
|
35
71
|
background: var(--pk-surface-2);
|
|
36
72
|
}
|
|
37
73
|
.pk-drawer-header {
|
|
38
74
|
display: flex;
|
|
39
75
|
flex-direction: column;
|
|
40
|
-
gap: .
|
|
76
|
+
gap: .125rem;
|
|
41
77
|
padding: 1rem;
|
|
42
78
|
text-align: center;
|
|
43
79
|
}
|
|
44
|
-
.pk-drawer-title { margin: 0; font-size:
|
|
80
|
+
.pk-drawer-title { margin: 0; font-size: 1rem; font-weight: 500; color: var(--pk-text); }
|
|
45
81
|
.pk-drawer-description { margin: 0; font-size: .875rem; color: var(--pk-muted); }
|
|
46
82
|
.pk-drawer-footer {
|
|
47
83
|
display: flex;
|
|
@@ -50,6 +86,9 @@
|
|
|
50
86
|
margin-top: auto;
|
|
51
87
|
padding: 1rem;
|
|
52
88
|
}
|
|
53
|
-
@media (min-width:
|
|
89
|
+
@media (min-width: 768px) {
|
|
54
90
|
.pk-drawer-header { text-align: left; }
|
|
55
91
|
}
|
|
92
|
+
@supports not (backdrop-filter: blur(1px)) {
|
|
93
|
+
.pk-drawer-overlay { background: rgb(0 0 0 / .5); }
|
|
94
|
+
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
module PhlexKit
|
|
2
|
-
# <template> holding the drawer overlay +
|
|
3
|
-
#
|
|
2
|
+
# <template> holding the drawer overlay + panel (cloned into <body> on open,
|
|
3
|
+
# like SheetContent). `side:` picks the attached edge — :bottom (default),
|
|
4
|
+
# :top, :left or :right, matching shadcn's direction prop. See drawer.rb.
|
|
4
5
|
class DrawerContent < BaseComponent
|
|
5
|
-
|
|
6
|
+
SIDES = { bottom: "bottom", top: "top", left: "left", right: "right" }.freeze
|
|
7
|
+
|
|
8
|
+
def initialize(side: :bottom, **attrs)
|
|
9
|
+
@side = side.to_sym
|
|
6
10
|
@attrs = attrs
|
|
7
11
|
end
|
|
8
12
|
|
|
@@ -10,7 +14,7 @@ module PhlexKit
|
|
|
10
14
|
template(data: { phlex_kit__sheet_target: "content" }) do
|
|
11
15
|
div(data: { controller: "phlex-kit--sheet-content" }) do
|
|
12
16
|
div(class: "pk-drawer-overlay", data: { action: "click->phlex-kit--sheet-content#close" })
|
|
13
|
-
div(**mix({ class: "pk-drawer", role: "dialog", aria: { modal: "true" } }, @attrs)) do
|
|
17
|
+
div(**mix({ class: "pk-drawer #{SIDES.fetch(@side)}", role: "dialog", aria: { modal: "true" } }, @attrs)) do
|
|
14
18
|
div(class: "pk-drawer-handle", aria: { hidden: "true" })
|
|
15
19
|
yield if block
|
|
16
20
|
end
|
|
@@ -1,25 +1,30 @@
|
|
|
1
|
-
/* Co-located with empty.rb.
|
|
1
|
+
/* Co-located with empty.rb. Matched to shadcn/ui's current empty (nova):
|
|
2
|
+
borderless by default (their border-dashed sets only the STYLE — the
|
|
3
|
+
Outline example opts into a visible dashed border via class/style),
|
|
4
|
+
rounded-xl, p-6, text-sm title. Theme tokens from the global stylesheet. */
|
|
2
5
|
.pk-empty {
|
|
3
6
|
display: flex; width: 100%; min-width: 0; flex: 1; flex-direction: column;
|
|
4
7
|
align-items: center; justify-content: center; gap: 1rem;
|
|
5
|
-
border:
|
|
6
|
-
padding:
|
|
8
|
+
border: 0 dashed var(--pk-border); border-radius: .75rem;
|
|
9
|
+
padding: 1.5rem; text-align: center; text-wrap: balance;
|
|
7
10
|
}
|
|
11
|
+
.pk-empty.outline { border-width: 1px; }
|
|
8
12
|
.pk-empty-header { display: flex; max-width: 24rem; flex-direction: column; align-items: center; gap: .5rem; }
|
|
9
13
|
.pk-empty-media {
|
|
10
14
|
margin-bottom: .5rem; display: flex; flex: none;
|
|
11
15
|
align-items: center; justify-content: center;
|
|
12
16
|
}
|
|
17
|
+
.pk-empty-media svg { pointer-events: none; flex: none; }
|
|
13
18
|
.pk-empty-media.icon {
|
|
14
|
-
height:
|
|
19
|
+
height: 2rem; width: 2rem; border-radius: var(--pk-radius);
|
|
15
20
|
background: var(--pk-surface-2); color: var(--pk-text);
|
|
16
21
|
}
|
|
17
|
-
.pk-empty-media.icon > svg:not([class*="size-"]) { width:
|
|
18
|
-
.pk-empty-title { font-size:
|
|
22
|
+
.pk-empty-media.icon > svg:not([class*="size-"]) { width: 1rem; height: 1rem; }
|
|
23
|
+
.pk-empty-title { font-size: .875rem; font-weight: 500; letter-spacing: -.025em; }
|
|
19
24
|
.pk-empty-description { font-size: .875rem; line-height: 1.625; color: var(--pk-muted); }
|
|
20
|
-
.pk-empty-description a { text-decoration: underline; text-underline-offset: 4px; }
|
|
25
|
+
.pk-empty-description a { text-decoration: underline; text-underline-offset: 4px; color: inherit; }
|
|
21
26
|
.pk-empty-description a:hover { color: var(--pk-brand); }
|
|
22
27
|
.pk-empty-content {
|
|
23
28
|
display: flex; width: 100%; max-width: 24rem; min-width: 0;
|
|
24
|
-
flex-direction: column; align-items: center; gap:
|
|
29
|
+
flex-direction: column; align-items: center; gap: .625rem; font-size: .875rem;
|
|
25
30
|
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/* Co-located with field.rb — the PhlexKit::Field system (Set / Legend /
|
|
2
|
+
Group / Field / Content / Label / Title / Description / Separator /
|
|
3
|
+
Error), shadcn parity. Field is presentational layout; FormField remains
|
|
4
|
+
the Stimulus live-validation wrapper — they nest freely. FieldGroup is a
|
|
5
|
+
size container so orientation: :responsive fields can flip horizontal at
|
|
6
|
+
≥28rem of group width. The choice-card recipe is a FieldLabel wrapping a
|
|
7
|
+
whole Field: it grows a border and highlights while its control is
|
|
8
|
+
checked (:has(input:checked)). */
|
|
9
|
+
.pk-field-set {
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
gap: 1rem;
|
|
13
|
+
min-width: 0;
|
|
14
|
+
margin: 0;
|
|
15
|
+
padding: 0;
|
|
16
|
+
border: 0;
|
|
17
|
+
}
|
|
18
|
+
.pk-field-set:has(> .pk-radio-group) { gap: .75rem; }
|
|
19
|
+
.pk-field-legend {
|
|
20
|
+
margin-bottom: .375rem;
|
|
21
|
+
padding: 0;
|
|
22
|
+
font-weight: 500;
|
|
23
|
+
}
|
|
24
|
+
.pk-field-legend.legend { font-size: 1rem; }
|
|
25
|
+
.pk-field-legend.label { font-size: .875rem; }
|
|
26
|
+
.pk-field-group {
|
|
27
|
+
display: flex;
|
|
28
|
+
width: 100%;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
gap: 1.25rem;
|
|
31
|
+
container-type: inline-size;
|
|
32
|
+
}
|
|
33
|
+
.pk-field-group .pk-field-group { gap: 1rem; container-type: normal; }
|
|
34
|
+
|
|
35
|
+
.pk-field { display: flex; width: 100%; gap: .5rem; }
|
|
36
|
+
.pk-field[data-invalid="true"] { color: var(--pk-red); }
|
|
37
|
+
.pk-field[data-orientation="vertical"] { flex-direction: column; }
|
|
38
|
+
.pk-field[data-orientation="vertical"] > * { width: 100%; }
|
|
39
|
+
.pk-field[data-orientation="vertical"] > .pk-sr-only { width: auto; }
|
|
40
|
+
.pk-field[data-orientation="horizontal"] { flex-direction: row; align-items: center; }
|
|
41
|
+
.pk-field[data-orientation="horizontal"] > .pk-field-label,
|
|
42
|
+
.pk-field[data-orientation="horizontal"] > .pk-field-content { flex: 1 1 auto; }
|
|
43
|
+
.pk-field[data-orientation="horizontal"]:has(> .pk-field-content) { align-items: flex-start; }
|
|
44
|
+
.pk-field[data-orientation="horizontal"]:has(> .pk-field-content) > .pk-checkbox,
|
|
45
|
+
.pk-field[data-orientation="horizontal"]:has(> .pk-field-content) > .pk-radio { margin-top: 1px; }
|
|
46
|
+
.pk-field[data-orientation="responsive"] { flex-direction: column; }
|
|
47
|
+
.pk-field[data-orientation="responsive"] > * { width: 100%; }
|
|
48
|
+
.pk-field[data-orientation="responsive"] > .pk-sr-only { width: auto; }
|
|
49
|
+
@container (min-width: 28rem) {
|
|
50
|
+
.pk-field[data-orientation="responsive"] { flex-direction: row; align-items: center; }
|
|
51
|
+
.pk-field[data-orientation="responsive"] > * { width: auto; }
|
|
52
|
+
.pk-field[data-orientation="responsive"] > .pk-field-label,
|
|
53
|
+
.pk-field[data-orientation="responsive"] > .pk-field-content { flex: 1 1 auto; }
|
|
54
|
+
.pk-field[data-orientation="responsive"]:has(> .pk-field-content) { align-items: flex-start; }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.pk-field-content {
|
|
58
|
+
display: flex;
|
|
59
|
+
flex: 1;
|
|
60
|
+
flex-direction: column;
|
|
61
|
+
gap: .125rem;
|
|
62
|
+
line-height: 1.375;
|
|
63
|
+
}
|
|
64
|
+
.pk-field-label {
|
|
65
|
+
gap: .5rem;
|
|
66
|
+
line-height: 1.375;
|
|
67
|
+
width: fit-content;
|
|
68
|
+
}
|
|
69
|
+
.pk-field[data-disabled="true"] .pk-field-label,
|
|
70
|
+
.pk-field[data-disabled="true"] .pk-field-title { opacity: .5; }
|
|
71
|
+
|
|
72
|
+
/* Choice card: a FieldLabel wrapping a whole Field. */
|
|
73
|
+
.pk-field-label:has(> .pk-field) {
|
|
74
|
+
width: 100%;
|
|
75
|
+
flex-direction: column;
|
|
76
|
+
align-items: stretch;
|
|
77
|
+
border: 1px solid var(--pk-border);
|
|
78
|
+
border-radius: var(--pk-radius);
|
|
79
|
+
font-weight: 400;
|
|
80
|
+
transition: background-color .15s ease, border-color .15s ease;
|
|
81
|
+
}
|
|
82
|
+
.pk-field-label > .pk-field { padding: .625rem; }
|
|
83
|
+
.pk-field-label:has(> .pk-field):has(input:checked) {
|
|
84
|
+
background: color-mix(in oklab, var(--pk-brand) 10%, transparent);
|
|
85
|
+
border-color: color-mix(in oklab, var(--pk-brand) 20%, transparent);
|
|
86
|
+
}
|
|
87
|
+
:root[data-theme="light"] .pk-field-label:has(> .pk-field):has(input:checked) {
|
|
88
|
+
background: color-mix(in oklab, var(--pk-brand) 5%, transparent);
|
|
89
|
+
border-color: color-mix(in oklab, var(--pk-brand) 30%, transparent);
|
|
90
|
+
}
|
|
91
|
+
@media (prefers-color-scheme: light) {
|
|
92
|
+
:root[data-theme="system"] .pk-field-label:has(> .pk-field):has(input:checked) {
|
|
93
|
+
background: color-mix(in oklab, var(--pk-brand) 5%, transparent);
|
|
94
|
+
border-color: color-mix(in oklab, var(--pk-brand) 30%, transparent);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.pk-field-title {
|
|
99
|
+
display: flex;
|
|
100
|
+
width: fit-content;
|
|
101
|
+
align-items: center;
|
|
102
|
+
gap: .5rem;
|
|
103
|
+
font-size: .875rem;
|
|
104
|
+
font-weight: 500;
|
|
105
|
+
line-height: 1.375;
|
|
106
|
+
}
|
|
107
|
+
.pk-field-description {
|
|
108
|
+
margin: 0;
|
|
109
|
+
color: var(--pk-muted);
|
|
110
|
+
font-size: .875rem;
|
|
111
|
+
font-weight: 400;
|
|
112
|
+
line-height: 1.5;
|
|
113
|
+
text-align: left;
|
|
114
|
+
}
|
|
115
|
+
.pk-field-description a { text-decoration: underline; text-underline-offset: 4px; color: inherit; }
|
|
116
|
+
.pk-field-description a:hover { color: var(--pk-brand); }
|
|
117
|
+
.pk-field-legend.legend + .pk-field-description { margin-top: -.375rem; }
|
|
118
|
+
|
|
119
|
+
.pk-field-separator {
|
|
120
|
+
position: relative;
|
|
121
|
+
height: 1.25rem;
|
|
122
|
+
margin-block: -.5rem;
|
|
123
|
+
font-size: .875rem;
|
|
124
|
+
}
|
|
125
|
+
.pk-field-separator .pk-field-separator-line {
|
|
126
|
+
position: absolute;
|
|
127
|
+
inset-inline: 0;
|
|
128
|
+
top: 50%;
|
|
129
|
+
}
|
|
130
|
+
.pk-field-separator-content {
|
|
131
|
+
position: relative;
|
|
132
|
+
display: block;
|
|
133
|
+
width: fit-content;
|
|
134
|
+
margin-inline: auto;
|
|
135
|
+
padding-inline: .5rem;
|
|
136
|
+
background: var(--pk-bg);
|
|
137
|
+
color: var(--pk-muted);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.pk-field-error {
|
|
141
|
+
color: var(--pk-red);
|
|
142
|
+
font-size: .875rem;
|
|
143
|
+
font-weight: 400;
|
|
144
|
+
}
|
|
145
|
+
.pk-field-error-list {
|
|
146
|
+
display: flex;
|
|
147
|
+
flex-direction: column;
|
|
148
|
+
gap: .25rem;
|
|
149
|
+
margin: 0 0 0 1rem;
|
|
150
|
+
padding: 0;
|
|
151
|
+
list-style: disc;
|
|
152
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# One labeled control row/column, ported from shadcn/ui's Field. PRESENTATIONAL
|
|
3
|
+
# layout only — pair with PhlexKit::FormField when you want the kit's Stimulus
|
|
4
|
+
# live-validation (FormField wraps the control; Field arranges label, control,
|
|
5
|
+
# description and error around it — they nest without conflict).
|
|
6
|
+
#
|
|
7
|
+
# render PhlexKit::Field.new do
|
|
8
|
+
# render PhlexKit::FieldLabel.new(for: "email") { "Email" }
|
|
9
|
+
# render PhlexKit::Input.new(id: "email", type: :email)
|
|
10
|
+
# render PhlexKit::FieldDescription.new { "We never share it." }
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# orientation: :vertical (default) stacks; :horizontal puts the label beside
|
|
14
|
+
# the control (checkbox/switch rows); :responsive is vertical that goes
|
|
15
|
+
# horizontal when the enclosing FieldGroup is ≥28rem wide (container query).
|
|
16
|
+
# `invalid: true` tints the whole field; `disabled: true` dims labels.
|
|
17
|
+
# `.pk-field*` (field.css).
|
|
18
|
+
class Field < BaseComponent
|
|
19
|
+
ORIENTATIONS = { vertical: "vertical", horizontal: "horizontal", responsive: "responsive" }.freeze
|
|
20
|
+
|
|
21
|
+
def initialize(orientation: :vertical, invalid: false, disabled: false, **attrs)
|
|
22
|
+
@orientation = orientation.to_sym
|
|
23
|
+
@invalid = invalid
|
|
24
|
+
@disabled = disabled
|
|
25
|
+
@attrs = attrs
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def view_template(&)
|
|
29
|
+
data = { slot: "field", orientation: ORIENTATIONS.fetch(@orientation) }
|
|
30
|
+
data[:invalid] = "true" if @invalid
|
|
31
|
+
data[:disabled] = "true" if @disabled
|
|
32
|
+
div(**mix({ class: "pk-field", role: "group", data: data }, @attrs), &)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Stacks a FieldTitle/FieldLabel + FieldDescription beside a control in a
|
|
3
|
+
# horizontal Field (e.g. checkbox rows with helper text), ported from
|
|
4
|
+
# shadcn/ui's FieldContent. See field.rb.
|
|
5
|
+
class FieldContent < BaseComponent
|
|
6
|
+
def initialize(**attrs) = (@attrs = attrs)
|
|
7
|
+
def view_template(&)
|
|
8
|
+
div(**mix({ class: "pk-field-content", data: { slot: "field-content" } }, @attrs), &)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Muted helper text under (or beside) a Field's control, ported from
|
|
3
|
+
# shadcn/ui's FieldDescription. See field.rb.
|
|
4
|
+
class FieldDescription < BaseComponent
|
|
5
|
+
def initialize(**attrs) = (@attrs = attrs)
|
|
6
|
+
def view_template(&)
|
|
7
|
+
p(**mix({ class: "pk-field-description", data: { slot: "field-description" } }, @attrs), &)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|