phlex_kit 0.2.0 → 0.2.2
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/README.md +210 -49
- data/THIRD_PARTY_LICENSES +104 -0
- data/app/assets/stylesheets/phlex_kit/_tokens.css +72 -0
- data/app/assets/stylesheets/phlex_kit/phlex_kit.css +1 -1
- data/app/assets/stylesheets/phlex_kit/themes/claude.css +86 -0
- data/app/assets/stylesheets/phlex_kit/themes/neutral.css +95 -0
- data/app/assets/stylesheets/phlex_kit/themes/zinc.css +85 -0
- data/app/components/phlex_kit/accordion/accordion.css +13 -0
- data/app/components/phlex_kit/accordion/accordion.rb +11 -2
- data/app/components/phlex_kit/accordion/accordion_icon.rb +1 -3
- data/app/components/phlex_kit/accordion/accordion_item.rb +5 -2
- data/app/components/phlex_kit/alert_dialog/alert_dialog.css +45 -4
- data/app/components/phlex_kit/alert_dialog/alert_dialog_action.rb +3 -2
- data/app/components/phlex_kit/alert_dialog/alert_dialog_content.rb +6 -2
- data/app/components/phlex_kit/alert_dialog/alert_dialog_media.rb +14 -0
- data/app/components/phlex_kit/attachment/attachment.css +102 -2
- data/app/components/phlex_kit/attachment/attachment.rb +18 -6
- data/app/components/phlex_kit/attachment/attachment_action.rb +1 -6
- data/app/components/phlex_kit/attachment/attachment_group.rb +21 -0
- data/app/components/phlex_kit/attachment/attachment_media.rb +13 -4
- data/app/components/phlex_kit/attachment/attachment_trigger.rb +22 -0
- data/app/components/phlex_kit/breadcrumb/breadcrumb_ellipsis.rb +1 -5
- data/app/components/phlex_kit/breadcrumb/breadcrumb_separator.rb +1 -1
- data/app/components/phlex_kit/calendar/calendar_next.rb +1 -15
- data/app/components/phlex_kit/calendar/calendar_prev.rb +1 -15
- data/app/components/phlex_kit/card/card.css +50 -14
- data/app/components/phlex_kit/card/card.rb +6 -2
- data/app/components/phlex_kit/card/card_action.rb +14 -0
- data/app/components/phlex_kit/carousel/carousel_next.rb +1 -15
- data/app/components/phlex_kit/carousel/carousel_previous.rb +1 -15
- data/app/components/phlex_kit/codeblock/codeblock.css +1 -1
- data/app/components/phlex_kit/combobox/combobox_clear_button.rb +1 -15
- data/app/components/phlex_kit/combobox/combobox_input_trigger.rb +1 -12
- data/app/components/phlex_kit/combobox/combobox_item_indicator.rb +1 -15
- data/app/components/phlex_kit/combobox/combobox_search_input.rb +1 -14
- data/app/components/phlex_kit/combobox/combobox_trigger.rb +1 -14
- data/app/components/phlex_kit/command/command.css +2 -2
- data/app/components/phlex_kit/command/command_input.rb +1 -13
- data/app/components/phlex_kit/context_menu/context_menu_item.rb +1 -3
- data/app/components/phlex_kit/data_table/data_table_column_toggle.rb +1 -13
- data/app/components/phlex_kit/data_table/data_table_expand_toggle.rb +1 -12
- data/app/components/phlex_kit/data_table/data_table_sort_head.rb +2 -28
- data/app/components/phlex_kit/dialog/dialog.css +8 -2
- data/app/components/phlex_kit/dialog/dialog_content.rb +1 -3
- data/app/components/phlex_kit/drawer/drawer.css +1 -0
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu.css +64 -0
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_checkbox_item.rb +31 -0
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_group.rb +11 -0
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_item.rb +5 -2
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_radio_group.rb +10 -0
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_radio_item.rb +31 -0
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_shortcut.rb +10 -0
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_sub.rb +11 -0
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_sub_content.rb +12 -0
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_sub_trigger.rb +18 -0
- data/app/components/phlex_kit/icon/icon.rb +51 -0
- data/app/components/phlex_kit/marker/marker.css +25 -0
- data/app/components/phlex_kit/marker/marker.rb +18 -0
- data/app/components/phlex_kit/marker/marker_content.rb +7 -0
- data/app/components/phlex_kit/marker/marker_icon.rb +7 -0
- data/app/components/phlex_kit/native_select/native_select_icon.rb +1 -10
- data/app/components/phlex_kit/navigation_menu/navigation_menu_trigger.rb +1 -5
- data/app/components/phlex_kit/pagination/pagination_ellipsis.rb +1 -5
- data/app/components/phlex_kit/select/select_item.rb +1 -11
- data/app/components/phlex_kit/select/select_trigger.rb +1 -14
- data/app/components/phlex_kit/sheet/sheet.css +1 -1
- data/app/components/phlex_kit/sheet/sheet_content.rb +1 -3
- data/app/components/phlex_kit/sidebar/sidebar.css +129 -11
- data/app/components/phlex_kit/sidebar/sidebar.rb +19 -6
- data/app/components/phlex_kit/sidebar/sidebar_group_action.rb +14 -0
- data/app/components/phlex_kit/sidebar/sidebar_group_content.rb +13 -0
- data/app/components/phlex_kit/sidebar/sidebar_group_label.rb +13 -0
- data/app/components/phlex_kit/sidebar/sidebar_input.rb +14 -0
- data/app/components/phlex_kit/sidebar/sidebar_menu_action.rb +13 -0
- data/app/components/phlex_kit/sidebar/sidebar_menu_badge.rb +13 -0
- data/app/components/phlex_kit/sidebar/sidebar_menu_skeleton.rb +19 -0
- data/app/components/phlex_kit/sidebar/sidebar_menu_sub.rb +13 -0
- data/app/components/phlex_kit/sidebar/sidebar_menu_sub_button.rb +18 -0
- data/app/components/phlex_kit/sidebar/sidebar_menu_sub_item.rb +13 -0
- data/app/components/phlex_kit/sidebar/sidebar_separator.rb +14 -0
- data/app/components/phlex_kit/spinner/spinner.rb +5 -10
- data/app/components/phlex_kit/toast/toast_close.rb +1 -15
- data/app/components/phlex_kit/toast/toast_icon.rb +12 -36
- data/app/components/phlex_kit/typography/heading.rb +1 -1
- data/app/components/phlex_kit/typography/typography.css +7 -3
- data/app/javascript/phlex_kit/controllers/accordion_controller.js +17 -1
- data/app/javascript/phlex_kit/controllers/index.js +2 -0
- data/app/javascript/phlex_kit/controllers/scroll_fade_controller.js +31 -0
- data/lib/phlex_kit/configuration.rb +7 -0
- data/lib/phlex_kit/icons/lucide.rb +88 -0
- data/lib/phlex_kit/icons/phosphor.rb +88 -0
- data/lib/phlex_kit/icons/remix.rb +88 -0
- data/lib/phlex_kit/icons/tabler.rb +88 -0
- data/lib/phlex_kit/icons.rb +40 -0
- data/lib/phlex_kit/version.rb +1 -1
- data/lib/phlex_kit.rb +1 -0
- metadata +45 -8
- data/app/components/phlex_kit/stars/stars.css +0 -4
- data/app/components/phlex_kit/stars/stars.rb +0 -19
|
@@ -20,21 +20,7 @@ module PhlexKit
|
|
|
20
20
|
private
|
|
21
21
|
|
|
22
22
|
def icon
|
|
23
|
-
|
|
24
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
25
|
-
width: "15",
|
|
26
|
-
height: "15",
|
|
27
|
-
viewbox: "0 0 15 15",
|
|
28
|
-
fill: "none",
|
|
29
|
-
"aria-hidden": "true"
|
|
30
|
-
) do |s|
|
|
31
|
-
s.path(
|
|
32
|
-
d: "M8.84182 3.13514C9.04327 3.32401 9.05348 3.64042 8.86462 3.84188L5.43521 7.49991L8.86462 11.1579C9.05348 11.3594 9.04327 11.6758 8.84182 11.8647C8.64036 12.0535 8.32394 12.0433 8.13508 11.8419L4.38508 7.84188C4.20477 7.64955 4.20477 7.35027 4.38508 7.15794L8.13508 3.15794C8.32394 2.95648 8.64036 2.94628 8.84182 3.13514Z",
|
|
33
|
-
fill: "currentColor",
|
|
34
|
-
fill_rule: "evenodd",
|
|
35
|
-
clip_rule: "evenodd"
|
|
36
|
-
)
|
|
37
|
-
end
|
|
23
|
+
render Icon.new(:chevron_left, size: 15)
|
|
38
24
|
end
|
|
39
25
|
end
|
|
40
26
|
end
|
|
@@ -1,15 +1,51 @@
|
|
|
1
1
|
/* Co-located with card.rb — the UI::Card container kit (Card + Header / Title /
|
|
2
|
-
Description / Content / Footer)
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
2
|
+
Description / Action / Content / Footer), matching shadcn/ui's current card:
|
|
3
|
+
every gap and inset derives from --pk-card-spacing (1rem; .75rem for
|
|
4
|
+
size :sm — or set it inline per card), the header becomes a 1fr/auto grid
|
|
5
|
+
when a CardAction is present, the footer is a full-bleed muted band, and a
|
|
6
|
+
leading <img> child runs edge-to-edge. Theme tokens come from the global
|
|
7
|
+
stylesheet. */
|
|
8
|
+
.pk-card {
|
|
9
|
+
--pk-card-spacing: 1rem;
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
gap: var(--pk-card-spacing);
|
|
13
|
+
overflow: hidden;
|
|
14
|
+
border: 1px solid var(--pk-border);
|
|
15
|
+
border-radius: calc(var(--pk-radius) + 4px);
|
|
16
|
+
background: var(--pk-surface);
|
|
17
|
+
color: var(--pk-text);
|
|
18
|
+
padding-block: var(--pk-card-spacing);
|
|
19
|
+
font-size: .875rem;
|
|
20
|
+
}
|
|
21
|
+
.pk-card.sm { --pk-card-spacing: .75rem; }
|
|
22
|
+
/* the footer band and a leading image supply their own inset */
|
|
23
|
+
.pk-card:has(> .pk-card-footer:last-child) { padding-bottom: 0; }
|
|
24
|
+
.pk-card:has(> img:first-child) { padding-top: 0; }
|
|
25
|
+
.pk-card > img { width: 100%; object-fit: cover; }
|
|
26
|
+
|
|
27
|
+
.pk-card-header {
|
|
28
|
+
display: grid;
|
|
29
|
+
grid-auto-rows: min-content;
|
|
30
|
+
align-items: start;
|
|
31
|
+
gap: .25rem;
|
|
32
|
+
padding-inline: var(--pk-card-spacing);
|
|
33
|
+
}
|
|
34
|
+
.pk-card-header:has(.pk-card-action) { grid-template-columns: 1fr auto; }
|
|
35
|
+
.pk-card-title { margin: 0; font-weight: 600; line-height: 1.25; }
|
|
36
|
+
.pk-card-description { margin: 0; font-size: .875rem; color: var(--pk-muted); }
|
|
37
|
+
.pk-card-action {
|
|
38
|
+
grid-row: 1 / span 2;
|
|
39
|
+
grid-column: 2;
|
|
40
|
+
justify-self: end;
|
|
41
|
+
align-self: start;
|
|
42
|
+
}
|
|
43
|
+
.pk-card-content { padding-inline: var(--pk-card-spacing); }
|
|
44
|
+
.pk-card-footer {
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
gap: .5rem;
|
|
48
|
+
border-top: 1px solid var(--pk-border);
|
|
49
|
+
background: color-mix(in oklab, var(--pk-surface-2) 50%, transparent);
|
|
50
|
+
padding: var(--pk-card-spacing);
|
|
51
|
+
}
|
|
@@ -18,12 +18,16 @@ module PhlexKit
|
|
|
18
18
|
# render PhlexKit::CardFooter.new { render PhlexKit::Button.new { "Action" } }
|
|
19
19
|
# end
|
|
20
20
|
class Card < BaseComponent
|
|
21
|
-
|
|
21
|
+
# size => modifier class; :sm tightens --pk-card-spacing (shadcn's size prop).
|
|
22
|
+
SIZES = { default: nil, sm: "sm" }.freeze
|
|
23
|
+
|
|
24
|
+
def initialize(size: :default, **attrs)
|
|
25
|
+
@size = size.to_sym
|
|
22
26
|
@attrs = attrs
|
|
23
27
|
end
|
|
24
28
|
|
|
25
29
|
def view_template(&block)
|
|
26
|
-
div(**mix({ class: "pk-card" }, @attrs), &block)
|
|
30
|
+
div(**mix({ class: [ "pk-card", SIZES.fetch(@size) ].compact.join(" ") }, @attrs), &block)
|
|
27
31
|
end
|
|
28
32
|
end
|
|
29
33
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Top-right slot of a CardHeader (a button, link or badge) — the header
|
|
3
|
+
# becomes a 1fr/auto grid when present. Ported from shadcn/ui's CardAction
|
|
4
|
+
# (newer than ruby_ui's card). See card.rb.
|
|
5
|
+
class CardAction < BaseComponent
|
|
6
|
+
def initialize(**attrs)
|
|
7
|
+
@attrs = attrs
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def view_template(&)
|
|
11
|
+
div(**mix({ class: "pk-card-action" }, @attrs), &)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -19,21 +19,7 @@ module PhlexKit
|
|
|
19
19
|
private
|
|
20
20
|
|
|
21
21
|
def icon
|
|
22
|
-
|
|
23
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
24
|
-
width: "24",
|
|
25
|
-
height: "24",
|
|
26
|
-
viewbox: "0 0 24 24",
|
|
27
|
-
fill: "none",
|
|
28
|
-
stroke: "currentColor",
|
|
29
|
-
"stroke-width": "2",
|
|
30
|
-
"stroke-linecap": "round",
|
|
31
|
-
"stroke-linejoin": "round",
|
|
32
|
-
"aria-hidden": "true"
|
|
33
|
-
) do |s|
|
|
34
|
-
s.path(d: "M5 12h14")
|
|
35
|
-
s.path(d: "m12 5 7 7-7 7")
|
|
36
|
-
end
|
|
22
|
+
render Icon.new(:arrow_right, size: 24)
|
|
37
23
|
end
|
|
38
24
|
end
|
|
39
25
|
end
|
|
@@ -20,21 +20,7 @@ module PhlexKit
|
|
|
20
20
|
private
|
|
21
21
|
|
|
22
22
|
def icon
|
|
23
|
-
|
|
24
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
25
|
-
width: "24",
|
|
26
|
-
height: "24",
|
|
27
|
-
viewbox: "0 0 24 24",
|
|
28
|
-
fill: "none",
|
|
29
|
-
stroke: "currentColor",
|
|
30
|
-
"stroke-width": "2",
|
|
31
|
-
"stroke-linecap": "round",
|
|
32
|
-
"stroke-linejoin": "round",
|
|
33
|
-
"aria-hidden": "true"
|
|
34
|
-
) do |s|
|
|
35
|
-
s.path(d: "m12 19-7-7 7-7")
|
|
36
|
-
s.path(d: "M19 12H5")
|
|
37
|
-
end
|
|
23
|
+
render Icon.new(:arrow_left, size: 24)
|
|
38
24
|
end
|
|
39
25
|
end
|
|
40
26
|
end
|
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
background: var(--pk-surface-2); font-size: .875rem;
|
|
5
5
|
}
|
|
6
6
|
.pk-codeblock pre { margin: 0; padding: 1rem; }
|
|
7
|
-
.pk-codeblock code { font-family:
|
|
7
|
+
.pk-codeblock code { font-family: var(--pk-font-mono, ui-monospace, monospace); color: var(--pk-text); }
|
|
@@ -18,21 +18,7 @@ module PhlexKit
|
|
|
18
18
|
action: "phlex-kit--combobox#clearAll"
|
|
19
19
|
}
|
|
20
20
|
}, @attrs)) do
|
|
21
|
-
|
|
22
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
23
|
-
width: "24",
|
|
24
|
-
height: "24",
|
|
25
|
-
viewbox: "0 0 24 24",
|
|
26
|
-
fill: "none",
|
|
27
|
-
stroke: "currentColor",
|
|
28
|
-
"stroke-width": "2",
|
|
29
|
-
"stroke-linecap": "round",
|
|
30
|
-
"stroke-linejoin": "round",
|
|
31
|
-
"aria-hidden": "true"
|
|
32
|
-
) do |s|
|
|
33
|
-
s.path(d: "M18 6 6 18")
|
|
34
|
-
s.path(d: "m6 6 12 12")
|
|
35
|
-
end
|
|
21
|
+
render Icon.new(:x, size: 24)
|
|
36
22
|
end
|
|
37
23
|
end
|
|
38
24
|
end
|
|
@@ -51,18 +51,7 @@ module PhlexKit
|
|
|
51
51
|
|
|
52
52
|
def chevron_icon
|
|
53
53
|
span(class: "pk-combobox-input-trigger-icon") do
|
|
54
|
-
|
|
55
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
56
|
-
width: "24",
|
|
57
|
-
height: "24",
|
|
58
|
-
viewbox: "0 0 24 24",
|
|
59
|
-
fill: "none",
|
|
60
|
-
stroke: "currentColor",
|
|
61
|
-
"stroke-width": "2",
|
|
62
|
-
"stroke-linecap": "round",
|
|
63
|
-
"stroke-linejoin": "round",
|
|
64
|
-
"aria-hidden": "true"
|
|
65
|
-
) { |s| s.path(d: "m6 9 6 6 6-6") }
|
|
54
|
+
render Icon.new(:chevron_down, size: 24)
|
|
66
55
|
end
|
|
67
56
|
end
|
|
68
57
|
end
|
|
@@ -7,21 +7,7 @@ module PhlexKit
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def view_template
|
|
10
|
-
|
|
11
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
12
|
-
width: "24",
|
|
13
|
-
height: "24",
|
|
14
|
-
viewbox: "0 0 24 24",
|
|
15
|
-
fill: "none",
|
|
16
|
-
stroke: "currentColor",
|
|
17
|
-
"stroke-width": "2",
|
|
18
|
-
"stroke-linecap": "round",
|
|
19
|
-
"stroke-linejoin": "round",
|
|
20
|
-
class: "pk-combobox-item-indicator",
|
|
21
|
-
"aria-hidden": "true"
|
|
22
|
-
}, @attrs)) do |s|
|
|
23
|
-
s.path(d: "M20 6 9 17l-5-5")
|
|
24
|
-
end
|
|
10
|
+
render Icon.new(:check, size: 24, **mix({ class: "pk-combobox-item-indicator" }, @attrs))
|
|
25
11
|
end
|
|
26
12
|
end
|
|
27
13
|
end
|
|
@@ -30,20 +30,7 @@ module PhlexKit
|
|
|
30
30
|
private
|
|
31
31
|
|
|
32
32
|
def icon
|
|
33
|
-
|
|
34
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
35
|
-
viewbox: "0 0 24 24",
|
|
36
|
-
fill: "none",
|
|
37
|
-
stroke: "currentColor",
|
|
38
|
-
"stroke-width": "2",
|
|
39
|
-
"stroke-linecap": "round",
|
|
40
|
-
"stroke-linejoin": "round",
|
|
41
|
-
class: "pk-combobox-search-icon",
|
|
42
|
-
"aria-hidden": "true"
|
|
43
|
-
) do |s|
|
|
44
|
-
s.circle(cx: "11", cy: "11", r: "8")
|
|
45
|
-
s.path(d: "m21 21-4.3-4.3")
|
|
46
|
-
end
|
|
33
|
+
render Icon.new(:search, size: nil, class: "pk-combobox-search-icon")
|
|
47
34
|
end
|
|
48
35
|
end
|
|
49
36
|
end
|
|
@@ -29,20 +29,7 @@ module PhlexKit
|
|
|
29
29
|
private
|
|
30
30
|
|
|
31
31
|
def icon
|
|
32
|
-
|
|
33
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
34
|
-
viewbox: "0 0 24 24",
|
|
35
|
-
fill: "none",
|
|
36
|
-
stroke: "currentColor",
|
|
37
|
-
"stroke-width": "2",
|
|
38
|
-
"stroke-linecap": "round",
|
|
39
|
-
"stroke-linejoin": "round",
|
|
40
|
-
class: "pk-combobox-trigger-icon",
|
|
41
|
-
"aria-hidden": "true"
|
|
42
|
-
) do |s|
|
|
43
|
-
s.path(d: "m7 15 5 5 5-5")
|
|
44
|
-
s.path(d: "m7 9 5-5 5 5")
|
|
45
|
-
end
|
|
32
|
+
render Icon.new(:chevrons_up_down, size: nil, class: "pk-combobox-trigger-icon")
|
|
46
33
|
end
|
|
47
34
|
end
|
|
48
35
|
end
|
|
@@ -38,19 +38,7 @@ module PhlexKit
|
|
|
38
38
|
private
|
|
39
39
|
|
|
40
40
|
def search_icon
|
|
41
|
-
|
|
42
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
43
|
-
viewbox: "0 0 20 20",
|
|
44
|
-
fill: "currentColor",
|
|
45
|
-
class: "pk-command-input-icon",
|
|
46
|
-
"aria-hidden": "true"
|
|
47
|
-
) do |s|
|
|
48
|
-
s.path(
|
|
49
|
-
fill_rule: "evenodd",
|
|
50
|
-
d: "M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z",
|
|
51
|
-
clip_rule: "evenodd"
|
|
52
|
-
)
|
|
53
|
-
end
|
|
41
|
+
render Icon.new(:search, size: nil, class: "pk-command-input-icon")
|
|
54
42
|
end
|
|
55
43
|
end
|
|
56
44
|
end
|
|
@@ -12,9 +12,7 @@ module PhlexKit
|
|
|
12
12
|
data: { action: "click->phlex-kit--context-menu#close", phlex_kit__context_menu_target: "menuItem", disabled: @disabled } }, @attrs)) do
|
|
13
13
|
if @checked
|
|
14
14
|
span(class: "pk-context-menu-check") do
|
|
15
|
-
|
|
16
|
-
s.path(fill_rule: "evenodd", clip_rule: "evenodd", fill: "currentColor", d: "M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3355 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.55529 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z")
|
|
17
|
-
end
|
|
15
|
+
render Icon.new(:check, size: 15)
|
|
18
16
|
end
|
|
19
17
|
end
|
|
20
18
|
yield
|
|
@@ -43,19 +43,7 @@ module PhlexKit
|
|
|
43
43
|
private
|
|
44
44
|
|
|
45
45
|
def chevron_icon
|
|
46
|
-
|
|
47
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
48
|
-
width: "16",
|
|
49
|
-
height: "16",
|
|
50
|
-
viewbox: "0 0 24 24",
|
|
51
|
-
fill: "none",
|
|
52
|
-
stroke: "currentColor",
|
|
53
|
-
"stroke-width": "2",
|
|
54
|
-
"stroke-linecap": "round",
|
|
55
|
-
"stroke-linejoin": "round",
|
|
56
|
-
class: "pk-data-table-column-toggle-icon",
|
|
57
|
-
"aria-hidden": "true"
|
|
58
|
-
) { |s| s.polyline(points: "6 9 12 15 18 9") }
|
|
46
|
+
render Icon.new(:chevron_down, class: "pk-data-table-column-toggle-icon")
|
|
59
47
|
end
|
|
60
48
|
end
|
|
61
49
|
end
|
|
@@ -23,18 +23,7 @@ module PhlexKit
|
|
|
23
23
|
private
|
|
24
24
|
|
|
25
25
|
def icon
|
|
26
|
-
|
|
27
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
28
|
-
width: "16",
|
|
29
|
-
height: "16",
|
|
30
|
-
viewbox: "0 0 24 24",
|
|
31
|
-
fill: "none",
|
|
32
|
-
stroke: "currentColor",
|
|
33
|
-
"stroke-width": "2",
|
|
34
|
-
"stroke-linecap": "round",
|
|
35
|
-
"stroke-linejoin": "round",
|
|
36
|
-
"aria-hidden": "true"
|
|
37
|
-
) { |s| s.polyline(points: "9 18 15 12 9 6") }
|
|
26
|
+
render Icon.new(:chevron_right)
|
|
38
27
|
end
|
|
39
28
|
end
|
|
40
29
|
end
|
|
@@ -49,34 +49,8 @@ module PhlexKit
|
|
|
49
49
|
|
|
50
50
|
def sort_icon
|
|
51
51
|
icon_class = [ "pk-data-table-sort-icon", (current_direction ? nil : "unsorted") ].compact.join(" ")
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
sort_svg(icon_class) { |s| s.polyline(points: "18 15 12 9 6 15") }
|
|
55
|
-
when "desc"
|
|
56
|
-
sort_svg(icon_class) { |s| s.polyline(points: "6 9 12 15 18 9") }
|
|
57
|
-
else
|
|
58
|
-
sort_svg(icon_class) do |s|
|
|
59
|
-
s.polyline(points: "8 15 12 19 16 15")
|
|
60
|
-
s.polyline(points: "8 9 12 5 16 9")
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def sort_svg(icon_class, &)
|
|
66
|
-
svg(
|
|
67
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
68
|
-
width: "12",
|
|
69
|
-
height: "12",
|
|
70
|
-
viewbox: "0 0 24 24",
|
|
71
|
-
fill: "none",
|
|
72
|
-
stroke: "currentColor",
|
|
73
|
-
"stroke-width": "2",
|
|
74
|
-
"stroke-linecap": "round",
|
|
75
|
-
"stroke-linejoin": "round",
|
|
76
|
-
class: icon_class,
|
|
77
|
-
"aria-hidden": "true",
|
|
78
|
-
&
|
|
79
|
-
)
|
|
52
|
+
glyph = { "asc" => :chevron_up, "desc" => :chevron_down }.fetch(current_direction, :chevrons_up_down)
|
|
53
|
+
render Icon.new(glyph, size: 12, class: icon_class)
|
|
80
54
|
end
|
|
81
55
|
end
|
|
82
56
|
end
|
|
@@ -8,14 +8,20 @@
|
|
|
8
8
|
padding: 1.5rem; box-shadow: 0 10px 30px rgb(0 0 0 / .35);
|
|
9
9
|
}
|
|
10
10
|
.pk-dialog[open] { display: flex; flex-direction: column; }
|
|
11
|
-
.pk-dialog::backdrop { background: rgb(0 0 0 / .5); backdrop-filter: blur(
|
|
11
|
+
.pk-dialog::backdrop { background: rgb(0 0 0 / .5); backdrop-filter: blur(8px); }
|
|
12
12
|
.pk-dialog.xs { max-width: 24rem; }
|
|
13
13
|
.pk-dialog.sm { max-width: 28rem; }
|
|
14
14
|
.pk-dialog.lg { max-width: 42rem; }
|
|
15
15
|
.pk-dialog.xl { max-width: 56rem; }
|
|
16
16
|
.pk-dialog.full { max-width: 100%; }
|
|
17
17
|
.pk-dialog-header { display: flex; flex-direction: column; gap: .375rem; text-align: center; }
|
|
18
|
-
.pk-dialog-footer {
|
|
18
|
+
.pk-dialog-footer {
|
|
19
|
+
display: flex; flex-direction: column-reverse; gap: .5rem;
|
|
20
|
+
margin: 0 calc(-1 * var(--pk-dialog-spacing)) calc(-1 * var(--pk-dialog-spacing));
|
|
21
|
+
padding: var(--pk-dialog-spacing);
|
|
22
|
+
border-top: 1px solid var(--pk-border);
|
|
23
|
+
background: color-mix(in oklab, var(--pk-surface-2) 50%, transparent);
|
|
24
|
+
}
|
|
19
25
|
.pk-dialog-title { font-size: 1.125rem; font-weight: 600; line-height: 1; letter-spacing: -.01em; }
|
|
20
26
|
.pk-dialog-description { font-size: .875rem; color: var(--pk-muted); }
|
|
21
27
|
.pk-dialog-middle { padding-block: 1rem; }
|
|
@@ -10,9 +10,7 @@ module PhlexKit
|
|
|
10
10
|
dialog(**mix({ class: cls, data: { phlex_kit__dialog_target: "dialog", action: "click->phlex-kit--dialog#backdropClick" } }, @attrs)) do
|
|
11
11
|
yield
|
|
12
12
|
button(type: "button", class: "pk-overlay-close", data: { action: "click->phlex-kit--dialog#dismiss" }) do
|
|
13
|
-
|
|
14
|
-
s.path(fill_rule: "evenodd", clip_rule: "evenodd", fill: "currentColor", d: "M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z")
|
|
15
|
-
end
|
|
13
|
+
render Icon.new(:x, size: 15)
|
|
16
14
|
span(class: "pk-sr-only") { "Close" }
|
|
17
15
|
end
|
|
18
16
|
end
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
.pk-dropdown-menu-item {
|
|
22
22
|
display: flex;
|
|
23
23
|
align-items: center;
|
|
24
|
+
gap: .5rem;
|
|
24
25
|
width: 100%;
|
|
25
26
|
cursor: pointer;
|
|
26
27
|
user-select: none;
|
|
@@ -36,3 +37,66 @@
|
|
|
36
37
|
|
|
37
38
|
.pk-dropdown-menu-label { margin: 0; padding: .375rem .5rem; font-size: .875rem; font-weight: 600; }
|
|
38
39
|
.pk-dropdown-menu-separator { height: 1px; margin: .25rem -.25rem; background: var(--pk-border); }
|
|
40
|
+
|
|
41
|
+
.pk-dropdown-menu-item > svg { flex: none; width: 1rem; height: 1rem; color: var(--pk-muted); }
|
|
42
|
+
.pk-dropdown-menu-group { display: contents; }
|
|
43
|
+
|
|
44
|
+
/* Destructive rows (irreversible actions). */
|
|
45
|
+
.pk-dropdown-menu-item.destructive { color: var(--pk-red); }
|
|
46
|
+
.pk-dropdown-menu-item.destructive > svg { color: var(--pk-red); }
|
|
47
|
+
.pk-dropdown-menu-item.destructive:hover,
|
|
48
|
+
.pk-dropdown-menu-item.destructive:focus {
|
|
49
|
+
background: color-mix(in oklab, var(--pk-red) 12%, transparent);
|
|
50
|
+
color: var(--pk-red);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Keyboard hint, pushed to the row's end. */
|
|
54
|
+
.pk-dropdown-menu-shortcut {
|
|
55
|
+
margin-left: auto;
|
|
56
|
+
font-size: .75rem;
|
|
57
|
+
letter-spacing: .1em;
|
|
58
|
+
color: var(--pk-muted);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Checkbox / radio rows: real (hidden) inputs; indicator shows via :has. */
|
|
62
|
+
.pk-dropdown-menu-item-input {
|
|
63
|
+
position: absolute;
|
|
64
|
+
width: 1px;
|
|
65
|
+
height: 1px;
|
|
66
|
+
opacity: 0;
|
|
67
|
+
pointer-events: none;
|
|
68
|
+
}
|
|
69
|
+
.pk-dropdown-menu-checkbox-item,
|
|
70
|
+
.pk-dropdown-menu-radio-item { position: relative; }
|
|
71
|
+
.pk-dropdown-menu-item-indicator {
|
|
72
|
+
display: inline-flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
justify-content: center;
|
|
75
|
+
flex: none;
|
|
76
|
+
width: 1rem;
|
|
77
|
+
height: 1rem;
|
|
78
|
+
visibility: hidden;
|
|
79
|
+
}
|
|
80
|
+
.pk-dropdown-menu-item-indicator svg { width: .875rem; height: .875rem; }
|
|
81
|
+
.pk-dropdown-menu-radio-item .pk-dropdown-menu-item-indicator svg { width: .5rem; height: .5rem; }
|
|
82
|
+
.pk-dropdown-menu-item:has(.pk-dropdown-menu-item-input:checked) .pk-dropdown-menu-item-indicator { visibility: visible; }
|
|
83
|
+
.pk-dropdown-menu-item:has(.pk-dropdown-menu-item-input:focus-visible) {
|
|
84
|
+
box-shadow: 0 0 0 2px color-mix(in oklab, var(--pk-ring) 50%, transparent);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Submenu: trigger row + side panel, revealed on hover/focus — pure CSS. */
|
|
88
|
+
.pk-dropdown-menu-sub { position: relative; }
|
|
89
|
+
.pk-dropdown-menu-sub-chevron { margin-left: auto; width: 1rem; height: 1rem; }
|
|
90
|
+
.pk-dropdown-menu-sub-content {
|
|
91
|
+
position: absolute;
|
|
92
|
+
top: -0.25rem;
|
|
93
|
+
left: 100%;
|
|
94
|
+
z-index: 60;
|
|
95
|
+
min-width: 10rem;
|
|
96
|
+
padding-left: .25rem; /* hover bridge over the gap */
|
|
97
|
+
display: none;
|
|
98
|
+
}
|
|
99
|
+
.pk-dropdown-menu-sub:hover > .pk-dropdown-menu-sub-content,
|
|
100
|
+
.pk-dropdown-menu-sub:focus-within > .pk-dropdown-menu-sub-content { display: block; }
|
|
101
|
+
.pk-dropdown-menu-sub:hover > .pk-dropdown-menu-sub-trigger,
|
|
102
|
+
.pk-dropdown-menu-sub:focus-within > .pk-dropdown-menu-sub-trigger { background: var(--pk-accent); }
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Toggleable menu row, ported from shadcn/ui's DropdownMenuCheckboxItem: a
|
|
3
|
+
# <label> around a real (hidden) checkbox — clicking toggles it natively and
|
|
4
|
+
# the ✓ shows via CSS, without closing the menu (matching Radix). Pass
|
|
5
|
+
# `name:`/`value:` to submit the state with a form. See dropdown_menu.rb.
|
|
6
|
+
class DropdownMenuCheckboxItem < BaseComponent
|
|
7
|
+
def initialize(checked: false, name: nil, value: "1", **attrs)
|
|
8
|
+
@checked = checked
|
|
9
|
+
@name = name
|
|
10
|
+
@value = value
|
|
11
|
+
@attrs = attrs
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def view_template(&block)
|
|
15
|
+
label(**mix({
|
|
16
|
+
class: "pk-dropdown-menu-item pk-dropdown-menu-checkbox-item",
|
|
17
|
+
role: "menuitemcheckbox"
|
|
18
|
+
}, @attrs)) do
|
|
19
|
+
input(type: :checkbox, class: "pk-dropdown-menu-item-input", name: @name, value: @value, checked: @checked)
|
|
20
|
+
span(class: "pk-dropdown-menu-item-indicator", aria: { hidden: "true" }) { check_icon }
|
|
21
|
+
yield if block
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def check_icon
|
|
28
|
+
render Icon.new(:check, size: nil)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Grouping wrapper for related items (with an optional leading
|
|
3
|
+
# DropdownMenuLabel). Ported from shadcn/ui's DropdownMenuGroup.
|
|
4
|
+
# See dropdown_menu.rb.
|
|
5
|
+
class DropdownMenuGroup < BaseComponent
|
|
6
|
+
def initialize(**attrs) = (@attrs = attrs)
|
|
7
|
+
def view_template(&)
|
|
8
|
+
div(**mix({ class: "pk-dropdown-menu-group", role: "group" }, @attrs), &)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -2,9 +2,12 @@ module PhlexKit
|
|
|
2
2
|
# A menu row in a PhlexKit::DropdownMenuContent. `as: :a` (default, pass `href:`) or
|
|
3
3
|
# `:div` (e.g. to wrap a button_to). Closes the menu on click. See dropdown_menu.rb.
|
|
4
4
|
class DropdownMenuItem < BaseComponent
|
|
5
|
-
|
|
5
|
+
VARIANTS = { default: nil, destructive: "destructive" }.freeze
|
|
6
|
+
|
|
7
|
+
def initialize(as: :a, href: "#", variant: :default, **attrs)
|
|
6
8
|
@as = as.to_sym
|
|
7
9
|
@href = href
|
|
10
|
+
@variant = variant.to_sym
|
|
8
11
|
@attrs = attrs
|
|
9
12
|
end
|
|
10
13
|
|
|
@@ -12,7 +15,7 @@ module PhlexKit
|
|
|
12
15
|
base = {
|
|
13
16
|
role: "menuitem",
|
|
14
17
|
tabindex: "-1",
|
|
15
|
-
class: "pk-dropdown-menu-item",
|
|
18
|
+
class: [ "pk-dropdown-menu-item", VARIANTS.fetch(@variant) ].compact.join(" "),
|
|
16
19
|
data: { phlex_kit__dropdown_menu_target: "menuItem", action: "click->phlex-kit--dropdown-menu#close" }
|
|
17
20
|
}
|
|
18
21
|
base[:href] = @href unless @as == :div
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Exclusive-choice group of DropdownMenuRadioItems (share the same `name:`).
|
|
3
|
+
# Ported from shadcn/ui's DropdownMenuRadioGroup. See dropdown_menu.rb.
|
|
4
|
+
class DropdownMenuRadioGroup < BaseComponent
|
|
5
|
+
def initialize(**attrs) = (@attrs = attrs)
|
|
6
|
+
def view_template(&)
|
|
7
|
+
div(**mix({ class: "pk-dropdown-menu-group", role: "radiogroup" }, @attrs), &)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# One choice in a DropdownMenuRadioGroup — a <label> around a real (hidden)
|
|
3
|
+
# radio; the ● shows via CSS and picking doesn't close the menu (matching
|
|
4
|
+
# Radix). See dropdown_menu.rb.
|
|
5
|
+
class DropdownMenuRadioItem < 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-dropdown-menu-item pk-dropdown-menu-radio-item",
|
|
16
|
+
role: "menuitemradio"
|
|
17
|
+
}, @attrs)) do
|
|
18
|
+
input(type: :radio, class: "pk-dropdown-menu-item-input", name: @name, value: @value, checked: @checked)
|
|
19
|
+
span(class: "pk-dropdown-menu-item-indicator", aria: { hidden: "true" }) do
|
|
20
|
+
# Deliberately NOT a PhlexKit::Icon: a filled selection dot is
|
|
21
|
+
# geometry, not icon-library vocabulary — it stays identical across
|
|
22
|
+
# icon_library settings (shadcn fills its Circle icon via CSS too).
|
|
23
|
+
svg(xmlns: "http://www.w3.org/2000/svg", viewbox: "0 0 24 24", fill: "currentColor") do |s|
|
|
24
|
+
s.circle(cx: "12", cy: "12", r: "5")
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
yield if block
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|