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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 71cbb9b8727b67b80fe0bcbd69fb4eb3dd298529cd3ec43248c770df56c1554b
|
|
4
|
+
data.tar.gz: fa3abdc391af56d26491645a262a8d56657bf2ae21996370e82a7e0cc4704776
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 064253644752dac989e4585356a8896ce35a4ca8ec79eee58c603c6b807e3203ae5c64bd3ccc6d2e9340007af49a85488e9f4acf3147d1f7fd76680afd54103e
|
|
7
|
+
data.tar.gz: 6687d41814334c237479b7bdfbaa6e47f61c06a6c00505efeb218b62a7f2e1926b2b7441bd95b5d5216fe56491a2b03db82ff9c8cf613b9954954ca89d269c6b
|
|
@@ -63,6 +63,22 @@
|
|
|
63
63
|
mask-image: linear-gradient(to bottom, transparent 0, black var(--pk-scroll-fade-size));
|
|
64
64
|
}
|
|
65
65
|
.pk-scroll-fade[data-at-start][data-at-end] { mask-image: none; }
|
|
66
|
+
/* Horizontal mode (pair with the controller's axis value "x"). */
|
|
67
|
+
.pk-scroll-fade.horizontal {
|
|
68
|
+
overflow-y: visible;
|
|
69
|
+
overflow-x: auto;
|
|
70
|
+
mask-image: linear-gradient(to right,
|
|
71
|
+
transparent 0, black var(--pk-scroll-fade-size),
|
|
72
|
+
black calc(100% - var(--pk-scroll-fade-size)), transparent 100%);
|
|
73
|
+
}
|
|
74
|
+
.pk-scroll-fade.horizontal[data-at-start] {
|
|
75
|
+
mask-image: linear-gradient(to right,
|
|
76
|
+
black calc(100% - var(--pk-scroll-fade-size)), transparent 100%);
|
|
77
|
+
}
|
|
78
|
+
.pk-scroll-fade.horizontal[data-at-end] {
|
|
79
|
+
mask-image: linear-gradient(to right, transparent 0, black var(--pk-scroll-fade-size));
|
|
80
|
+
}
|
|
81
|
+
.pk-scroll-fade.horizontal[data-at-start][data-at-end] { mask-image: none; }
|
|
66
82
|
|
|
67
83
|
/* Kit elements size predictably: width includes padding + border, so
|
|
68
84
|
`width: 100%` controls (inputs, triggers, option rows) never overflow their
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
@import url("drawer/drawer.css");
|
|
41
41
|
@import url("dropdown_menu/dropdown_menu.css");
|
|
42
42
|
@import url("empty/empty.css");
|
|
43
|
+
@import url("field/field.css");
|
|
43
44
|
@import url("form/form.css");
|
|
44
45
|
@import url("form_field/form_field.css");
|
|
45
46
|
@import url("hover_card/hover_card.css");
|
|
@@ -1,32 +1,70 @@
|
|
|
1
|
-
/* Co-located with alert.rb —
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
/* Co-located with alert.rb — PhlexKit::Alert (+ Title / Description / Action).
|
|
2
|
+
Matched to shadcn/ui's current alert (nova): a grid callout on the card
|
|
3
|
+
surface. A leading `> svg` child switches on the two-column icon layout
|
|
4
|
+
(icon spans both rows); an AlertAction pins to the top-right corner and
|
|
5
|
+
reserves right padding on the box. Variants tint the TEXT only — the
|
|
6
|
+
background stays the card surface (shadcn dropped the tinted-fill look).
|
|
7
|
+
success/warning are kit extras (ruby_ui heritage) in the same grammar. */
|
|
6
8
|
.pk-alert {
|
|
7
|
-
|
|
9
|
+
display: grid;
|
|
10
|
+
gap: .125rem;
|
|
8
11
|
width: 100%;
|
|
9
12
|
border: 1px solid var(--pk-border);
|
|
10
13
|
border-radius: var(--pk-radius);
|
|
11
|
-
padding: .
|
|
14
|
+
padding: .5rem .625rem;
|
|
12
15
|
font-size: .875rem;
|
|
13
|
-
|
|
16
|
+
line-height: 1.25rem;
|
|
17
|
+
text-align: left;
|
|
18
|
+
background: var(--pk-surface);
|
|
14
19
|
color: var(--pk-text);
|
|
15
20
|
}
|
|
16
|
-
.pk-alert.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
.pk-alert:has(> .pk-alert-action) {
|
|
22
|
+
position: relative;
|
|
23
|
+
padding-right: 4.5rem;
|
|
24
|
+
}
|
|
25
|
+
.pk-alert:has(> svg) {
|
|
26
|
+
grid-template-columns: auto 1fr;
|
|
27
|
+
column-gap: .5rem;
|
|
28
|
+
}
|
|
29
|
+
.pk-alert > svg {
|
|
30
|
+
grid-row: span 2;
|
|
31
|
+
translate: 0 .125rem;
|
|
32
|
+
width: 1rem;
|
|
33
|
+
height: 1rem;
|
|
34
|
+
color: currentColor;
|
|
35
|
+
}
|
|
36
|
+
.pk-alert.destructive { color: var(--pk-red); }
|
|
37
|
+
.pk-alert.destructive .pk-alert-description {
|
|
38
|
+
color: color-mix(in oklab, var(--pk-red) 90%, transparent);
|
|
39
|
+
}
|
|
40
|
+
.pk-alert.success { color: var(--pk-green); }
|
|
41
|
+
.pk-alert.success .pk-alert-description {
|
|
42
|
+
color: color-mix(in oklab, var(--pk-green) 90%, transparent);
|
|
43
|
+
}
|
|
44
|
+
.pk-alert.warning { color: var(--pk-amber); }
|
|
45
|
+
.pk-alert.warning .pk-alert-description {
|
|
46
|
+
color: color-mix(in oklab, var(--pk-amber) 90%, transparent);
|
|
47
|
+
}
|
|
48
|
+
.pk-alert-title { font-weight: 500; }
|
|
49
|
+
.pk-alert:has(> svg) .pk-alert-title { grid-column-start: 2; }
|
|
50
|
+
.pk-alert-description {
|
|
51
|
+
color: var(--pk-muted);
|
|
52
|
+
font-size: .875rem;
|
|
53
|
+
text-wrap: balance;
|
|
54
|
+
}
|
|
55
|
+
@media (min-width: 768px) {
|
|
56
|
+
.pk-alert-description { text-wrap: pretty; }
|
|
20
57
|
}
|
|
21
|
-
.pk-alert
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
58
|
+
.pk-alert-description p:not(:last-child) { margin-bottom: 1rem; }
|
|
59
|
+
.pk-alert-title a,
|
|
60
|
+
.pk-alert-description a {
|
|
61
|
+
text-decoration: underline;
|
|
62
|
+
text-underline-offset: 3px;
|
|
25
63
|
}
|
|
26
|
-
.pk-alert
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
64
|
+
.pk-alert-title a:hover,
|
|
65
|
+
.pk-alert-description a:hover { color: var(--pk-text); }
|
|
66
|
+
.pk-alert-action {
|
|
67
|
+
position: absolute;
|
|
68
|
+
top: .5rem;
|
|
69
|
+
right: .5rem;
|
|
30
70
|
}
|
|
31
|
-
.pk-alert-title { margin: 0 0 .25rem; font-weight: 500; line-height: 1.1; letter-spacing: -.01em; }
|
|
32
|
-
.pk-alert-description { font-size: .875rem; }
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
module PhlexKit
|
|
2
|
-
# Callout box, ported from ruby_ui's RubyUI::Alert
|
|
3
|
-
# `variant:` selector (same shape as
|
|
4
|
-
#
|
|
2
|
+
# Callout box, ported from ruby_ui's RubyUI::Alert and matched to shadcn/ui's
|
|
3
|
+
# current alert. Presentational, no JS. A `variant:` selector (same shape as
|
|
4
|
+
# PhlexKit::Button) tints the text; a leading svg child becomes the icon column
|
|
5
|
+
# (CSS :has switches the grid); compose with AlertTitle + AlertDescription
|
|
6
|
+
# (+ optional AlertAction pinned top-right):
|
|
5
7
|
#
|
|
6
|
-
# render PhlexKit::Alert.new
|
|
8
|
+
# render PhlexKit::Alert.new do
|
|
9
|
+
# svg(...) { ... } # optional icon
|
|
7
10
|
# render PhlexKit::AlertTitle.new { "Saved" }
|
|
8
11
|
# render PhlexKit::AlertDescription.new { "Your changes are live." }
|
|
12
|
+
# render PhlexKit::AlertAction.new { render PhlexKit::Button.new(size: :sm) { "Undo" } }
|
|
9
13
|
# end
|
|
10
14
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
15
|
+
# shadcn ships default/destructive; success/warning are kit extras kept from
|
|
16
|
+
# ruby_ui, restyled to the same text-tint grammar. `VARIANTS.fetch` fails loud.
|
|
13
17
|
class Alert < BaseComponent
|
|
14
18
|
# variant => extra class appended after the base `ui-alert` (nil = neutral).
|
|
15
19
|
VARIANTS = {
|
|
@@ -25,7 +29,7 @@ module PhlexKit
|
|
|
25
29
|
end
|
|
26
30
|
|
|
27
31
|
def view_template(&block)
|
|
28
|
-
div(**mix({ class: classes }, @attrs), &block)
|
|
32
|
+
div(**mix({ class: classes, role: "alert" }, @attrs), &block)
|
|
29
33
|
end
|
|
30
34
|
|
|
31
35
|
private
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Top-right action slot of a PhlexKit::Alert, ported from shadcn/ui's
|
|
3
|
+
# AlertAction. Pins its content (typically a small Button) to the alert's
|
|
4
|
+
# corner; the alert reserves right padding via :has(). See alert.rb.
|
|
5
|
+
class AlertAction < BaseComponent
|
|
6
|
+
def initialize(**attrs)
|
|
7
|
+
@attrs = attrs
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def view_template(&block)
|
|
11
|
+
div(**mix({ class: "pk-alert-action" }, @attrs), &block)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
module PhlexKit
|
|
2
|
-
# Heading line of a PhlexKit::Alert.
|
|
2
|
+
# Heading line of a PhlexKit::Alert. A div, matching shadcn/ui's markup
|
|
3
|
+
# (role="alert" on the box carries the semantics). See alert.rb.
|
|
3
4
|
class AlertTitle < BaseComponent
|
|
4
5
|
def initialize(**attrs)
|
|
5
6
|
@attrs = attrs
|
|
6
7
|
end
|
|
7
8
|
|
|
8
9
|
def view_template(&block)
|
|
9
|
-
|
|
10
|
+
div(**mix({ class: "pk-alert-title" }, @attrs), &block)
|
|
10
11
|
end
|
|
11
12
|
end
|
|
12
13
|
end
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
position: fixed;
|
|
9
9
|
inset: 0;
|
|
10
10
|
z-index: 50;
|
|
11
|
-
background: rgb(0 0 0 / .
|
|
12
|
-
backdrop-filter: blur(
|
|
11
|
+
background: rgb(0 0 0 / .1);
|
|
12
|
+
backdrop-filter: blur(4px);
|
|
13
13
|
}
|
|
14
14
|
.pk-alert-dialog-panel {
|
|
15
15
|
position: fixed;
|
|
@@ -76,3 +76,6 @@
|
|
|
76
76
|
border-top: 1px solid var(--pk-border);
|
|
77
77
|
background: color-mix(in oklab, var(--pk-surface-2) 50%, transparent);
|
|
78
78
|
}
|
|
79
|
+
@supports not (backdrop-filter: blur(1px)) {
|
|
80
|
+
.pk-alert-dialog-overlay { background: rgb(0 0 0 / .5); }
|
|
81
|
+
}
|
|
@@ -1,22 +1,45 @@
|
|
|
1
|
-
/* Co-located with avatar.rb —
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/* Co-located with avatar.rb — PhlexKit::Avatar (+ Image / Fallback / Badge /
|
|
2
|
+
Group / GroupCount). Matched to shadcn/ui's current avatar (nova): the root
|
|
3
|
+
is NOT overflow-clipped (a Badge hangs over the edge) — image and fallback
|
|
4
|
+
round themselves, and a ::after overlay draws the hairline border, blended
|
|
5
|
+
darken (light mode) / lighten (dark mode) so it hugs any photo. The
|
|
6
|
+
controller toggles `.hidden` between image and fallback. shadcn ships
|
|
7
|
+
sm/default/lg; xs/xl are kit extras. */
|
|
4
8
|
.pk-avatar {
|
|
5
9
|
position: relative;
|
|
6
10
|
display: inline-flex;
|
|
7
11
|
flex-shrink: 0;
|
|
8
|
-
|
|
12
|
+
user-select: none;
|
|
9
13
|
border-radius: 999px;
|
|
10
14
|
height: 2rem;
|
|
11
15
|
width: 2rem;
|
|
12
16
|
font-size: .875rem;
|
|
13
17
|
}
|
|
18
|
+
.pk-avatar::after {
|
|
19
|
+
content: "";
|
|
20
|
+
position: absolute;
|
|
21
|
+
inset: 0;
|
|
22
|
+
border: 1px solid var(--pk-border);
|
|
23
|
+
border-radius: 999px;
|
|
24
|
+
mix-blend-mode: lighten;
|
|
25
|
+
pointer-events: none;
|
|
26
|
+
}
|
|
27
|
+
:root[data-theme="light"] .pk-avatar::after { mix-blend-mode: darken; }
|
|
28
|
+
@media (prefers-color-scheme: light) {
|
|
29
|
+
:root[data-theme="system"] .pk-avatar::after { mix-blend-mode: darken; }
|
|
30
|
+
}
|
|
14
31
|
.pk-avatar.xs { height: 1rem; width: 1rem; font-size: .5rem; }
|
|
15
32
|
.pk-avatar.sm { height: 1.5rem; width: 1.5rem; font-size: .75rem; }
|
|
16
33
|
.pk-avatar.lg { height: 2.5rem; width: 2.5rem; font-size: 1rem; }
|
|
17
34
|
.pk-avatar.xl { height: 5rem; width: 5rem; font-size: 1.875rem; }
|
|
18
35
|
|
|
19
|
-
.pk-avatar-image {
|
|
36
|
+
.pk-avatar-image {
|
|
37
|
+
aspect-ratio: 1 / 1;
|
|
38
|
+
height: 100%;
|
|
39
|
+
width: 100%;
|
|
40
|
+
object-fit: cover;
|
|
41
|
+
border-radius: 999px;
|
|
42
|
+
}
|
|
20
43
|
.pk-avatar-fallback {
|
|
21
44
|
display: flex;
|
|
22
45
|
height: 100%;
|
|
@@ -25,11 +48,59 @@
|
|
|
25
48
|
justify-content: center;
|
|
26
49
|
border-radius: 999px;
|
|
27
50
|
background: var(--pk-surface-2);
|
|
28
|
-
|
|
51
|
+
color: var(--pk-muted);
|
|
29
52
|
}
|
|
30
53
|
.pk-avatar-image.hidden, .pk-avatar-fallback.hidden { display: none; }
|
|
31
54
|
|
|
32
|
-
/*
|
|
55
|
+
/* Status dot pinned to the bottom-right corner; sized by the avatar's size
|
|
56
|
+
modifier, glyph hidden when there's no room (shadcn's AvatarBadge). */
|
|
57
|
+
.pk-avatar-badge {
|
|
58
|
+
position: absolute;
|
|
59
|
+
right: 0;
|
|
60
|
+
bottom: 0;
|
|
61
|
+
z-index: 10;
|
|
62
|
+
display: inline-flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
justify-content: center;
|
|
65
|
+
user-select: none;
|
|
66
|
+
border-radius: 999px;
|
|
67
|
+
background: var(--pk-brand);
|
|
68
|
+
color: var(--pk-brand-ink);
|
|
69
|
+
box-shadow: 0 0 0 2px var(--pk-bg);
|
|
70
|
+
width: .625rem;
|
|
71
|
+
height: .625rem;
|
|
72
|
+
}
|
|
73
|
+
.pk-avatar-badge > svg { width: .5rem; height: .5rem; }
|
|
74
|
+
.pk-avatar.xs .pk-avatar-badge { width: .375rem; height: .375rem; }
|
|
75
|
+
.pk-avatar.sm .pk-avatar-badge { width: .5rem; height: .5rem; }
|
|
76
|
+
.pk-avatar.xs .pk-avatar-badge > svg,
|
|
77
|
+
.pk-avatar.sm .pk-avatar-badge > svg { display: none; }
|
|
78
|
+
.pk-avatar.lg .pk-avatar-badge { width: .75rem; height: .75rem; }
|
|
79
|
+
.pk-avatar.xl .pk-avatar-badge { width: 1rem; height: 1rem; }
|
|
80
|
+
.pk-avatar.xl .pk-avatar-badge > svg { width: .625rem; height: .625rem; }
|
|
81
|
+
|
|
82
|
+
/* Overlapping row (shadcn's -space-x-2 grouped avatars); a trailing
|
|
83
|
+
GroupCount pill follows the avatars' size via :has(). */
|
|
33
84
|
.pk-avatar-group { display: flex; }
|
|
34
|
-
.pk-avatar-group .pk-avatar
|
|
35
|
-
.pk-avatar-group .pk-avatar
|
|
85
|
+
.pk-avatar-group .pk-avatar,
|
|
86
|
+
.pk-avatar-group .pk-avatar-group-count { box-shadow: 0 0 0 2px var(--pk-bg); }
|
|
87
|
+
.pk-avatar-group > :not(:first-child) { margin-left: -.5rem; }
|
|
88
|
+
|
|
89
|
+
.pk-avatar-group-count {
|
|
90
|
+
position: relative;
|
|
91
|
+
display: flex;
|
|
92
|
+
flex-shrink: 0;
|
|
93
|
+
align-items: center;
|
|
94
|
+
justify-content: center;
|
|
95
|
+
border-radius: 999px;
|
|
96
|
+
background: var(--pk-surface-2);
|
|
97
|
+
color: var(--pk-muted);
|
|
98
|
+
width: 2rem;
|
|
99
|
+
height: 2rem;
|
|
100
|
+
font-size: .875rem;
|
|
101
|
+
}
|
|
102
|
+
.pk-avatar-group-count > svg { width: 1rem; height: 1rem; }
|
|
103
|
+
.pk-avatar-group:has(.pk-avatar.sm) .pk-avatar-group-count { width: 1.5rem; height: 1.5rem; font-size: .75rem; }
|
|
104
|
+
.pk-avatar-group:has(.pk-avatar.sm) .pk-avatar-group-count > svg { width: .75rem; height: .75rem; }
|
|
105
|
+
.pk-avatar-group:has(.pk-avatar.lg) .pk-avatar-group-count { width: 2.5rem; height: 2.5rem; font-size: 1rem; }
|
|
106
|
+
.pk-avatar-group:has(.pk-avatar.lg) .pk-avatar-group-count > svg { width: 1.25rem; height: 1.25rem; }
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
module PhlexKit
|
|
2
2
|
# Avatar, ported from ruby_ui's RubyUI::Avatar — a round image with a fallback
|
|
3
|
-
# (initials)
|
|
4
|
-
# image once it loads, else
|
|
5
|
-
# fallback
|
|
6
|
-
#
|
|
3
|
+
# (initials), matched to shadcn/ui's current avatar. Keeps ruby_ui's
|
|
4
|
+
# `phlex-kit--avatar` Stimulus controller (shows the image once it loads, else
|
|
5
|
+
# the fallback). Compose AvatarImage + AvatarFallback (+ optional AvatarBadge
|
|
6
|
+
# status dot); fallback-only is fine (the controller no-ops without an image).
|
|
7
|
+
# Overlap several in an AvatarGroup, with AvatarGroupCount as the "+3" pill.
|
|
8
|
+
# shadcn ships sm/default/lg; xs/xl are kit extras. `SIZES.fetch` fails loud.
|
|
7
9
|
class Avatar < BaseComponent
|
|
8
10
|
SIZES = { xs: "xs", sm: "sm", md: nil, lg: "lg", xl: "xl" }.freeze
|
|
9
11
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Status dot pinned to a PhlexKit::Avatar's bottom-right corner, ported from
|
|
3
|
+
# shadcn/ui's AvatarBadge. Empty for a plain presence dot, or wrap a small
|
|
4
|
+
# svg glyph (hidden automatically on xs/sm avatars). Recolor via style/class
|
|
5
|
+
# — defaults to the brand fill. See avatar.rb.
|
|
6
|
+
class AvatarBadge < BaseComponent
|
|
7
|
+
def initialize(**attrs)
|
|
8
|
+
@attrs = attrs
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def view_template(&block)
|
|
12
|
+
span(**mix({ class: "pk-avatar-badge" }, @attrs), &block)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Trailing overflow pill in a PhlexKit::AvatarGroup ("+3", or a small svg),
|
|
3
|
+
# ported from shadcn/ui's AvatarGroupCount. Sizes itself to match the
|
|
4
|
+
# group's avatars via :has() in avatar.css. See avatar.rb.
|
|
5
|
+
class AvatarGroupCount < BaseComponent
|
|
6
|
+
def initialize(**attrs)
|
|
7
|
+
@attrs = attrs
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def view_template(&block)
|
|
11
|
+
div(**mix({ class: "pk-avatar-group-count" }, @attrs), &block)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -1,50 +1,72 @@
|
|
|
1
|
-
/* Co-located with badge.rb —
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/* Co-located with badge.rb — PhlexKit::Badge. Matched to shadcn/ui's current
|
|
2
|
+
badge (nova): a fixed-height solid pill (h-5, full-round), gap for inline
|
|
3
|
+
glyphs, near-side padding tightened when a child carries
|
|
4
|
+
data-icon="inline-start/end". Hover fills apply to LINK badges only
|
|
5
|
+
(href:), except ghost which hovers everywhere, like theirs. destructive's
|
|
6
|
+
tint is stronger in dark mode (their dark:bg-destructive/20).
|
|
7
|
+
success/warning + sm/lg sizes are kit extras. */
|
|
5
8
|
.pk-badge {
|
|
6
9
|
display: inline-flex;
|
|
7
10
|
align-items: center;
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
justify-content: center;
|
|
12
|
+
width: fit-content;
|
|
13
|
+
flex-shrink: 0;
|
|
14
|
+
gap: .25rem;
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
white-space: nowrap;
|
|
17
|
+
height: 1.25rem;
|
|
18
|
+
border-radius: 2rem;
|
|
19
|
+
border: 1px solid transparent;
|
|
20
|
+
padding: .125rem .5rem;
|
|
10
21
|
font-size: .75rem;
|
|
11
22
|
font-weight: 500;
|
|
12
23
|
line-height: 1;
|
|
13
|
-
|
|
24
|
+
text-decoration: none;
|
|
25
|
+
transition: color .15s ease, background-color .15s ease, border-color .15s ease, box-shadow .15s ease;
|
|
26
|
+
}
|
|
27
|
+
/* shadcn forces glyphs to size-3 with `!` — override Spinner's own sizing. */
|
|
28
|
+
.pk-badge > svg { width: .75rem !important; height: .75rem !important; pointer-events: none; }
|
|
29
|
+
.pk-badge:has(> [data-icon="inline-start"]) { padding-left: .375rem; }
|
|
30
|
+
.pk-badge:has(> [data-icon="inline-end"]) { padding-right: .375rem; }
|
|
31
|
+
.pk-badge:focus-visible {
|
|
32
|
+
outline: none;
|
|
33
|
+
border-color: var(--pk-ring);
|
|
34
|
+
box-shadow: 0 0 0 3px color-mix(in oklab, var(--pk-ring) 50%, transparent);
|
|
14
35
|
}
|
|
15
36
|
|
|
16
|
-
/* Sizes (md is the base above). */
|
|
17
|
-
.pk-badge.sm { padding:
|
|
18
|
-
.pk-badge.
|
|
37
|
+
/* Sizes (md is the base above) — kit extras. */
|
|
38
|
+
.pk-badge.sm { height: 1rem; padding: 0 .375rem; font-size: .7rem; }
|
|
39
|
+
.pk-badge.sm:has(> [data-icon="inline-start"]) { padding-left: .25rem; }
|
|
40
|
+
.pk-badge.sm:has(> [data-icon="inline-end"]) { padding-right: .25rem; }
|
|
41
|
+
.pk-badge.lg { height: 1.5rem; padding: .125rem .75rem; font-size: .875rem; }
|
|
19
42
|
|
|
20
|
-
|
|
21
|
-
.pk-badge.primary {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
.pk-badge.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
color: var(--pk-red);
|
|
38
|
-
background: color-mix(in oklab, var(--pk-red) 10%, transparent);
|
|
39
|
-
border-color: color-mix(in oklab, var(--pk-red) 25%, transparent);
|
|
40
|
-
}
|
|
41
|
-
.pk-badge.success {
|
|
42
|
-
color: var(--pk-green);
|
|
43
|
-
background: color-mix(in oklab, var(--pk-green) 10%, transparent);
|
|
44
|
-
border-color: color-mix(in oklab, var(--pk-green) 25%, transparent);
|
|
43
|
+
.pk-badge.primary { background: var(--pk-brand); color: var(--pk-brand-ink); }
|
|
44
|
+
a.pk-badge.primary:hover { background: color-mix(in oklab, var(--pk-brand) 80%, transparent); }
|
|
45
|
+
.pk-badge.secondary { background: var(--pk-surface-2); color: var(--pk-text); }
|
|
46
|
+
a.pk-badge.secondary:hover { background: color-mix(in oklab, var(--pk-surface-2) 80%, transparent); }
|
|
47
|
+
.pk-badge.outline { border-color: var(--pk-border); color: var(--pk-text); }
|
|
48
|
+
a.pk-badge.outline:hover { background: var(--pk-surface-2); color: var(--pk-muted); }
|
|
49
|
+
.pk-badge.ghost { color: var(--pk-text); }
|
|
50
|
+
.pk-badge.ghost:hover { background: var(--pk-surface-2); color: var(--pk-muted); }
|
|
51
|
+
.pk-badge.link { color: var(--pk-brand); }
|
|
52
|
+
.pk-badge.link:hover { text-decoration: underline; text-underline-offset: 4px; }
|
|
53
|
+
|
|
54
|
+
/* Tinted text variants — 20% fill in dark, 10% in light (their /20 dark, /10). */
|
|
55
|
+
.pk-badge.destructive { color: var(--pk-red); background: color-mix(in oklab, var(--pk-red) 20%, transparent); }
|
|
56
|
+
a.pk-badge.destructive:hover { background: color-mix(in oklab, var(--pk-red) 30%, transparent); }
|
|
57
|
+
.pk-badge.destructive:focus-visible {
|
|
58
|
+
border-color: var(--pk-red);
|
|
59
|
+
box-shadow: 0 0 0 3px color-mix(in oklab, var(--pk-red) 40%, transparent);
|
|
45
60
|
}
|
|
46
|
-
.pk-badge.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
61
|
+
.pk-badge.success { color: var(--pk-green); background: color-mix(in oklab, var(--pk-green) 20%, transparent); }
|
|
62
|
+
.pk-badge.warning { color: var(--pk-amber); background: color-mix(in oklab, var(--pk-amber) 20%, transparent); }
|
|
63
|
+
:root[data-theme="light"] .pk-badge.destructive { background: color-mix(in oklab, var(--pk-red) 10%, transparent); }
|
|
64
|
+
:root[data-theme="light"] .pk-badge.destructive:focus-visible { box-shadow: 0 0 0 3px color-mix(in oklab, var(--pk-red) 20%, transparent); }
|
|
65
|
+
:root[data-theme="light"] .pk-badge.success { background: color-mix(in oklab, var(--pk-green) 10%, transparent); }
|
|
66
|
+
:root[data-theme="light"] .pk-badge.warning { background: color-mix(in oklab, var(--pk-amber) 10%, transparent); }
|
|
67
|
+
@media (prefers-color-scheme: light) {
|
|
68
|
+
:root[data-theme="system"] .pk-badge.destructive { background: color-mix(in oklab, var(--pk-red) 10%, transparent); }
|
|
69
|
+
:root[data-theme="system"] .pk-badge.destructive:focus-visible { box-shadow: 0 0 0 3px color-mix(in oklab, var(--pk-red) 20%, transparent); }
|
|
70
|
+
:root[data-theme="system"] .pk-badge.success { background: color-mix(in oklab, var(--pk-green) 10%, transparent); }
|
|
71
|
+
:root[data-theme="system"] .pk-badge.warning { background: color-mix(in oklab, var(--pk-amber) 10%, transparent); }
|
|
50
72
|
}
|
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
module PhlexKit
|
|
2
|
-
# Pill/label badge, ported from ruby_ui's RubyUI::Badge
|
|
3
|
-
# Same kit shape as PhlexKit::Button — a
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
2
|
+
# Pill/label badge, ported from ruby_ui's RubyUI::Badge and matched to
|
|
3
|
+
# shadcn/ui's current badge. Same kit shape as PhlexKit::Button — a
|
|
4
|
+
# `variant:`/`size:` selector plus attrs/block passed through. Pass `href:`
|
|
5
|
+
# to render an <a> (shadcn's asChild link badge — hover fills only apply to
|
|
6
|
+
# links). Mark leading/trailing svg glyphs with data-icon: "inline-start" /
|
|
7
|
+
# "inline-end" to tighten the near-side padding, like theirs:
|
|
8
|
+
#
|
|
9
|
+
# render PhlexKit::Badge.new(variant: :secondary) do
|
|
10
|
+
# render PhlexKit::Icon.new(:circle_check, size: nil, data: { icon: "inline-start" })
|
|
11
|
+
# plain "Verified"
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# shadcn ships default/secondary/destructive/outline/ghost/link (:primary is
|
|
15
|
+
# their :default); success/warning and the sm/lg sizes are kit extras.
|
|
16
|
+
# `VARIANTS.fetch` fails loud.
|
|
7
17
|
class Badge < BaseComponent
|
|
8
18
|
VARIANTS = {
|
|
9
19
|
primary: "primary",
|
|
10
20
|
secondary: "secondary",
|
|
11
21
|
outline: "outline",
|
|
12
22
|
destructive: "destructive",
|
|
23
|
+
ghost: "ghost",
|
|
24
|
+
link: "link",
|
|
13
25
|
success: "success",
|
|
14
26
|
warning: "warning"
|
|
15
27
|
}.freeze
|
|
@@ -20,14 +32,19 @@ module PhlexKit
|
|
|
20
32
|
lg: "lg"
|
|
21
33
|
}.freeze
|
|
22
34
|
|
|
23
|
-
def initialize(variant: :primary, size: :md, **attrs)
|
|
35
|
+
def initialize(variant: :primary, size: :md, href: nil, **attrs)
|
|
24
36
|
@variant = variant.to_sym
|
|
25
37
|
@size = size.to_sym
|
|
38
|
+
@href = href
|
|
26
39
|
@attrs = attrs
|
|
27
40
|
end
|
|
28
41
|
|
|
29
42
|
def view_template(&block)
|
|
30
|
-
|
|
43
|
+
if @href
|
|
44
|
+
a(**mix({ class: classes, href: @href }, @attrs), &block)
|
|
45
|
+
else
|
|
46
|
+
span(**mix({ class: classes }, @attrs), &block)
|
|
47
|
+
end
|
|
31
48
|
end
|
|
32
49
|
|
|
33
50
|
private
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
gap: .375rem; word-break: break-word;
|
|
5
5
|
font-size: .875rem; color: var(--pk-muted);
|
|
6
6
|
}
|
|
7
|
-
.pk-breadcrumb-item { display: inline-flex; align-items: center; gap: .
|
|
7
|
+
.pk-breadcrumb-item { display: inline-flex; align-items: center; gap: .25rem; }
|
|
8
8
|
.pk-breadcrumb-link { color: inherit; text-decoration: none; transition: color .15s ease; }
|
|
9
9
|
.pk-breadcrumb-link:hover { color: var(--pk-text); }
|
|
10
10
|
.pk-breadcrumb-page { font-weight: 400; color: var(--pk-text); }
|
|
11
11
|
.pk-breadcrumb-separator { display: inline-flex; }
|
|
12
12
|
.pk-breadcrumb-separator > svg { width: .875rem; height: .875rem; }
|
|
13
|
-
.pk-breadcrumb-ellipsis { display: flex; height:
|
|
13
|
+
.pk-breadcrumb-ellipsis { display: flex; height: 1.25rem; width: 1.25rem; align-items: center; justify-content: center; }
|
|
14
14
|
.pk-breadcrumb-ellipsis > svg { width: 1rem; height: 1rem; }
|
|
15
15
|
.pk-sr-only {
|
|
16
16
|
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|