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,24 +1,64 @@
|
|
|
1
|
-
/* Co-located with bubble.rb
|
|
2
|
-
|
|
1
|
+
/* Co-located with bubble.rb — PhlexKit::Bubble (+ Content / Reactions / Group).
|
|
2
|
+
Matched to shadcn/ui's current bubble (nova). The variant colours the child
|
|
3
|
+
[data-slot=bubble-content]; hover fills apply only when the content itself
|
|
4
|
+
is a <button>/<a> (BubbleContent as: :button / :a). tinted derives its fill
|
|
5
|
+
from the brand color via relative oklch — lightness pinned per theme.
|
|
6
|
+
ghost is frameless and full-width (assistant prose). */
|
|
3
7
|
.pk-bubble {
|
|
4
8
|
position: relative; display: flex; width: fit-content; min-width: 0;
|
|
5
9
|
flex-direction: column; gap: .25rem; max-width: 80%;
|
|
6
10
|
}
|
|
7
11
|
.pk-bubble[data-align="end"] { align-self: flex-end; }
|
|
12
|
+
.pk-bubble[data-variant="ghost"] { max-width: 100%; border: none; }
|
|
13
|
+
/* Inside a Message row aligned to the end, bubbles follow. */
|
|
14
|
+
.pk-message[data-align="end"] .pk-bubble { align-self: flex-end; }
|
|
8
15
|
.pk-bubble-content {
|
|
9
16
|
width: fit-content; max-width: 100%; min-width: 0; overflow: hidden;
|
|
10
|
-
overflow-wrap: break-word; border: 1px solid transparent; border-radius:
|
|
11
|
-
padding: .
|
|
17
|
+
overflow-wrap: break-word; border: 1px solid transparent; border-radius: .75rem;
|
|
18
|
+
padding: .5rem .75rem; font-size: .875rem; line-height: 1.625;
|
|
12
19
|
}
|
|
13
20
|
.pk-bubble[data-align="end"] > .pk-bubble-content { align-self: flex-end; }
|
|
14
|
-
|
|
21
|
+
button.pk-bubble-content { text-align: left; font: inherit; }
|
|
22
|
+
a.pk-bubble-content { color: inherit; text-decoration: none; }
|
|
23
|
+
button.pk-bubble-content, a.pk-bubble-content {
|
|
24
|
+
cursor: pointer; outline: none;
|
|
25
|
+
transition: color .15s ease, background-color .15s ease;
|
|
26
|
+
}
|
|
27
|
+
:is(button, a).pk-bubble-content:focus-visible {
|
|
28
|
+
border-color: var(--pk-ring);
|
|
29
|
+
box-shadow: 0 0 0 3px color-mix(in oklab, var(--pk-ring) 50%, transparent);
|
|
30
|
+
}
|
|
31
|
+
/* variants (dark-first; light overrides below) */
|
|
15
32
|
.pk-bubble[data-variant="default"] > .pk-bubble-content { background: var(--pk-brand); color: var(--pk-brand-ink); }
|
|
33
|
+
.pk-bubble[data-variant="default"] > :is(button, a).pk-bubble-content:hover { background: color-mix(in oklab, var(--pk-brand) 80%, transparent); }
|
|
16
34
|
.pk-bubble[data-variant="secondary"] > .pk-bubble-content { background: var(--pk-surface-2); color: var(--pk-text); }
|
|
17
|
-
.pk-bubble[data-variant="
|
|
18
|
-
.pk-bubble[data-variant="
|
|
35
|
+
.pk-bubble[data-variant="secondary"] > :is(button, a).pk-bubble-content:hover { background: color-mix(in oklch, var(--pk-surface-2), var(--pk-text) 5%); }
|
|
36
|
+
.pk-bubble[data-variant="muted"] > .pk-bubble-content { background: var(--pk-surface-2); }
|
|
37
|
+
.pk-bubble[data-variant="muted"] > :is(button, a).pk-bubble-content:hover { background: color-mix(in oklch, var(--pk-surface-2), var(--pk-text) 5%); }
|
|
38
|
+
.pk-bubble[data-variant="tinted"] > .pk-bubble-content { background: oklch(from var(--pk-brand) 0.3 calc(c * 0.4) h); color: var(--pk-text); }
|
|
39
|
+
.pk-bubble[data-variant="tinted"] > :is(button, a).pk-bubble-content:hover { background: oklch(from var(--pk-brand) 0.35 calc(c * 0.5) h); }
|
|
19
40
|
.pk-bubble[data-variant="outline"] > .pk-bubble-content { background: var(--pk-bg); border-color: var(--pk-border); color: var(--pk-text); }
|
|
41
|
+
.pk-bubble[data-variant="outline"] > :is(button, a).pk-bubble-content:hover { background: color-mix(in oklab, var(--pk-input) 30%, transparent); color: var(--pk-text); }
|
|
20
42
|
.pk-bubble[data-variant="ghost"] > .pk-bubble-content { background: transparent; border-radius: 0; padding: 0; }
|
|
21
|
-
.pk-bubble[data-variant="
|
|
43
|
+
.pk-bubble[data-variant="ghost"] > :is(button, a).pk-bubble-content:hover { background: color-mix(in oklab, var(--pk-surface-2) 50%, transparent); color: var(--pk-text); }
|
|
44
|
+
.pk-bubble[data-variant="destructive"] > .pk-bubble-content { background: color-mix(in oklab, var(--pk-red) 20%, transparent); color: var(--pk-red); }
|
|
45
|
+
.pk-bubble[data-variant="destructive"] > :is(button, a).pk-bubble-content:hover { background: color-mix(in oklab, var(--pk-red) 30%, transparent); }
|
|
46
|
+
|
|
47
|
+
:root[data-theme="light"] .pk-bubble[data-variant="tinted"] > .pk-bubble-content { background: oklch(from var(--pk-brand) 0.93 calc(c * 0.4) h); }
|
|
48
|
+
:root[data-theme="light"] .pk-bubble[data-variant="tinted"] > :is(button, a).pk-bubble-content:hover { background: oklch(from var(--pk-brand) 0.88 calc(c * 0.5) h); }
|
|
49
|
+
:root[data-theme="light"] .pk-bubble[data-variant="outline"] > :is(button, a).pk-bubble-content:hover { background: var(--pk-surface-2); }
|
|
50
|
+
:root[data-theme="light"] .pk-bubble[data-variant="ghost"] > :is(button, a).pk-bubble-content:hover { background: var(--pk-surface-2); }
|
|
51
|
+
:root[data-theme="light"] .pk-bubble[data-variant="destructive"] > .pk-bubble-content { background: color-mix(in oklab, var(--pk-red) 10%, transparent); }
|
|
52
|
+
:root[data-theme="light"] .pk-bubble[data-variant="destructive"] > :is(button, a).pk-bubble-content:hover { background: color-mix(in oklab, var(--pk-red) 20%, transparent); }
|
|
53
|
+
@media (prefers-color-scheme: light) {
|
|
54
|
+
:root[data-theme="system"] .pk-bubble[data-variant="tinted"] > .pk-bubble-content { background: oklch(from var(--pk-brand) 0.93 calc(c * 0.4) h); }
|
|
55
|
+
:root[data-theme="system"] .pk-bubble[data-variant="tinted"] > :is(button, a).pk-bubble-content:hover { background: oklch(from var(--pk-brand) 0.88 calc(c * 0.5) h); }
|
|
56
|
+
:root[data-theme="system"] .pk-bubble[data-variant="outline"] > :is(button, a).pk-bubble-content:hover { background: var(--pk-surface-2); }
|
|
57
|
+
:root[data-theme="system"] .pk-bubble[data-variant="ghost"] > :is(button, a).pk-bubble-content:hover { background: var(--pk-surface-2); }
|
|
58
|
+
:root[data-theme="system"] .pk-bubble[data-variant="destructive"] > .pk-bubble-content { background: color-mix(in oklab, var(--pk-red) 10%, transparent); }
|
|
59
|
+
:root[data-theme="system"] .pk-bubble[data-variant="destructive"] > :is(button, a).pk-bubble-content:hover { background: color-mix(in oklab, var(--pk-red) 20%, transparent); }
|
|
60
|
+
}
|
|
61
|
+
|
|
22
62
|
.pk-bubble-group { display: flex; min-width: 0; flex-direction: column; gap: .5rem; }
|
|
23
63
|
.pk-bubble-reactions {
|
|
24
64
|
position: absolute; z-index: 10; display: flex; width: fit-content;
|
|
@@ -26,6 +66,7 @@
|
|
|
26
66
|
box-shadow: 0 0 0 3px var(--pk-surface); background: var(--pk-surface-2);
|
|
27
67
|
flex: none; gap: .25rem; padding: .125rem .375rem; font-size: .875rem;
|
|
28
68
|
}
|
|
69
|
+
.pk-bubble-reactions:has(button) { padding: 0; }
|
|
29
70
|
.pk-bubble-reactions[data-side="bottom"] { bottom: 0; transform: translateY(75%); }
|
|
30
71
|
.pk-bubble-reactions[data-side="top"] { top: 0; transform: translateY(-75%); }
|
|
31
72
|
.pk-bubble-reactions[data-align="end"] { right: .75rem; }
|
|
@@ -1,66 +1,96 @@
|
|
|
1
|
-
/* Co-located with button.rb —
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/* Co-located with button.rb — PhlexKit::Button. Matched to shadcn/ui's current
|
|
2
|
+
button (nova): h-8 default, tinted destructive (no more solid red), theme-
|
|
3
|
+
forked outline (translucent input fill in dark, plain bordered in light),
|
|
4
|
+
active nudge, per-size radius caps, data-icon inset tightening, svg-4
|
|
5
|
+
glyphs. Hover fills; aria-expanded holds the open state for menu triggers.
|
|
6
|
+
xl is a kit extra. Dark-first; light overrides at the bottom. */
|
|
5
7
|
.pk-button {
|
|
6
8
|
display: inline-flex;
|
|
7
9
|
align-items: center;
|
|
8
10
|
justify-content: center;
|
|
11
|
+
flex-shrink: 0;
|
|
9
12
|
white-space: nowrap;
|
|
13
|
+
user-select: none;
|
|
10
14
|
gap: .375rem;
|
|
11
15
|
height: 2rem;
|
|
12
16
|
padding: .25rem .625rem;
|
|
13
17
|
border: 1px solid transparent;
|
|
14
18
|
border-radius: var(--pk-radius);
|
|
19
|
+
background-clip: padding-box;
|
|
15
20
|
font: inherit;
|
|
16
21
|
font-size: .875rem;
|
|
17
22
|
font-weight: 500;
|
|
18
23
|
cursor: pointer;
|
|
19
|
-
|
|
24
|
+
text-decoration: none;
|
|
25
|
+
transition: background-color .15s ease, color .15s ease, border-color .15s ease, box-shadow .15s ease, transform .15s ease;
|
|
20
26
|
}
|
|
21
27
|
.pk-button:focus-visible {
|
|
22
28
|
outline: none;
|
|
29
|
+
border-color: var(--pk-ring);
|
|
23
30
|
box-shadow: 0 0 0 3px color-mix(in oklab, var(--pk-ring) 50%, transparent);
|
|
24
31
|
}
|
|
32
|
+
.pk-button:active:not([aria-haspopup]) { transform: translateY(1px); }
|
|
25
33
|
.pk-button:disabled, .pk-button[disabled], .pk-button[aria-disabled="true"] {
|
|
26
34
|
pointer-events: none;
|
|
27
35
|
opacity: .5;
|
|
28
36
|
}
|
|
37
|
+
/* Glyphs default to size-4; explicit width attrs (Icon size:) win. */
|
|
38
|
+
.pk-button svg { flex-shrink: 0; pointer-events: none; }
|
|
39
|
+
.pk-button svg:not([width]) { width: 1rem; height: 1rem; }
|
|
40
|
+
.pk-button:has(> [data-icon="inline-start"]) { padding-left: .5rem; }
|
|
41
|
+
.pk-button:has(> [data-icon="inline-end"]) { padding-right: .5rem; }
|
|
29
42
|
|
|
30
43
|
/* Sizes (md is the base above). */
|
|
31
|
-
.pk-button.
|
|
32
|
-
|
|
44
|
+
.pk-button.xs {
|
|
45
|
+
height: 1.5rem; gap: .25rem; padding: .125rem .5rem; font-size: .75rem;
|
|
46
|
+
border-radius: min(calc(var(--pk-radius) - 2px), 10px);
|
|
47
|
+
}
|
|
48
|
+
.pk-button.xs svg:not([width]) { width: .75rem; height: .75rem; }
|
|
49
|
+
.pk-button.xs:has(> [data-icon="inline-start"]) { padding-left: .375rem; }
|
|
50
|
+
.pk-button.xs:has(> [data-icon="inline-end"]) { padding-right: .375rem; }
|
|
51
|
+
.pk-button.sm {
|
|
52
|
+
height: 1.75rem; gap: .25rem; padding: .25rem .625rem; font-size: .8rem;
|
|
53
|
+
border-radius: min(calc(var(--pk-radius) - 2px), 12px);
|
|
54
|
+
}
|
|
55
|
+
.pk-button.sm svg:not([width]) { width: .875rem; height: .875rem; }
|
|
56
|
+
.pk-button.sm:has(> [data-icon="inline-start"]) { padding-left: .375rem; }
|
|
57
|
+
.pk-button.sm:has(> [data-icon="inline-end"]) { padding-right: .375rem; }
|
|
58
|
+
.pk-button.lg { height: 2.25rem; padding: .375rem .625rem; }
|
|
33
59
|
.pk-button.xl { height: 3rem; padding: .625rem 1.5rem; font-size: 1rem; }
|
|
34
60
|
|
|
35
|
-
/* Icon-only square buttons. */
|
|
61
|
+
/* Icon-only square buttons (their icon / icon-xs / icon-sm / icon-lg). */
|
|
36
62
|
.pk-button.icon { width: 2rem; padding: 0; }
|
|
63
|
+
.pk-button.icon.xs { width: 1.5rem; }
|
|
37
64
|
.pk-button.icon.sm { width: 1.75rem; }
|
|
38
|
-
.pk-button.icon.lg { width: 2.
|
|
65
|
+
.pk-button.icon.lg { width: 2.25rem; }
|
|
39
66
|
.pk-button.icon.xl { width: 3rem; }
|
|
40
67
|
|
|
41
|
-
/* Variants. */
|
|
42
|
-
.pk-button.primary {
|
|
43
|
-
|
|
44
|
-
color: var(--pk-brand-ink);
|
|
45
|
-
border-color: var(--pk-brand);
|
|
46
|
-
}
|
|
47
|
-
.pk-button.primary:hover { background: color-mix(in oklab, var(--pk-brand) 90%, transparent); border-color: transparent; }
|
|
68
|
+
/* Variants (dark-first; light overrides below). */
|
|
69
|
+
.pk-button.primary { background: var(--pk-brand); color: var(--pk-brand-ink); }
|
|
70
|
+
.pk-button.primary:hover { background: color-mix(in oklab, var(--pk-brand) 80%, transparent); }
|
|
48
71
|
|
|
49
72
|
.pk-button.secondary { background: var(--pk-surface-2); color: var(--pk-text); }
|
|
50
|
-
.pk-button.secondary:hover { background: color-mix(in
|
|
73
|
+
.pk-button.secondary:hover { background: color-mix(in oklch, var(--pk-surface-2), var(--pk-text) 5%); }
|
|
74
|
+
.pk-button.secondary[aria-expanded="true"] { background: var(--pk-surface-2); color: var(--pk-text); }
|
|
51
75
|
|
|
52
|
-
.pk-button.destructive {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
border-color: var(--pk-red);
|
|
76
|
+
.pk-button.destructive { background: color-mix(in oklab, var(--pk-red) 20%, transparent); color: var(--pk-red); }
|
|
77
|
+
.pk-button.destructive:hover { background: color-mix(in oklab, var(--pk-red) 30%, transparent); }
|
|
78
|
+
.pk-button.destructive:focus-visible {
|
|
79
|
+
border-color: color-mix(in oklab, var(--pk-red) 40%, transparent);
|
|
80
|
+
box-shadow: 0 0 0 3px color-mix(in oklab, var(--pk-red) 40%, transparent);
|
|
56
81
|
}
|
|
57
|
-
.pk-button.destructive:hover { background: color-mix(in oklab, var(--pk-red) 88%, black); }
|
|
58
82
|
|
|
59
|
-
.pk-button.outline {
|
|
60
|
-
|
|
83
|
+
.pk-button.outline {
|
|
84
|
+
background: color-mix(in oklab, var(--pk-input) 30%, transparent);
|
|
85
|
+
color: var(--pk-text);
|
|
86
|
+
border-color: var(--pk-input);
|
|
87
|
+
}
|
|
88
|
+
.pk-button.outline:hover { background: color-mix(in oklab, var(--pk-input) 50%, transparent); }
|
|
89
|
+
.pk-button.outline[aria-expanded="true"] { background: var(--pk-surface-2); color: var(--pk-text); }
|
|
61
90
|
|
|
62
91
|
.pk-button.ghost { background: transparent; border-color: transparent; color: var(--pk-text); }
|
|
63
|
-
.pk-button.ghost:hover { background: var(--pk-
|
|
92
|
+
.pk-button.ghost:hover { background: color-mix(in oklab, var(--pk-surface-2) 50%, transparent); color: var(--pk-text); }
|
|
93
|
+
.pk-button.ghost[aria-expanded="true"] { background: var(--pk-surface-2); color: var(--pk-text); }
|
|
64
94
|
|
|
65
95
|
.pk-button.link {
|
|
66
96
|
height: auto;
|
|
@@ -69,4 +99,19 @@
|
|
|
69
99
|
border-color: transparent;
|
|
70
100
|
color: var(--pk-brand);
|
|
71
101
|
}
|
|
72
|
-
.pk-button.link:hover { text-decoration: underline; }
|
|
102
|
+
.pk-button.link:hover { text-decoration: underline; text-underline-offset: 4px; }
|
|
103
|
+
|
|
104
|
+
:root[data-theme="light"] .pk-button.outline { background: var(--pk-bg); border-color: var(--pk-border); }
|
|
105
|
+
:root[data-theme="light"] .pk-button.outline:hover { background: var(--pk-surface-2); color: var(--pk-text); }
|
|
106
|
+
:root[data-theme="light"] .pk-button.ghost:hover { background: var(--pk-surface-2); }
|
|
107
|
+
:root[data-theme="light"] .pk-button.destructive { background: color-mix(in oklab, var(--pk-red) 10%, transparent); }
|
|
108
|
+
:root[data-theme="light"] .pk-button.destructive:hover { background: color-mix(in oklab, var(--pk-red) 20%, transparent); }
|
|
109
|
+
:root[data-theme="light"] .pk-button.destructive:focus-visible { box-shadow: 0 0 0 3px color-mix(in oklab, var(--pk-red) 20%, transparent); }
|
|
110
|
+
@media (prefers-color-scheme: light) {
|
|
111
|
+
:root[data-theme="system"] .pk-button.outline { background: var(--pk-bg); border-color: var(--pk-border); }
|
|
112
|
+
:root[data-theme="system"] .pk-button.outline:hover { background: var(--pk-surface-2); color: var(--pk-text); }
|
|
113
|
+
:root[data-theme="system"] .pk-button.ghost:hover { background: var(--pk-surface-2); }
|
|
114
|
+
:root[data-theme="system"] .pk-button.destructive { background: color-mix(in oklab, var(--pk-red) 10%, transparent); }
|
|
115
|
+
:root[data-theme="system"] .pk-button.destructive:hover { background: color-mix(in oklab, var(--pk-red) 20%, transparent); }
|
|
116
|
+
:root[data-theme="system"] .pk-button.destructive:focus-visible { box-shadow: 0 0 0 3px color-mix(in oklab, var(--pk-red) 20%, transparent); }
|
|
117
|
+
}
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
module PhlexKit
|
|
2
|
-
# Button, ported from ruby_ui's RubyUI::Button
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
2
|
+
# Button, ported from ruby_ui's RubyUI::Button and matched to shadcn/ui's
|
|
3
|
+
# current button. Renders a <button>, or an <a> when `href:` is given
|
|
4
|
+
# (their asChild link button). The `mix` pass-through means a caller's
|
|
5
|
+
# `class:` augments ours and a phlex-reactive `**on(:event)` bundle flows
|
|
6
|
+
# straight onto the element:
|
|
6
7
|
#
|
|
7
8
|
# render PhlexKit::Button.new(variant: :primary, **on(:publish)) { "Approve" }
|
|
9
|
+
# render PhlexKit::Button.new(href: "/login") { "Login" }
|
|
8
10
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
11
|
+
# Mark leading/trailing glyphs with data-icon: "inline-start"/"inline-end"
|
|
12
|
+
# to tighten the near-side padding like theirs. `icon: true` makes the
|
|
13
|
+
# button square (their icon/icon-xs/… sizes — compose with `size:`).
|
|
14
|
+
# :primary is their :default; :xl is a kit extra. `VARIANTS.fetch`/
|
|
15
|
+
# `SIZES.fetch` fail loud on a bad value.
|
|
14
16
|
class Button < BaseComponent
|
|
15
|
-
# variant => modifier class. Default is :primary (the filled brand button)
|
|
16
|
-
# matching ruby_ui.
|
|
17
|
+
# variant => modifier class. Default is :primary (the filled brand button).
|
|
17
18
|
VARIANTS = {
|
|
18
19
|
primary: "primary",
|
|
19
20
|
secondary: "secondary",
|
|
@@ -24,22 +25,28 @@ module PhlexKit
|
|
|
24
25
|
}.freeze
|
|
25
26
|
|
|
26
27
|
SIZES = {
|
|
28
|
+
xs: "xs",
|
|
27
29
|
sm: "sm",
|
|
28
30
|
md: nil,
|
|
29
31
|
lg: "lg",
|
|
30
32
|
xl: "xl"
|
|
31
33
|
}.freeze
|
|
32
34
|
|
|
33
|
-
def initialize(variant: :primary, size: :md, type: :button, icon: false, **attrs)
|
|
35
|
+
def initialize(variant: :primary, size: :md, type: :button, icon: false, href: nil, **attrs)
|
|
34
36
|
@variant = variant.to_sym
|
|
35
37
|
@size = size.to_sym
|
|
36
38
|
@type = type
|
|
37
39
|
@icon = icon
|
|
40
|
+
@href = href
|
|
38
41
|
@attrs = attrs
|
|
39
42
|
end
|
|
40
43
|
|
|
41
44
|
def view_template(&block)
|
|
42
|
-
|
|
45
|
+
if @href
|
|
46
|
+
a(**mix({ href: @href, class: classes }, @attrs), &block)
|
|
47
|
+
else
|
|
48
|
+
button(**mix({ type: @type, class: classes }, @attrs), &block)
|
|
49
|
+
end
|
|
43
50
|
end
|
|
44
51
|
|
|
45
52
|
private
|
|
@@ -1,8 +1,59 @@
|
|
|
1
|
-
/* Co-located with button_group.rb —
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
.pk-button-group
|
|
1
|
+
/* Co-located with button_group.rb — PhlexKit::ButtonGroup (+ Text /
|
|
2
|
+
Separator). Matched to shadcn/ui's current button-group: children keep
|
|
3
|
+
their OWN outer corners and only lose the inner ones (so sm buttons keep
|
|
4
|
+
their smaller radius), shared edges drop the trailing border. Nested
|
|
5
|
+
groups separate with a gap and stay independently rounded. The separator
|
|
6
|
+
is a hairline for same-fill segments (secondary/primary) where no border
|
|
7
|
+
exists to divide them. */
|
|
8
|
+
.pk-button-group { display: flex; width: fit-content; align-items: stretch; }
|
|
9
|
+
.pk-button-group:has(> .pk-button-group) { gap: .5rem; }
|
|
10
|
+
.pk-button-group > :focus-visible { position: relative; z-index: 10; }
|
|
11
|
+
.pk-button-group > input { flex: 1; }
|
|
12
|
+
|
|
13
|
+
/* horizontal (default) */
|
|
14
|
+
.pk-button-group:not(.vertical) > :not(:first-child) {
|
|
15
|
+
border-top-left-radius: 0;
|
|
16
|
+
border-bottom-left-radius: 0;
|
|
17
|
+
border-left: 0;
|
|
18
|
+
}
|
|
19
|
+
.pk-button-group:not(.vertical) > :not(:last-child) {
|
|
20
|
+
border-top-right-radius: 0;
|
|
21
|
+
border-bottom-right-radius: 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* vertical */
|
|
25
|
+
.pk-button-group.vertical { flex-direction: column; }
|
|
26
|
+
.pk-button-group.vertical > :not(:first-child) {
|
|
27
|
+
border-top-left-radius: 0;
|
|
28
|
+
border-top-right-radius: 0;
|
|
29
|
+
border-top: 0;
|
|
30
|
+
}
|
|
31
|
+
.pk-button-group.vertical > :not(:last-child) {
|
|
32
|
+
border-bottom-left-radius: 0;
|
|
33
|
+
border-bottom-right-radius: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Nested groups keep their own corners — undo the squaring the parent applied. */
|
|
37
|
+
.pk-button-group > .pk-button-group { border-radius: 0; border: 0; }
|
|
38
|
+
|
|
39
|
+
.pk-button-group-text {
|
|
40
|
+
display: flex;
|
|
41
|
+
align-items: center;
|
|
42
|
+
gap: .5rem;
|
|
43
|
+
background: var(--pk-surface-2);
|
|
44
|
+
border: 1px solid var(--pk-border);
|
|
45
|
+
border-radius: var(--pk-radius);
|
|
46
|
+
padding: 0 .625rem;
|
|
47
|
+
font-size: .875rem;
|
|
48
|
+
font-weight: 500;
|
|
49
|
+
}
|
|
50
|
+
.pk-button-group-text svg { pointer-events: none; }
|
|
51
|
+
.pk-button-group-text svg:not([width]) { width: 1rem; height: 1rem; }
|
|
52
|
+
|
|
53
|
+
.pk-button-group-separator {
|
|
54
|
+
position: relative;
|
|
55
|
+
align-self: stretch;
|
|
56
|
+
background: var(--pk-input);
|
|
57
|
+
}
|
|
58
|
+
.pk-button-group:not(.vertical) > .pk-button-group-separator { width: 1px; margin: 0 1px; }
|
|
59
|
+
.pk-button-group.vertical > .pk-button-group-separator { height: 1px; margin: 1px 0; }
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
module PhlexKit
|
|
2
|
-
# Joined row of buttons, ported from shadcn/ui's ButtonGroup:
|
|
3
|
-
# their inner corners/double borders and read as one segmented
|
|
4
|
-
#
|
|
2
|
+
# Joined row (or column) of buttons, ported from shadcn/ui's ButtonGroup:
|
|
3
|
+
# children lose their inner corners/double borders and read as one segmented
|
|
4
|
+
# control. `orientation: :vertical` stacks. Nest groups to separate segments
|
|
5
|
+
# with a gap; add ButtonGroupText for static labels and ButtonGroupSeparator
|
|
6
|
+
# between same-fill buttons. `.pk-button-group` (button_group.css).
|
|
5
7
|
class ButtonGroup < BaseComponent
|
|
6
|
-
|
|
8
|
+
ORIENTATIONS = { horizontal: nil, vertical: "vertical" }.freeze
|
|
9
|
+
|
|
10
|
+
def initialize(orientation: :horizontal, **attrs)
|
|
11
|
+
@orientation = orientation.to_sym
|
|
7
12
|
@attrs = attrs
|
|
8
13
|
end
|
|
9
14
|
|
|
10
15
|
def view_template(&)
|
|
11
|
-
|
|
16
|
+
classes = [ "pk-button-group", ORIENTATIONS.fetch(@orientation) ].compact.join(" ")
|
|
17
|
+
div(**mix({ class: classes, role: "group" }, @attrs), &)
|
|
12
18
|
end
|
|
13
19
|
end
|
|
14
20
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Hairline divider between same-fill segments of a PhlexKit::ButtonGroup,
|
|
3
|
+
# ported from shadcn/ui's ButtonGroupSeparator. Runs perpendicular to the
|
|
4
|
+
# group's orientation automatically (button_group.css). See button_group.rb.
|
|
5
|
+
class ButtonGroupSeparator < BaseComponent
|
|
6
|
+
def initialize(**attrs)
|
|
7
|
+
@attrs = attrs
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def view_template
|
|
11
|
+
div(**mix({ class: "pk-button-group-separator", role: "separator", aria: { hidden: true } }, @attrs))
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Static text segment inside a PhlexKit::ButtonGroup, ported from shadcn/ui's
|
|
3
|
+
# ButtonGroupText — a muted, bordered cell that joins the group like a
|
|
4
|
+
# button but isn't interactive. See button_group.rb.
|
|
5
|
+
class ButtonGroupText < BaseComponent
|
|
6
|
+
def initialize(**attrs)
|
|
7
|
+
@attrs = attrs
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def view_template(&block)
|
|
11
|
+
div(**mix({ class: "pk-button-group-text" }, @attrs), &block)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
/* Co-located with calendar.rb —
|
|
2
|
-
kept (the controller renders week rows from <template>s; its
|
|
3
|
-
dependency replaced with a tiny interpolator);
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
/* Co-located with calendar.rb — PhlexKit::Calendar. ruby_ui's structure +
|
|
2
|
+
Stimulus kept (the controller renders week rows from <template>s; its
|
|
3
|
+
Mustache dependency replaced with a tiny interpolator); extended to
|
|
4
|
+
shadcn's current surface. Sizing rides nova's variable system:
|
|
5
|
+
--pk-cell-size (default 2rem) and --pk-cell-radius drive every day cell —
|
|
6
|
+
the Custom Cell Size example just overrides --pk-cell-size inline. Range
|
|
7
|
+
days paint a contiguous accent band (in-range cells square off; the
|
|
8
|
+
start/end caps keep their outer radius). Booked (explicitly disabled)
|
|
9
|
+
days strike through at full opacity, like theirs. */
|
|
7
10
|
.pk-calendar {
|
|
11
|
+
--pk-cell-size: 2rem;
|
|
12
|
+
--pk-cell-radius: calc(var(--pk-radius) - 2px);
|
|
8
13
|
display: inline-flex;
|
|
9
14
|
flex-direction: column;
|
|
10
15
|
gap: 1rem;
|
|
@@ -20,6 +25,18 @@
|
|
|
20
25
|
}
|
|
21
26
|
.pk-calendar-title { font-size: .875rem; font-weight: 500; }
|
|
22
27
|
|
|
28
|
+
/* Month/year dropdown caption (caption_layout: :dropdown) — native selects
|
|
29
|
+
restyled to their compact h-6 caption controls. */
|
|
30
|
+
.pk-calendar-dropdowns { display: flex; align-items: center; gap: .25rem; }
|
|
31
|
+
.pk-native-select-field.pk-calendar-dropdown {
|
|
32
|
+
width: auto;
|
|
33
|
+
height: 1.5rem;
|
|
34
|
+
padding: 0 .25rem 0 .375rem;
|
|
35
|
+
font-size: .875rem;
|
|
36
|
+
font-weight: 500;
|
|
37
|
+
border-color: var(--pk-input);
|
|
38
|
+
}
|
|
39
|
+
|
|
23
40
|
.pk-calendar-prev, .pk-calendar-next {
|
|
24
41
|
position: absolute;
|
|
25
42
|
display: inline-flex;
|
|
@@ -50,23 +67,39 @@
|
|
|
50
67
|
.pk-calendar-weekdays-row { display: flex; }
|
|
51
68
|
.pk-calendar-week { display: flex; width: 100%; margin-top: .5rem; }
|
|
52
69
|
.pk-calendar-weekday {
|
|
53
|
-
|
|
54
|
-
|
|
70
|
+
flex: none;
|
|
71
|
+
width: var(--pk-cell-size);
|
|
72
|
+
border-radius: var(--pk-cell-radius);
|
|
55
73
|
font-size: .8rem;
|
|
56
74
|
font-weight: 400;
|
|
57
75
|
color: var(--pk-muted);
|
|
58
76
|
}
|
|
77
|
+
.pk-calendar-weeknumber,
|
|
78
|
+
.pk-calendar-weeknumber-head {
|
|
79
|
+
flex: none;
|
|
80
|
+
display: inline-flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
justify-content: center;
|
|
83
|
+
width: var(--pk-cell-size);
|
|
84
|
+
height: var(--pk-cell-size);
|
|
85
|
+
font-size: .75rem;
|
|
86
|
+
font-weight: 400;
|
|
87
|
+
color: var(--pk-muted);
|
|
88
|
+
}
|
|
89
|
+
.pk-calendar-weeknumber-head { height: auto; }
|
|
59
90
|
|
|
60
91
|
.pk-calendar-cell {
|
|
61
92
|
position: relative;
|
|
93
|
+
display: inline-flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
justify-content: center;
|
|
96
|
+
flex: none;
|
|
97
|
+
width: var(--pk-cell-size);
|
|
98
|
+
height: var(--pk-cell-size);
|
|
62
99
|
padding: 0;
|
|
63
100
|
text-align: center;
|
|
64
101
|
font-size: .875rem;
|
|
65
102
|
}
|
|
66
|
-
.pk-calendar-cell:has([aria-selected]) {
|
|
67
|
-
background: var(--pk-accent);
|
|
68
|
-
border-radius: calc(var(--pk-radius) - 2px);
|
|
69
|
-
}
|
|
70
103
|
.pk-calendar-cell:focus-within { position: relative; z-index: 20; }
|
|
71
104
|
|
|
72
105
|
/* Day button states (base = current month). */
|
|
@@ -74,11 +107,11 @@
|
|
|
74
107
|
display: inline-flex;
|
|
75
108
|
align-items: center;
|
|
76
109
|
justify-content: center;
|
|
77
|
-
width:
|
|
78
|
-
height:
|
|
110
|
+
width: var(--pk-cell-size);
|
|
111
|
+
height: var(--pk-cell-size);
|
|
79
112
|
padding: 0;
|
|
80
113
|
border: 0;
|
|
81
|
-
border-radius:
|
|
114
|
+
border-radius: var(--pk-cell-radius);
|
|
82
115
|
background: transparent;
|
|
83
116
|
color: var(--pk-text);
|
|
84
117
|
font: inherit;
|
|
@@ -107,3 +140,21 @@
|
|
|
107
140
|
color: var(--pk-muted);
|
|
108
141
|
opacity: .5;
|
|
109
142
|
}
|
|
143
|
+
/* Booked: explicitly blocked dates keep full opacity, struck through. */
|
|
144
|
+
.pk-calendar-day.booked {
|
|
145
|
+
text-decoration: line-through;
|
|
146
|
+
opacity: 1;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Range selection: a contiguous accent band between the brand-filled caps. */
|
|
150
|
+
.pk-calendar-cell:has(> .in-range) { background: var(--pk-accent); }
|
|
151
|
+
.pk-calendar-day.in-range {
|
|
152
|
+
width: 100%;
|
|
153
|
+
height: 100%;
|
|
154
|
+
border-radius: 0;
|
|
155
|
+
background: var(--pk-accent);
|
|
156
|
+
}
|
|
157
|
+
.pk-calendar-cell:has(> .range-cap) { background: var(--pk-accent); }
|
|
158
|
+
.pk-calendar-cell:has(> .range-start.range-cap) { border-radius: var(--pk-cell-radius) 0 0 var(--pk-cell-radius); }
|
|
159
|
+
.pk-calendar-cell:has(> .range-end.range-cap) { border-radius: 0 var(--pk-cell-radius) var(--pk-cell-radius) 0; }
|
|
160
|
+
.pk-calendar-cell:has(> .range-start.range-end) { border-radius: var(--pk-cell-radius); }
|