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,25 +1,45 @@
|
|
|
1
|
-
/* Co-located with marker.rb —
|
|
2
|
-
bordered system-note row
|
|
1
|
+
/* Co-located with marker.rb — PhlexKit::Marker, shadcn parity: muted inline
|
|
2
|
+
status, bordered system-note row (bottom border only, like theirs), or
|
|
3
|
+
labeled separator. Interactive markers (href: / as: :button) underline and
|
|
4
|
+
brighten on hover. */
|
|
3
5
|
.pk-marker {
|
|
4
6
|
display: flex;
|
|
5
7
|
align-items: center;
|
|
6
8
|
gap: .5rem;
|
|
7
|
-
|
|
9
|
+
width: 100%;
|
|
10
|
+
min-height: 1rem;
|
|
8
11
|
color: var(--pk-muted);
|
|
9
12
|
font-size: .875rem;
|
|
13
|
+
text-align: left;
|
|
10
14
|
}
|
|
11
|
-
.pk-marker-
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
a.pk-marker, button.pk-marker {
|
|
16
|
+
cursor: pointer;
|
|
17
|
+
color: var(--pk-muted);
|
|
18
|
+
text-decoration: underline;
|
|
19
|
+
text-underline-offset: 3px;
|
|
20
|
+
transition: color .15s ease;
|
|
21
|
+
border: 0;
|
|
22
|
+
background: none;
|
|
23
|
+
padding: 0;
|
|
24
|
+
font: inherit;
|
|
25
|
+
font-size: .875rem;
|
|
26
|
+
}
|
|
27
|
+
a.pk-marker:hover, button.pk-marker:hover { color: var(--pk-text); }
|
|
28
|
+
.pk-marker svg:not([class*="size-"]) { width: 1rem; height: 1rem; }
|
|
29
|
+
.pk-marker-icon { display: inline-flex; flex: none; width: 1rem; height: 1rem; }
|
|
30
|
+
.pk-marker-content { min-width: 0; overflow-wrap: break-word; }
|
|
31
|
+
.pk-marker-content a { text-decoration: underline; text-underline-offset: 3px; color: inherit; }
|
|
32
|
+
.pk-marker-content a:hover { color: var(--pk-text); }
|
|
14
33
|
.pk-marker.border {
|
|
15
|
-
border: 1px solid var(--pk-border);
|
|
16
|
-
|
|
17
|
-
padding: .5rem .625rem;
|
|
34
|
+
border-bottom: 1px solid var(--pk-border);
|
|
35
|
+
padding-bottom: .5rem;
|
|
18
36
|
}
|
|
19
37
|
.pk-marker.separator { justify-content: center; }
|
|
38
|
+
.pk-marker.separator .pk-marker-content { flex: none; text-align: center; }
|
|
20
39
|
.pk-marker.separator::before, .pk-marker.separator::after {
|
|
21
40
|
content: "";
|
|
22
41
|
flex: 1;
|
|
42
|
+
min-width: 0;
|
|
23
43
|
height: 1px;
|
|
24
44
|
background: var(--pk-border);
|
|
25
45
|
}
|
|
@@ -2,17 +2,27 @@ module PhlexKit
|
|
|
2
2
|
# Conversation annotation, ported from shadcn/ui's Marker (an AI-chat-era
|
|
3
3
|
# addition): an inline status line, bordered system-note row, or labeled
|
|
4
4
|
# separator. Compose Marker(variant:) > [MarkerIcon +] MarkerContent.
|
|
5
|
-
#
|
|
5
|
+
# `href:` renders an <a>, `as: :button` a <button> (their asChild) — both
|
|
6
|
+
# underline and brighten on hover. `.pk-marker*` (marker.css).
|
|
6
7
|
class Marker < BaseComponent
|
|
7
8
|
VARIANTS = { default: nil, border: "border", separator: "separator" }.freeze
|
|
8
9
|
|
|
9
|
-
def initialize(variant: :default, **attrs)
|
|
10
|
+
def initialize(variant: :default, href: nil, as: :div, **attrs)
|
|
10
11
|
@variant = variant.to_sym
|
|
12
|
+
@href = href
|
|
13
|
+
@as = as.to_sym
|
|
11
14
|
@attrs = attrs
|
|
12
15
|
end
|
|
13
16
|
|
|
14
17
|
def view_template(&)
|
|
15
|
-
|
|
18
|
+
classes = [ "pk-marker", VARIANTS.fetch(@variant) ].compact.join(" ")
|
|
19
|
+
if @href
|
|
20
|
+
a(**mix({ class: classes, href: @href }, @attrs), &)
|
|
21
|
+
elsif @as == :button
|
|
22
|
+
button(**mix({ class: classes, type: "button" }, @attrs), &)
|
|
23
|
+
else
|
|
24
|
+
div(**mix({ class: classes }, @attrs), &)
|
|
25
|
+
end
|
|
16
26
|
end
|
|
17
27
|
end
|
|
18
28
|
end
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
/* Co-located with menubar.rb —
|
|
2
|
-
the phlex-kit--menubar controller).
|
|
1
|
+
/* Co-located with menubar.rb — PhlexKit::Menubar, shadcn parity (Radix
|
|
2
|
+
replaced by the phlex-kit--menubar controller). Matched to nova: h-8 bar
|
|
3
|
+
with 3px padding, tight triggers, ring-bordered popover panels, the full
|
|
4
|
+
menu-item family (destructive, inset, checkbox/radio with hidden inputs,
|
|
5
|
+
pure-CSS hover submenus). NavigationMenu shares the item look. */
|
|
3
6
|
.pk-menubar {
|
|
4
7
|
display: inline-flex;
|
|
5
8
|
align-items: center;
|
|
6
|
-
|
|
9
|
+
height: 2rem;
|
|
10
|
+
gap: .125rem;
|
|
7
11
|
border: 1px solid var(--pk-border);
|
|
8
12
|
border-radius: var(--pk-radius);
|
|
9
13
|
background: var(--pk-surface);
|
|
10
|
-
padding:
|
|
14
|
+
padding: 3px;
|
|
11
15
|
}
|
|
12
16
|
.pk-menubar-menu { position: relative; }
|
|
13
17
|
.pk-menubar-trigger {
|
|
@@ -17,7 +21,7 @@
|
|
|
17
21
|
border-radius: calc(var(--pk-radius) - 4px);
|
|
18
22
|
background: transparent;
|
|
19
23
|
color: var(--pk-text);
|
|
20
|
-
padding:
|
|
24
|
+
padding: 2px .375rem;
|
|
21
25
|
font: inherit;
|
|
22
26
|
font-size: .875rem;
|
|
23
27
|
font-weight: 500;
|
|
@@ -25,27 +29,36 @@
|
|
|
25
29
|
outline: none;
|
|
26
30
|
}
|
|
27
31
|
.pk-menubar-trigger:hover,
|
|
28
|
-
.pk-menubar-trigger[aria-expanded="true"] { background: var(--pk-
|
|
32
|
+
.pk-menubar-trigger[aria-expanded="true"] { background: var(--pk-surface-2); }
|
|
29
33
|
.pk-menubar-content {
|
|
30
34
|
position: absolute;
|
|
31
35
|
top: 100%;
|
|
32
36
|
left: 0;
|
|
33
37
|
z-index: 50;
|
|
34
|
-
min-width:
|
|
38
|
+
min-width: 9rem;
|
|
35
39
|
margin-top: .5rem;
|
|
36
|
-
border: 1px solid var(--pk-
|
|
40
|
+
border: 1px solid color-mix(in oklab, var(--pk-text) 10%, transparent);
|
|
37
41
|
border-radius: var(--pk-radius);
|
|
38
42
|
background: var(--pk-surface);
|
|
39
43
|
color: var(--pk-text);
|
|
40
44
|
padding: .25rem;
|
|
41
45
|
box-shadow: 0 8px 24px rgb(0 0 0 / .3);
|
|
42
46
|
}
|
|
47
|
+
/* Hover bridge over the trigger→panel gap. */
|
|
48
|
+
.pk-menubar-content::before {
|
|
49
|
+
content: "";
|
|
50
|
+
position: absolute;
|
|
51
|
+
top: calc(-.5rem - 1px);
|
|
52
|
+
left: 0;
|
|
53
|
+
right: 0;
|
|
54
|
+
height: calc(.5rem + 1px);
|
|
55
|
+
}
|
|
43
56
|
.pk-menubar-item {
|
|
44
57
|
display: flex;
|
|
45
58
|
align-items: center;
|
|
46
|
-
gap: .
|
|
47
|
-
border-radius: calc(var(--pk-radius) -
|
|
48
|
-
padding: .
|
|
59
|
+
gap: .375rem;
|
|
60
|
+
border-radius: calc(var(--pk-radius) - 2px);
|
|
61
|
+
padding: .25rem .375rem;
|
|
49
62
|
font-size: .875rem;
|
|
50
63
|
color: var(--pk-text);
|
|
51
64
|
text-decoration: none;
|
|
@@ -53,14 +66,72 @@
|
|
|
53
66
|
user-select: none;
|
|
54
67
|
outline: none;
|
|
55
68
|
}
|
|
69
|
+
.pk-menubar-item svg:not([class*="size-"]) { width: 1rem; height: 1rem; }
|
|
56
70
|
.pk-menubar-item:hover, .pk-menubar-item:focus { background: var(--pk-accent); }
|
|
71
|
+
.pk-menubar-item[data-disabled="true"] { pointer-events: none; opacity: .5; }
|
|
72
|
+
.pk-menubar-item.inset { padding-left: 1.75rem; }
|
|
73
|
+
.pk-menubar-item.destructive { color: var(--pk-red); }
|
|
74
|
+
.pk-menubar-item.destructive > svg { color: var(--pk-red); }
|
|
75
|
+
.pk-menubar-item.destructive:hover,
|
|
76
|
+
.pk-menubar-item.destructive:focus {
|
|
77
|
+
background: color-mix(in oklab, var(--pk-red) 12%, transparent);
|
|
78
|
+
color: var(--pk-red);
|
|
79
|
+
}
|
|
57
80
|
.pk-menubar-shortcut { margin-left: auto; font-size: .75rem; color: var(--pk-muted); letter-spacing: .1em; }
|
|
81
|
+
.pk-menubar-label { padding: .25rem .375rem; font-size: .875rem; font-weight: 500; }
|
|
82
|
+
.pk-menubar-label.inset { padding-left: 1.75rem; }
|
|
58
83
|
.pk-menubar-separator { height: 1px; margin: .25rem -0.25rem; background: var(--pk-border); }
|
|
59
|
-
.pk-menubar-
|
|
60
|
-
|
|
84
|
+
.pk-menubar-group { display: contents; }
|
|
85
|
+
|
|
86
|
+
/* Checkbox / radio rows: real (hidden) inputs; indicator shows via :has. */
|
|
87
|
+
.pk-menubar-item-input {
|
|
61
88
|
position: absolute;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
89
|
+
width: 1px;
|
|
90
|
+
height: 1px;
|
|
91
|
+
opacity: 0;
|
|
92
|
+
pointer-events: none;
|
|
93
|
+
}
|
|
94
|
+
.pk-menubar-checkbox-item,
|
|
95
|
+
.pk-menubar-radio-item { position: relative; padding-left: 1.75rem; }
|
|
96
|
+
.pk-menubar-item-indicator {
|
|
97
|
+
position: absolute;
|
|
98
|
+
left: .375rem;
|
|
99
|
+
display: inline-flex;
|
|
100
|
+
align-items: center;
|
|
101
|
+
justify-content: center;
|
|
102
|
+
width: 1rem;
|
|
103
|
+
height: 1rem;
|
|
104
|
+
visibility: hidden;
|
|
105
|
+
}
|
|
106
|
+
.pk-menubar-item-indicator svg { width: .875rem; height: .875rem; }
|
|
107
|
+
.pk-menubar-radio-item .pk-menubar-item-indicator svg { width: .5rem; height: .5rem; }
|
|
108
|
+
.pk-menubar-item:has(.pk-menubar-item-input:checked) .pk-menubar-item-indicator { visibility: visible; }
|
|
109
|
+
.pk-menubar-item:has(.pk-menubar-item-input:focus-visible) {
|
|
110
|
+
box-shadow: 0 0 0 2px color-mix(in oklab, var(--pk-ring) 50%, transparent);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* Submenu: trigger row + side panel, revealed on hover/focus — pure CSS. */
|
|
114
|
+
.pk-menubar-sub { position: relative; }
|
|
115
|
+
.pk-menubar-sub-chevron { margin-left: auto; width: 1rem; height: 1rem; }
|
|
116
|
+
.pk-menubar-sub-content {
|
|
117
|
+
position: absolute;
|
|
118
|
+
top: -0.25rem;
|
|
119
|
+
left: 100%;
|
|
120
|
+
z-index: 60;
|
|
121
|
+
min-width: 9rem;
|
|
122
|
+
padding-left: .25rem; /* hover bridge over the gap */
|
|
123
|
+
display: none;
|
|
124
|
+
}
|
|
125
|
+
.pk-menubar-sub-viewport {
|
|
126
|
+
min-width: 9rem;
|
|
127
|
+
border-radius: var(--pk-radius);
|
|
128
|
+
border: 1px solid color-mix(in oklab, var(--pk-text) 10%, transparent);
|
|
129
|
+
background: var(--pk-surface);
|
|
130
|
+
color: var(--pk-text);
|
|
131
|
+
padding: .25rem;
|
|
132
|
+
box-shadow: 0 8px 24px rgb(0 0 0 / .3);
|
|
66
133
|
}
|
|
134
|
+
.pk-menubar-sub:hover > .pk-menubar-sub-content,
|
|
135
|
+
.pk-menubar-sub:focus-within > .pk-menubar-sub-content { display: block; }
|
|
136
|
+
.pk-menubar-sub:hover > .pk-menubar-sub-trigger,
|
|
137
|
+
.pk-menubar-sub:focus-within > .pk-menubar-sub-trigger { background: var(--pk-accent); }
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Toggleable row, mirroring shadcn/ui's MenubarCheckboxItem: a <label>
|
|
3
|
+
# around a real (hidden) checkbox — clicking toggles it natively and the ✓
|
|
4
|
+
# shows via CSS, without closing the menu (matching Radix). See menubar.rb.
|
|
5
|
+
class MenubarCheckboxItem < BaseComponent
|
|
6
|
+
def initialize(checked: false, name: nil, value: "1", **attrs)
|
|
7
|
+
@checked = checked
|
|
8
|
+
@name = name
|
|
9
|
+
@value = value
|
|
10
|
+
@attrs = attrs
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def view_template(&block)
|
|
14
|
+
label(**mix({
|
|
15
|
+
class: "pk-menubar-item pk-menubar-checkbox-item",
|
|
16
|
+
role: "menuitemcheckbox"
|
|
17
|
+
}, @attrs)) do
|
|
18
|
+
input(type: :checkbox, class: "pk-menubar-item-input", name: @name, value: @value, checked: @checked)
|
|
19
|
+
span(class: "pk-menubar-item-indicator", aria: { hidden: "true" }) do
|
|
20
|
+
render Icon.new(:check, size: nil)
|
|
21
|
+
end
|
|
22
|
+
yield if block
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Grouping wrapper for related rows. Mirrors shadcn/ui's MenubarGroup.
|
|
3
|
+
# See menubar.rb.
|
|
4
|
+
class MenubarGroup < BaseComponent
|
|
5
|
+
def initialize(**attrs) = (@attrs = attrs)
|
|
6
|
+
def view_template(&)
|
|
7
|
+
div(**mix({ class: "pk-menubar-group", role: "group" }, @attrs), &)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
module PhlexKit
|
|
2
|
-
# Menu row (an <a> — pass href:, or as: :div for buttons).
|
|
3
|
-
#
|
|
2
|
+
# Menu row (an <a> — pass href:, or as: :div for buttons). `variant:
|
|
3
|
+
# :destructive` tints red; `inset: true` aligns with checkbox/radio rows.
|
|
4
|
+
# Closes the bar. See menubar.rb.
|
|
4
5
|
class MenubarItem < BaseComponent
|
|
5
|
-
|
|
6
|
+
VARIANTS = { default: nil, destructive: "destructive" }.freeze
|
|
7
|
+
|
|
8
|
+
def initialize(as: :a, href: "#", shortcut: nil, variant: :default, inset: false, **attrs)
|
|
6
9
|
@as = as.to_sym
|
|
7
10
|
@href = href
|
|
8
11
|
@shortcut = shortcut
|
|
12
|
+
@variant = variant.to_sym
|
|
13
|
+
@inset = inset
|
|
9
14
|
@attrs = attrs
|
|
10
15
|
end
|
|
11
16
|
|
|
@@ -13,7 +18,7 @@ module PhlexKit
|
|
|
13
18
|
base = {
|
|
14
19
|
role: "menuitem",
|
|
15
20
|
tabindex: "-1",
|
|
16
|
-
class: "pk-menubar-item",
|
|
21
|
+
class: [ "pk-menubar-item", VARIANTS.fetch(@variant), ("inset" if @inset) ].compact.join(" "),
|
|
17
22
|
data: { action: "click->phlex-kit--menubar#close" }
|
|
18
23
|
}
|
|
19
24
|
base[:href] = @href unless @as == :div
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Non-interactive heading row in a MenubarContent. Mirrors shadcn/ui's
|
|
3
|
+
# MenubarLabel. See menubar.rb.
|
|
4
|
+
class MenubarLabel < BaseComponent
|
|
5
|
+
def initialize(**attrs) = (@attrs = attrs)
|
|
6
|
+
def view_template(&)
|
|
7
|
+
div(**mix({ class: "pk-menubar-label" }, @attrs), &)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Exclusive-choice group of MenubarRadioItems (share the same `name:`).
|
|
3
|
+
# Mirrors shadcn/ui's MenubarRadioGroup. See menubar.rb.
|
|
4
|
+
class MenubarRadioGroup < BaseComponent
|
|
5
|
+
def initialize(**attrs) = (@attrs = attrs)
|
|
6
|
+
def view_template(&)
|
|
7
|
+
div(**mix({ class: "pk-menubar-group", role: "radiogroup" }, @attrs), &)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# One choice in a MenubarRadioGroup — a <label> around a real (hidden)
|
|
3
|
+
# radio; the ● shows via CSS and picking doesn't close the menu (matching
|
|
4
|
+
# Radix). See menubar.rb.
|
|
5
|
+
class MenubarRadioItem < 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-menubar-item pk-menubar-radio-item",
|
|
16
|
+
role: "menuitemradio"
|
|
17
|
+
}, @attrs)) do
|
|
18
|
+
input(type: :radio, class: "pk-menubar-item-input", name: @name, value: @value, checked: @checked)
|
|
19
|
+
span(class: "pk-menubar-item-indicator", aria: { hidden: "true" }) do
|
|
20
|
+
# A filled selection dot is geometry, not icon-library vocabulary.
|
|
21
|
+
svg(xmlns: "http://www.w3.org/2000/svg", viewbox: "0 0 24 24", fill: "currentColor") do |s|
|
|
22
|
+
s.circle(cx: "12", cy: "12", r: "5")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
yield if block
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Right-aligned keyboard hint on a MenubarItem (the item's `shortcut:` param
|
|
3
|
+
# renders the same span). Mirrors shadcn/ui's MenubarShortcut. See menubar.rb.
|
|
4
|
+
class MenubarShortcut < BaseComponent
|
|
5
|
+
def initialize(**attrs) = (@attrs = attrs)
|
|
6
|
+
def view_template(&block)
|
|
7
|
+
span(**mix({ class: "pk-menubar-shortcut" }, @attrs), &block)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Nested submenu wrapper: SubTrigger row + SubContent panel, revealed on
|
|
3
|
+
# hover/focus with pure CSS. Mirrors shadcn/ui's MenubarSub. See menubar.rb.
|
|
4
|
+
class MenubarSub < BaseComponent
|
|
5
|
+
def initialize(**attrs) = (@attrs = attrs)
|
|
6
|
+
def view_template(&)
|
|
7
|
+
div(**mix({ class: "pk-menubar-sub" }, @attrs), &)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# The nested panel of a MenubarSub — opens beside the trigger. See menubar.rb.
|
|
3
|
+
class MenubarSubContent < BaseComponent
|
|
4
|
+
def initialize(**attrs) = (@attrs = attrs)
|
|
5
|
+
def view_template(&)
|
|
6
|
+
div(**mix({ class: "pk-menubar-sub-content", role: "menu" }, @attrs)) do
|
|
7
|
+
div(class: "pk-menubar-sub-viewport", &)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# The row that opens a MenubarSub (trailing ▸ chevron). See menubar.rb.
|
|
3
|
+
class MenubarSubTrigger < BaseComponent
|
|
4
|
+
def initialize(**attrs) = (@attrs = attrs)
|
|
5
|
+
def view_template(&block)
|
|
6
|
+
div(**mix({
|
|
7
|
+
class: "pk-menubar-item pk-menubar-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-menubar-sub-chevron")
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
/* Co-located with message.rb.
|
|
1
|
+
/* Co-located with message.rb. Matched to shadcn/ui's current message: the
|
|
2
|
+
avatar rises above a footer row, header/footer lose their inset beside
|
|
3
|
+
ghost bubbles. Theme tokens from the global stylesheet. */
|
|
2
4
|
.pk-message {
|
|
3
5
|
position: relative; display: flex; width: 100%; min-width: 0;
|
|
4
6
|
gap: .5rem; font-size: .875rem;
|
|
@@ -18,3 +20,8 @@
|
|
|
18
20
|
align-items: center; justify-content: center; align-self: flex-end;
|
|
19
21
|
overflow: hidden; border-radius: 9999px; background: var(--pk-surface-2);
|
|
20
22
|
}
|
|
23
|
+
/* Keep the avatar level with the bubble when a footer row follows. */
|
|
24
|
+
.pk-message:has(.pk-message-footer) .pk-message-avatar { transform: translateY(-2rem); }
|
|
25
|
+
/* Frameless ghost bubbles sit flush — drop the header/footer inset. */
|
|
26
|
+
.pk-message:has([data-variant="ghost"]) .pk-message-header,
|
|
27
|
+
.pk-message:has([data-variant="ghost"]) .pk-message-footer { padding-inline: 0; }
|
|
@@ -1,2 +1,20 @@
|
|
|
1
1
|
/* Co-located with message_scroller.rb. Tokens from the global stylesheet. */
|
|
2
2
|
.pk-message-scroller { position: relative; display: flex; height: 100%; width: 100%; min-height: 0; flex-direction: column; overflow: hidden; }
|
|
3
|
+
/* Jump-to-latest affordance: floats bottom-center, appears only while the
|
|
4
|
+
reader is away from its edge (the controller stamps data-active). */
|
|
5
|
+
.pk-message-scroller-jump {
|
|
6
|
+
position: absolute;
|
|
7
|
+
bottom: .75rem;
|
|
8
|
+
left: 50%;
|
|
9
|
+
transform: translateX(-50%) translateY(.5rem);
|
|
10
|
+
z-index: 10;
|
|
11
|
+
opacity: 0;
|
|
12
|
+
pointer-events: none;
|
|
13
|
+
transition: opacity .15s ease, transform .15s ease;
|
|
14
|
+
box-shadow: 0 2px 8px rgb(0 0 0 / .25);
|
|
15
|
+
}
|
|
16
|
+
.pk-message-scroller-jump[data-active="true"] {
|
|
17
|
+
opacity: 1;
|
|
18
|
+
pointer-events: auto;
|
|
19
|
+
transform: translateX(-50%) translateY(0);
|
|
20
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* Co-located with native_select.rb — UI::NativeSelect (+ Icon / Option / Group).
|
|
2
2
|
ruby_ui's structure kept verbatim (wrapper > appearance:none select + absolute
|
|
3
|
-
chevron); only the styling is ours, vanilla CSS on the palette tokens.
|
|
4
|
-
|
|
3
|
+
chevron); only the styling is ours, vanilla CSS on the palette tokens. Matched to nova (h-8/h-7, rounded-lg,
|
|
4
|
+
translucent dark fill with hover step, ring focus, invalid states).
|
|
5
5
|
Theme tokens come from the global stylesheet. */
|
|
6
6
|
.pk-native-select {
|
|
7
7
|
position: relative;
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
height: 2rem;
|
|
15
15
|
appearance: none;
|
|
16
16
|
-webkit-appearance: none;
|
|
17
|
-
border-radius:
|
|
17
|
+
border-radius: var(--pk-radius);
|
|
18
18
|
border: 1px solid var(--pk-input);
|
|
19
|
-
background: transparent;
|
|
19
|
+
background: color-mix(in oklab, var(--pk-input) 30%, transparent);
|
|
20
20
|
color: var(--pk-text);
|
|
21
21
|
font: inherit;
|
|
22
22
|
font-size: .875rem;
|
|
@@ -24,9 +24,10 @@
|
|
|
24
24
|
cursor: pointer;
|
|
25
25
|
transition: color .15s ease, border-color .15s ease, box-shadow .15s ease;
|
|
26
26
|
}
|
|
27
|
+
.pk-native-select-field:hover { background: color-mix(in oklab, var(--pk-input) 50%, transparent); }
|
|
27
28
|
.pk-native-select-field:focus-visible {
|
|
28
29
|
outline: none;
|
|
29
|
-
border-color: var(--pk-
|
|
30
|
+
border-color: var(--pk-ring);
|
|
30
31
|
box-shadow: 0 0 0 3px color-mix(in oklab, var(--pk-ring) 50%, transparent);
|
|
31
32
|
}
|
|
32
33
|
.pk-native-select-field:disabled { cursor: not-allowed; opacity: .5; }
|
|
@@ -34,6 +35,25 @@
|
|
|
34
35
|
height: 1.75rem;
|
|
35
36
|
padding-top: .125rem;
|
|
36
37
|
padding-bottom: .125rem;
|
|
38
|
+
border-radius: min(calc(var(--pk-radius) - 2px), 10px);
|
|
39
|
+
}
|
|
40
|
+
.pk-native-select-field[aria-invalid="true"] {
|
|
41
|
+
border-color: color-mix(in oklab, var(--pk-red) 50%, transparent);
|
|
42
|
+
box-shadow: 0 0 0 3px color-mix(in oklab, var(--pk-red) 40%, transparent);
|
|
43
|
+
}
|
|
44
|
+
:root[data-theme="light"] .pk-native-select-field { background: transparent; }
|
|
45
|
+
:root[data-theme="light"] .pk-native-select-field:hover { background: transparent; }
|
|
46
|
+
:root[data-theme="light"] .pk-native-select-field[aria-invalid="true"] {
|
|
47
|
+
border-color: var(--pk-red);
|
|
48
|
+
box-shadow: 0 0 0 3px color-mix(in oklab, var(--pk-red) 20%, transparent);
|
|
49
|
+
}
|
|
50
|
+
@media (prefers-color-scheme: light) {
|
|
51
|
+
:root[data-theme="system"] .pk-native-select-field { background: transparent; }
|
|
52
|
+
:root[data-theme="system"] .pk-native-select-field:hover { background: transparent; }
|
|
53
|
+
:root[data-theme="system"] .pk-native-select-field[aria-invalid="true"] {
|
|
54
|
+
border-color: var(--pk-red);
|
|
55
|
+
box-shadow: 0 0 0 3px color-mix(in oklab, var(--pk-red) 20%, transparent);
|
|
56
|
+
}
|
|
37
57
|
}
|
|
38
58
|
/* Native option lists are OS-drawn; force a readable palette where honoured. */
|
|
39
59
|
.pk-native-select-field option,
|
|
@@ -14,12 +14,11 @@
|
|
|
14
14
|
display: inline-flex;
|
|
15
15
|
align-items: center;
|
|
16
16
|
gap: .25rem;
|
|
17
|
-
height: 2.25rem;
|
|
18
17
|
border: 0;
|
|
19
|
-
border-radius:
|
|
18
|
+
border-radius: var(--pk-radius);
|
|
20
19
|
background: transparent;
|
|
21
20
|
color: var(--pk-text);
|
|
22
|
-
padding: .
|
|
21
|
+
padding: .375rem .625rem;
|
|
23
22
|
font: inherit;
|
|
24
23
|
font-size: .875rem;
|
|
25
24
|
font-weight: 500;
|
|
@@ -29,7 +28,10 @@
|
|
|
29
28
|
transition: background-color .15s ease;
|
|
30
29
|
}
|
|
31
30
|
.pk-navigation-menu-trigger:hover, .pk-navigation-menu-link:hover,
|
|
32
|
-
.pk-navigation-menu-trigger
|
|
31
|
+
.pk-navigation-menu-trigger:focus-visible, .pk-navigation-menu-link:focus-visible { background: var(--pk-surface-2); }
|
|
32
|
+
.pk-navigation-menu-trigger[aria-expanded="true"] { background: color-mix(in oklab, var(--pk-surface-2) 50%, transparent); }
|
|
33
|
+
.pk-navigation-menu-trigger[aria-expanded="true"]:hover { background: var(--pk-surface-2); }
|
|
34
|
+
.pk-navigation-menu-link svg:not([class*="size-"]) { width: 1rem; height: 1rem; }
|
|
33
35
|
.pk-navigation-menu-chevron { width: .75rem; height: .75rem; transition: transform .2s ease; }
|
|
34
36
|
.pk-navigation-menu-trigger[aria-expanded="true"] .pk-navigation-menu-chevron { transform: rotate(180deg); }
|
|
35
37
|
.pk-navigation-menu-content {
|
|
@@ -42,17 +44,18 @@
|
|
|
42
44
|
z-index: 50;
|
|
43
45
|
min-width: 16rem;
|
|
44
46
|
margin-top: var(--pk-nav-gap);
|
|
45
|
-
border: 1px solid var(--pk-
|
|
47
|
+
border: 1px solid color-mix(in oklab, var(--pk-text) 10%, transparent);
|
|
46
48
|
border-radius: var(--pk-radius);
|
|
47
49
|
background: var(--pk-surface);
|
|
48
50
|
color: var(--pk-text);
|
|
49
|
-
padding: .
|
|
51
|
+
padding: .25rem;
|
|
50
52
|
box-shadow: 0 8px 24px rgb(0 0 0 / .3);
|
|
51
53
|
}
|
|
52
54
|
.pk-navigation-menu-content .pk-navigation-menu-link {
|
|
53
55
|
display: flex;
|
|
54
56
|
height: auto;
|
|
55
57
|
width: 100%;
|
|
58
|
+
border-radius: calc(var(--pk-radius) - 2px);
|
|
56
59
|
flex-direction: column;
|
|
57
60
|
align-items: flex-start;
|
|
58
61
|
gap: .25rem;
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
/* Co-located with pagination.rb. Items reuse .pk-button
|
|
1
|
+
/* Co-located with pagination.rb. Items reuse .pk-button (numbers are square
|
|
2
|
+
ghost buttons, outline when active; Previous/Next carry a chevron with a
|
|
3
|
+
label that hides on small screens). Matched to nova (gap-0.5 content,
|
|
4
|
+
size-8 ellipsis, near-side padding trims on Previous/Next). Tokens global. */
|
|
2
5
|
.pk-pagination { margin-inline: auto; display: flex; width: 100%; justify-content: center; }
|
|
3
|
-
.pk-pagination-content { display: flex; flex-direction: row; align-items: center; gap: .
|
|
4
|
-
.pk-pagination-ellipsis { display: flex; height:
|
|
6
|
+
.pk-pagination-content { display: flex; flex-direction: row; align-items: center; gap: .125rem; list-style: none; margin: 0; padding: 0; }
|
|
7
|
+
.pk-pagination-ellipsis { display: flex; height: 2rem; width: 2rem; align-items: center; justify-content: center; }
|
|
5
8
|
.pk-pagination-ellipsis > svg { width: 1rem; height: 1rem; }
|
|
9
|
+
.pk-button.pk-pagination-previous { padding-left: .375rem; }
|
|
10
|
+
.pk-button.pk-pagination-next { padding-right: .375rem; }
|
|
11
|
+
.pk-pagination-label { display: none; }
|
|
12
|
+
@media (min-width: 640px) { .pk-pagination-label { display: block; } }
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# A page-number link (square ghost button; outline when active), ported from
|
|
3
|
+
# shadcn/ui's PaginationLink. Renders its own <li>. See pagination.rb.
|
|
4
|
+
class PaginationLink < BaseComponent
|
|
5
|
+
def initialize(href: "#", active: false, **attrs)
|
|
6
|
+
@href = href
|
|
7
|
+
@active = active
|
|
8
|
+
@attrs = attrs
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def view_template(&block)
|
|
12
|
+
li do
|
|
13
|
+
a(**mix({
|
|
14
|
+
href: @href,
|
|
15
|
+
class: "pk-button #{@active ? "outline" : "ghost"} icon",
|
|
16
|
+
aria: { current: (@active ? "page" : nil) }
|
|
17
|
+
}, @attrs), &block)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# "Next" control (label + chevron, label hidden on small screens), ported
|
|
3
|
+
# from shadcn/ui's PaginationNext. Renders its own <li>. See pagination.rb.
|
|
4
|
+
class PaginationNext < BaseComponent
|
|
5
|
+
def initialize(href: "#", label: "Next", **attrs)
|
|
6
|
+
@href = href
|
|
7
|
+
@label = label
|
|
8
|
+
@attrs = attrs
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def view_template
|
|
12
|
+
li do
|
|
13
|
+
a(**mix({ href: @href, class: "pk-button ghost pk-pagination-next", aria: { label: "Go to next page" } }, @attrs)) do
|
|
14
|
+
span(class: "pk-pagination-label") { @label }
|
|
15
|
+
render Icon.new(:chevron_right, size: nil)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# "Previous" control (chevron + label, label hidden on small screens),
|
|
3
|
+
# ported from shadcn/ui's PaginationPrevious. Renders its own <li>.
|
|
4
|
+
# See pagination.rb.
|
|
5
|
+
class PaginationPrevious < BaseComponent
|
|
6
|
+
def initialize(href: "#", label: "Previous", **attrs)
|
|
7
|
+
@href = href
|
|
8
|
+
@label = label
|
|
9
|
+
@attrs = attrs
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def view_template
|
|
13
|
+
li do
|
|
14
|
+
a(**mix({ href: @href, class: "pk-button ghost pk-pagination-previous", aria: { label: "Go to previous page" } }, @attrs)) do
|
|
15
|
+
render Icon.new(:chevron_left, size: nil)
|
|
16
|
+
span(class: "pk-pagination-label") { @label }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|