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
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Right-aligned keyboard hint in a menu row. Ported from shadcn/ui's
|
|
3
|
+
# DropdownMenuShortcut. See dropdown_menu.rb.
|
|
4
|
+
class DropdownMenuShortcut < BaseComponent
|
|
5
|
+
def initialize(**attrs) = (@attrs = attrs)
|
|
6
|
+
def view_template(&)
|
|
7
|
+
span(**mix({ class: "pk-dropdown-menu-shortcut" }, @attrs), &)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Nested submenu wrapper: SubTrigger row + SubContent panel, revealed on
|
|
3
|
+
# hover/focus with pure CSS (no extra controller). Ported from shadcn/ui's
|
|
4
|
+
# DropdownMenuSub. See dropdown_menu.rb.
|
|
5
|
+
class DropdownMenuSub < BaseComponent
|
|
6
|
+
def initialize(**attrs) = (@attrs = attrs)
|
|
7
|
+
def view_template(&)
|
|
8
|
+
div(**mix({ class: "pk-dropdown-menu-sub" }, @attrs), &)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# The nested panel of a DropdownMenuSub — opens beside the trigger.
|
|
3
|
+
# See dropdown_menu.rb.
|
|
4
|
+
class DropdownMenuSubContent < BaseComponent
|
|
5
|
+
def initialize(**attrs) = (@attrs = attrs)
|
|
6
|
+
def view_template(&)
|
|
7
|
+
div(**mix({ class: "pk-dropdown-menu-sub-content", role: "menu" }, @attrs)) do
|
|
8
|
+
div(class: "pk-dropdown-menu-viewport", &)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# The row that opens a DropdownMenuSub (trailing ▸ chevron). See
|
|
3
|
+
# dropdown_menu.rb.
|
|
4
|
+
class DropdownMenuSubTrigger < BaseComponent
|
|
5
|
+
def initialize(**attrs) = (@attrs = attrs)
|
|
6
|
+
def view_template(&block)
|
|
7
|
+
div(**mix({
|
|
8
|
+
class: "pk-dropdown-menu-item pk-dropdown-menu-sub-trigger",
|
|
9
|
+
role: "menuitem",
|
|
10
|
+
tabindex: "0",
|
|
11
|
+
aria: { haspopup: "menu" }
|
|
12
|
+
}, @attrs)) do
|
|
13
|
+
block&.call
|
|
14
|
+
render Icon.new(:chevron_right, size: nil, class: "pk-dropdown-menu-sub-chevron")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# A glyph from the configured icon library (ui.shadcn.com/create's "Icon
|
|
3
|
+
# Library" option): :lucide (default), :tabler, :phosphor, or :remix — set
|
|
4
|
+
# kit-wide via PhlexKit.config.icon_library or per-instance with `library:`.
|
|
5
|
+
# Names are PhlexKit's canonical semantic set (PhlexKit::Icons.names);
|
|
6
|
+
# unknown names/libraries fail loud (KeyError).
|
|
7
|
+
#
|
|
8
|
+
# `size:` emits width/height attributes (default 16, shadcn's size-4); pass
|
|
9
|
+
# `size: nil` to defer sizing to CSS (existing `.pk-x svg { … }` rules).
|
|
10
|
+
# Stroke libraries (lucide/tabler) render stroke="currentColor"; fill
|
|
11
|
+
# libraries (phosphor/remix) render fill="currentColor" — both follow text
|
|
12
|
+
# color. Attrs pass through via mix.
|
|
13
|
+
class Icon < BaseComponent
|
|
14
|
+
def initialize(name, library: nil, size: 16, **attrs)
|
|
15
|
+
@name = name
|
|
16
|
+
@library = library
|
|
17
|
+
@size = size
|
|
18
|
+
@attrs = attrs
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def view_template
|
|
22
|
+
icon = Icons.fetch(@name, library: @library || PhlexKit.config.icon_library)
|
|
23
|
+
svg(**mix(base_attrs(icon), @attrs)) do |s|
|
|
24
|
+
icon[:elements].each { |tag, attrs| s.public_send(tag, **attrs) }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def base_attrs(icon)
|
|
31
|
+
base = {
|
|
32
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
33
|
+
viewbox: icon[:view_box],
|
|
34
|
+
class: "pk-icon",
|
|
35
|
+
"aria-hidden": "true"
|
|
36
|
+
}
|
|
37
|
+
base[:width] = base[:height] = @size.to_s if @size
|
|
38
|
+
if icon[:mode] == :stroke
|
|
39
|
+
base.merge(
|
|
40
|
+
fill: "none",
|
|
41
|
+
stroke: "currentColor",
|
|
42
|
+
"stroke-width": icon[:stroke_width].to_s,
|
|
43
|
+
"stroke-linecap": "round",
|
|
44
|
+
"stroke-linejoin": "round"
|
|
45
|
+
)
|
|
46
|
+
else
|
|
47
|
+
base.merge(fill: "currentColor")
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/* Co-located with marker.rb — UI::Marker, shadcn parity: muted inline status,
|
|
2
|
+
bordered system-note row, or labeled separator in a conversation. */
|
|
3
|
+
.pk-marker {
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
gap: .5rem;
|
|
7
|
+
min-height: 1.25rem;
|
|
8
|
+
color: var(--pk-muted);
|
|
9
|
+
font-size: .875rem;
|
|
10
|
+
}
|
|
11
|
+
.pk-marker-icon { display: inline-flex; flex: none; }
|
|
12
|
+
.pk-marker-icon svg { width: 1rem; height: 1rem; }
|
|
13
|
+
.pk-marker-content { min-width: 0; }
|
|
14
|
+
.pk-marker.border {
|
|
15
|
+
border: 1px solid var(--pk-border);
|
|
16
|
+
border-radius: var(--pk-radius);
|
|
17
|
+
padding: .5rem .625rem;
|
|
18
|
+
}
|
|
19
|
+
.pk-marker.separator { justify-content: center; }
|
|
20
|
+
.pk-marker.separator::before, .pk-marker.separator::after {
|
|
21
|
+
content: "";
|
|
22
|
+
flex: 1;
|
|
23
|
+
height: 1px;
|
|
24
|
+
background: var(--pk-border);
|
|
25
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Conversation annotation, ported from shadcn/ui's Marker (an AI-chat-era
|
|
3
|
+
# addition): an inline status line, bordered system-note row, or labeled
|
|
4
|
+
# separator. Compose Marker(variant:) > [MarkerIcon +] MarkerContent.
|
|
5
|
+
# `.pk-marker*` (marker.css).
|
|
6
|
+
class Marker < BaseComponent
|
|
7
|
+
VARIANTS = { default: nil, border: "border", separator: "separator" }.freeze
|
|
8
|
+
|
|
9
|
+
def initialize(variant: :default, **attrs)
|
|
10
|
+
@variant = variant.to_sym
|
|
11
|
+
@attrs = attrs
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def view_template(&)
|
|
15
|
+
div(**mix({ class: [ "pk-marker", VARIANTS.fetch(@variant) ].compact.join(" ") }, @attrs), &)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -17,16 +17,7 @@ module PhlexKit
|
|
|
17
17
|
private
|
|
18
18
|
|
|
19
19
|
def chevron
|
|
20
|
-
|
|
21
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
22
|
-
viewbox: "0 0 24 24",
|
|
23
|
-
fill: "none",
|
|
24
|
-
stroke: "currentColor",
|
|
25
|
-
"stroke-width": "2",
|
|
26
|
-
"stroke-linecap": "round",
|
|
27
|
-
"stroke-linejoin": "round",
|
|
28
|
-
"aria-hidden": "true"
|
|
29
|
-
) { |s| s.path(d: "m6 9 6 6 6-6") }
|
|
20
|
+
render Icon.new(:chevron_down, size: nil)
|
|
30
21
|
end
|
|
31
22
|
end
|
|
32
23
|
end
|
|
@@ -17,11 +17,7 @@ module PhlexKit
|
|
|
17
17
|
private
|
|
18
18
|
|
|
19
19
|
def chevron
|
|
20
|
-
|
|
21
|
-
stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round",
|
|
22
|
-
"stroke-linejoin": "round", class: "pk-navigation-menu-chevron", "aria-hidden": "true") do |s|
|
|
23
|
-
s.path(d: "m6 9 6 6 6-6")
|
|
24
|
-
end
|
|
20
|
+
render Icon.new(:chevron_down, size: nil, class: "pk-navigation-menu-chevron")
|
|
25
21
|
end
|
|
26
22
|
end
|
|
27
23
|
end
|
|
@@ -4,11 +4,7 @@ module PhlexKit
|
|
|
4
4
|
def view_template
|
|
5
5
|
li do
|
|
6
6
|
span(**mix({ class: "pk-pagination-ellipsis", aria: { hidden: true } }, @attrs)) do
|
|
7
|
-
|
|
8
|
-
s.circle(cx: "12", cy: "12", r: "1")
|
|
9
|
-
s.circle(cx: "19", cy: "12", r: "1")
|
|
10
|
-
s.circle(cx: "5", cy: "12", r: "1")
|
|
11
|
-
end
|
|
7
|
+
render Icon.new(:ellipsis, size: nil)
|
|
12
8
|
span(class: "pk-sr-only") { "More pages" }
|
|
13
9
|
end
|
|
14
10
|
end
|
|
@@ -36,17 +36,7 @@ module PhlexKit
|
|
|
36
36
|
private
|
|
37
37
|
|
|
38
38
|
def check_icon
|
|
39
|
-
|
|
40
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
41
|
-
viewbox: "0 0 24 24",
|
|
42
|
-
fill: "none",
|
|
43
|
-
stroke: "currentColor",
|
|
44
|
-
"stroke-width": "2",
|
|
45
|
-
"stroke-linecap": "round",
|
|
46
|
-
"stroke-linejoin": "round",
|
|
47
|
-
class: "pk-select-item-check",
|
|
48
|
-
"aria-hidden": "true"
|
|
49
|
-
) { |s| s.path(d: "M20 6 9 17l-5-5") }
|
|
39
|
+
render Icon.new(:check, size: nil, class: "pk-select-item-check")
|
|
50
40
|
end
|
|
51
41
|
end
|
|
52
42
|
end
|
|
@@ -23,20 +23,7 @@ module PhlexKit
|
|
|
23
23
|
private
|
|
24
24
|
|
|
25
25
|
def icon
|
|
26
|
-
|
|
27
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
28
|
-
viewbox: "0 0 24 24",
|
|
29
|
-
fill: "none",
|
|
30
|
-
stroke: "currentColor",
|
|
31
|
-
"stroke-width": "2",
|
|
32
|
-
"stroke-linecap": "round",
|
|
33
|
-
"stroke-linejoin": "round",
|
|
34
|
-
class: "pk-select-trigger-icon",
|
|
35
|
-
"aria-hidden": "true"
|
|
36
|
-
) do |s|
|
|
37
|
-
s.path(d: "m7 15 5 5 5-5")
|
|
38
|
-
s.path(d: "m7 9 5-5 5 5")
|
|
39
|
-
end
|
|
26
|
+
render Icon.new(:chevrons_up_down, size: nil, class: "pk-select-trigger-icon")
|
|
40
27
|
end
|
|
41
28
|
end
|
|
42
29
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Co-located with sheet.rb. Theme tokens from the global stylesheet. */
|
|
2
2
|
.pk-sheet-trigger { display: inline-block; }
|
|
3
|
-
.pk-sheet-backdrop { position: fixed; inset: 0; z-index: 50; background: rgb(0 0 0 / .5); backdrop-filter: blur(
|
|
3
|
+
.pk-sheet-backdrop { position: fixed; inset: 0; z-index: 50; background: rgb(0 0 0 / .5); backdrop-filter: blur(8px); }
|
|
4
4
|
.pk-sheet-content {
|
|
5
5
|
position: fixed; z-index: 50; display: flex; flex-direction: column; gap: 1rem;
|
|
6
6
|
background: var(--pk-surface); color: var(--pk-text); padding: 1.5rem;
|
|
@@ -12,9 +12,7 @@ module PhlexKit
|
|
|
12
12
|
div(**mix({ class: ["pk-sheet-content", SIDES.fetch(@side)].join(" ") }, @attrs)) do
|
|
13
13
|
block&.call
|
|
14
14
|
button(type: "button", class: "pk-overlay-close", data: { action: "click->phlex-kit--sheet-content#close" }) do
|
|
15
|
-
|
|
16
|
-
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")
|
|
17
|
-
end
|
|
15
|
+
render Icon.new(:x, size: 15)
|
|
18
16
|
span(class: "pk-sr-only") { "Close" }
|
|
19
17
|
end
|
|
20
18
|
end
|
|
@@ -1,23 +1,59 @@
|
|
|
1
1
|
/* Co-located with sidebar.rb — UI::Sidebar (+ Wrapper / Header / Content / Footer
|
|
2
|
-
/ Group /
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
/ Group / GroupLabel / GroupContent / GroupAction / Menu / MenuItem / MenuButton
|
|
3
|
+
/ MenuAction / MenuBadge / MenuSkeleton / MenuSub / MenuSubItem / MenuSubButton
|
|
4
|
+
/ Separator / Input / Inset). ruby_ui's structure at collapsible:none, extended
|
|
5
|
+
to shadcn/ui's static part set; Tailwind → vanilla CSS on the palette tokens.
|
|
6
|
+
The collapsible/offcanvas/icon modes (+ their trigger/rail/Stimulus/cookie/
|
|
7
|
+
Sheet) are deliberately not ported.
|
|
8
|
+
Theme tokens come from the global stylesheet. The sidebar reads the shared
|
|
9
|
+
palette by default; --pk-sidebar[-text|-accent|-border|-primary|-primary-ink]
|
|
10
|
+
are optional per-role overrides (shadcn sidebar-* parity), undefined by
|
|
11
|
+
default. shadcn's sidebar-ring / sidebar-accent-foreground have no consumption
|
|
12
|
+
site here and stay unmapped. */
|
|
6
13
|
.pk-sidebar-wrapper { display: flex; min-height: 100dvh; width: 100%; }
|
|
7
14
|
|
|
8
15
|
.pk-sidebar {
|
|
9
16
|
display: flex;
|
|
10
17
|
flex-direction: column;
|
|
11
|
-
width:
|
|
18
|
+
width: 16rem;
|
|
12
19
|
flex-shrink: 0;
|
|
13
|
-
background: var(--pk-surface);
|
|
14
|
-
border-right: 1px solid var(--pk-border);
|
|
20
|
+
background: var(--pk-sidebar, var(--pk-surface));
|
|
21
|
+
border-right: 1px solid var(--pk-sidebar-border, var(--pk-border));
|
|
15
22
|
}
|
|
16
23
|
|
|
17
24
|
.pk-sidebar-header { display: flex; flex-direction: column; gap: .5rem; padding: 1rem .75rem .25rem; }
|
|
18
25
|
.pk-sidebar-content { display: flex; flex-direction: column; gap: .25rem; flex: 1; min-height: 0; overflow: auto; padding: .5rem .75rem; }
|
|
19
26
|
.pk-sidebar-footer { display: flex; flex-direction: column; gap: .5rem; padding: .75rem; }
|
|
20
|
-
.pk-sidebar-group { display: flex; flex-direction: column; gap: .125rem; }
|
|
27
|
+
.pk-sidebar-group { position: relative; display: flex; flex-direction: column; gap: .125rem; }
|
|
28
|
+
.pk-sidebar-group-label {
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
flex-shrink: 0;
|
|
32
|
+
height: 2rem;
|
|
33
|
+
padding: 0 .5rem;
|
|
34
|
+
border-radius: calc(var(--pk-radius) - 2px);
|
|
35
|
+
font-size: .75rem;
|
|
36
|
+
font-weight: 500;
|
|
37
|
+
color: color-mix(in oklab, var(--pk-sidebar-text, var(--pk-text)) 70%, transparent);
|
|
38
|
+
}
|
|
39
|
+
.pk-sidebar-group-content { width: 100%; font-size: .875rem; }
|
|
40
|
+
.pk-sidebar-group-action {
|
|
41
|
+
position: absolute;
|
|
42
|
+
top: .375rem;
|
|
43
|
+
right: .25rem;
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
justify-content: center;
|
|
47
|
+
aspect-ratio: 1;
|
|
48
|
+
width: 1.25rem;
|
|
49
|
+
padding: 0;
|
|
50
|
+
border: 0;
|
|
51
|
+
border-radius: calc(var(--pk-radius) - 2px);
|
|
52
|
+
background: transparent;
|
|
53
|
+
color: var(--pk-sidebar-text, var(--pk-text));
|
|
54
|
+
cursor: pointer;
|
|
55
|
+
}
|
|
56
|
+
.pk-sidebar-group-action:hover { background: var(--pk-sidebar-accent, var(--pk-accent)); }
|
|
21
57
|
|
|
22
58
|
.pk-sidebar-menu { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .125rem; width: 100%; }
|
|
23
59
|
.pk-sidebar-menu-item { position: relative; }
|
|
@@ -30,13 +66,95 @@
|
|
|
30
66
|
border: 0;
|
|
31
67
|
border-radius: 8px;
|
|
32
68
|
background: transparent;
|
|
33
|
-
color: var(--pk-text);
|
|
69
|
+
color: var(--pk-sidebar-text, var(--pk-text));
|
|
34
70
|
font: inherit;
|
|
35
71
|
font-size: .9rem;
|
|
36
72
|
text-align: left;
|
|
73
|
+
text-decoration: none;
|
|
37
74
|
cursor: pointer;
|
|
38
75
|
}
|
|
39
|
-
.pk-sidebar-menu-button:hover { background: var(--pk-accent); text-decoration: none; }
|
|
40
|
-
|
|
76
|
+
.pk-sidebar-menu-button:hover { background: var(--pk-sidebar-accent, var(--pk-accent)); text-decoration: none; }
|
|
77
|
+
/* Active item: shadcn's stock "Default" menu look (accent tint). The create
|
|
78
|
+
page's "Solid" mode is the .menu-solid modifier on the sidebar root. */
|
|
79
|
+
.pk-sidebar-menu-button[data-active="true"] { background: var(--pk-sidebar-accent, var(--pk-accent)); color: var(--pk-sidebar-text, var(--pk-text)); font-weight: 500; }
|
|
80
|
+
.pk-sidebar.menu-solid .pk-sidebar-menu-button[data-active="true"] { background: var(--pk-sidebar-primary, var(--pk-brand)); color: var(--pk-sidebar-primary-ink, var(--pk-brand-ink)); font-weight: 600; }
|
|
81
|
+
|
|
82
|
+
/* Anchored to the top of the item (the button row) — the item may also
|
|
83
|
+
contain a submenu, so 50%-of-item would drift. */
|
|
84
|
+
.pk-sidebar-menu-action {
|
|
85
|
+
position: absolute;
|
|
86
|
+
top: .5rem;
|
|
87
|
+
right: .25rem;
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
justify-content: center;
|
|
91
|
+
aspect-ratio: 1;
|
|
92
|
+
width: 1.25rem;
|
|
93
|
+
padding: 0;
|
|
94
|
+
border: 0;
|
|
95
|
+
border-radius: calc(var(--pk-radius) - 2px);
|
|
96
|
+
background: transparent;
|
|
97
|
+
color: var(--pk-sidebar-text, var(--pk-text));
|
|
98
|
+
cursor: pointer;
|
|
99
|
+
}
|
|
100
|
+
.pk-sidebar-menu-action:hover { background: var(--pk-sidebar-accent, var(--pk-accent)); }
|
|
101
|
+
.pk-sidebar-menu-badge {
|
|
102
|
+
pointer-events: none;
|
|
103
|
+
user-select: none;
|
|
104
|
+
position: absolute;
|
|
105
|
+
top: .5rem;
|
|
106
|
+
right: .25rem;
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: center;
|
|
109
|
+
justify-content: center;
|
|
110
|
+
height: 1.25rem;
|
|
111
|
+
min-width: 1.25rem;
|
|
112
|
+
padding: 0 .25rem;
|
|
113
|
+
border-radius: calc(var(--pk-radius) - 2px);
|
|
114
|
+
font-size: .75rem;
|
|
115
|
+
font-weight: 500;
|
|
116
|
+
font-variant-numeric: tabular-nums;
|
|
117
|
+
color: var(--pk-sidebar-text, var(--pk-text));
|
|
118
|
+
}
|
|
119
|
+
.pk-sidebar-menu-skeleton { display: flex; align-items: center; gap: .5rem; height: 2rem; padding: 0 .5rem; border-radius: calc(var(--pk-radius) - 2px); }
|
|
120
|
+
.pk-sidebar-menu-skeleton .pk-skeleton.icon { width: 1rem; height: 1rem; flex-shrink: 0; }
|
|
121
|
+
.pk-sidebar-menu-skeleton .pk-skeleton.text { height: 1rem; flex: 1; max-width: var(--pk-skeleton-width, 70%); }
|
|
122
|
+
|
|
123
|
+
.pk-sidebar-menu-sub {
|
|
124
|
+
list-style: none;
|
|
125
|
+
display: flex;
|
|
126
|
+
flex-direction: column;
|
|
127
|
+
gap: .25rem;
|
|
128
|
+
min-width: 0;
|
|
129
|
+
margin: 0 .875rem;
|
|
130
|
+
padding: .125rem .625rem;
|
|
131
|
+
border-left: 1px solid var(--pk-sidebar-border, var(--pk-border));
|
|
132
|
+
}
|
|
133
|
+
.pk-sidebar-menu-sub-item { position: relative; }
|
|
134
|
+
.pk-sidebar-menu-sub-button {
|
|
135
|
+
display: flex;
|
|
136
|
+
align-items: center;
|
|
137
|
+
gap: .5rem;
|
|
138
|
+
height: 1.75rem;
|
|
139
|
+
min-width: 0;
|
|
140
|
+
width: 100%;
|
|
141
|
+
padding: 0 .5rem;
|
|
142
|
+
overflow: hidden;
|
|
143
|
+
border: 0;
|
|
144
|
+
border-radius: calc(var(--pk-radius) - 2px);
|
|
145
|
+
background: transparent;
|
|
146
|
+
color: var(--pk-sidebar-text, var(--pk-text));
|
|
147
|
+
font: inherit;
|
|
148
|
+
font-size: .875rem;
|
|
149
|
+
text-align: left;
|
|
150
|
+
text-decoration: none;
|
|
151
|
+
cursor: pointer;
|
|
152
|
+
}
|
|
153
|
+
.pk-sidebar-menu-sub-button:hover { background: var(--pk-sidebar-accent, var(--pk-accent)); text-decoration: none; }
|
|
154
|
+
/* shadcn highlights active sub-items with the accent role, not primary. */
|
|
155
|
+
.pk-sidebar-menu-sub-button[data-active="true"] { background: var(--pk-sidebar-accent, var(--pk-accent)); font-weight: 600; }
|
|
156
|
+
|
|
157
|
+
.pk-separator.pk-sidebar-separator { width: auto; margin: 0 .5rem; background: var(--pk-sidebar-border, var(--pk-border)); }
|
|
158
|
+
.pk-input.pk-sidebar-input { height: 2rem; width: 100%; background: var(--pk-bg); box-shadow: none; }
|
|
41
159
|
|
|
42
160
|
.pk-sidebar-inset { flex: 1; min-width: 0; }
|
|
@@ -3,17 +3,30 @@ module PhlexKit
|
|
|
3
3
|
# (the NonCollapsibleSidebar path): a static flex-column rail. Deliberately
|
|
4
4
|
# WITHOUT ruby_ui's collapsible/offcanvas Stimulus controller + cookie state +
|
|
5
5
|
# mobile Sheet (the admin sidebar is static) — those can be added later if a
|
|
6
|
-
# collapsible chrome is wanted.
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
6
|
+
# collapsible chrome is wanted. The static part set matches shadcn/ui's Sidebar:
|
|
7
|
+
# compose with SidebarWrapper (the page root) + SidebarHeader / SidebarContent /
|
|
8
|
+
# SidebarGroup (+ Label / Content / Action) / SidebarMenu (+ Item / Button /
|
|
9
|
+
# Action / Badge / Skeleton / Sub / SubItem / SubButton) / SidebarSeparator /
|
|
10
|
+
# SidebarInput / SidebarFooter, alongside a SidebarInset for the main content.
|
|
11
|
+
# Tailwind → vanilla `.pk-sidebar*` (sidebar.css).
|
|
10
12
|
class Sidebar < BaseComponent
|
|
11
|
-
|
|
13
|
+
# Active-item treatment (ui.shadcn.com/create's "Menu" option): :default is
|
|
14
|
+
# shadcn's stock accent tint; :solid fills with the primary/brand role.
|
|
15
|
+
MENUS = { default: nil, solid: "menu-solid" }.freeze
|
|
16
|
+
|
|
17
|
+
def initialize(menu: :default, **attrs)
|
|
18
|
+
@menu = menu.to_sym
|
|
12
19
|
@attrs = attrs
|
|
13
20
|
end
|
|
14
21
|
|
|
15
22
|
def view_template(&block)
|
|
16
|
-
div(**mix({ class:
|
|
23
|
+
div(**mix({ class: classes }, @attrs), &block)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def classes
|
|
29
|
+
[ "pk-sidebar", MENUS.fetch(@menu) ].compact.join(" ")
|
|
17
30
|
end
|
|
18
31
|
end
|
|
19
32
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Action button pinned to the top-right of a SidebarGroup (the group is the
|
|
3
|
+
# positioning context), ported from shadcn/ui's SidebarGroupAction.
|
|
4
|
+
# See sidebar.rb.
|
|
5
|
+
class SidebarGroupAction < BaseComponent
|
|
6
|
+
def initialize(**attrs)
|
|
7
|
+
@attrs = attrs
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def view_template(&block)
|
|
11
|
+
button(**mix({ class: "pk-sidebar-group-action", type: "button" }, @attrs), &block)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Content container within a SidebarGroup, ported from shadcn/ui's
|
|
3
|
+
# SidebarGroupContent. See sidebar.rb.
|
|
4
|
+
class SidebarGroupContent < BaseComponent
|
|
5
|
+
def initialize(**attrs)
|
|
6
|
+
@attrs = attrs
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def view_template(&block)
|
|
10
|
+
div(**mix({ class: "pk-sidebar-group-content" }, @attrs), &block)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Heading for a SidebarGroup, ported from shadcn/ui's SidebarGroupLabel.
|
|
3
|
+
# See sidebar.rb.
|
|
4
|
+
class SidebarGroupLabel < BaseComponent
|
|
5
|
+
def initialize(**attrs)
|
|
6
|
+
@attrs = attrs
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def view_template(&block)
|
|
10
|
+
div(**mix({ class: "pk-sidebar-group-label" }, @attrs), &block)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Search/filter input for the sidebar, ported from shadcn/ui's SidebarInput:
|
|
3
|
+
# the kit Input on the page background, sized to the sidebar's control height.
|
|
4
|
+
# See sidebar.rb.
|
|
5
|
+
class SidebarInput < BaseComponent
|
|
6
|
+
def initialize(**attrs)
|
|
7
|
+
@attrs = attrs
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def view_template
|
|
11
|
+
render Input.new(**mix({ class: "pk-sidebar-input" }, @attrs))
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Secondary action pinned to the right edge of a SidebarMenuItem (the item is
|
|
3
|
+
# position:relative), ported from shadcn/ui's SidebarMenuAction. See sidebar.rb.
|
|
4
|
+
class SidebarMenuAction < BaseComponent
|
|
5
|
+
def initialize(**attrs)
|
|
6
|
+
@attrs = attrs
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def view_template(&block)
|
|
10
|
+
button(**mix({ class: "pk-sidebar-menu-action", type: "button" }, @attrs), &block)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Non-interactive count/status pinned to the right edge of a SidebarMenuItem,
|
|
3
|
+
# ported from shadcn/ui's SidebarMenuBadge. See sidebar.rb.
|
|
4
|
+
class SidebarMenuBadge < BaseComponent
|
|
5
|
+
def initialize(**attrs)
|
|
6
|
+
@attrs = attrs
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def view_template(&block)
|
|
10
|
+
span(**mix({ class: "pk-sidebar-menu-badge" }, @attrs), &block)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Loading placeholder for a menu item, ported from shadcn/ui's
|
|
3
|
+
# SidebarMenuSkeleton: an optional icon shimmer plus a text shimmer whose
|
|
4
|
+
# width randomises 50–90% per render (their --skeleton-width trick).
|
|
5
|
+
# `show_icon:` mirrors shadcn's showIcon. See sidebar.rb.
|
|
6
|
+
class SidebarMenuSkeleton < BaseComponent
|
|
7
|
+
def initialize(show_icon: false, **attrs)
|
|
8
|
+
@show_icon = show_icon
|
|
9
|
+
@attrs = attrs
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def view_template
|
|
13
|
+
div(**mix({ class: "pk-sidebar-menu-skeleton" }, @attrs)) do
|
|
14
|
+
render Skeleton.new(class: "icon") if @show_icon
|
|
15
|
+
render Skeleton.new(class: "text", style: "--pk-skeleton-width: #{rand(50..90)}%;")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Nested submenu list under a SidebarMenuItem, ported from shadcn/ui's
|
|
3
|
+
# SidebarMenuSub. See sidebar.rb.
|
|
4
|
+
class SidebarMenuSub < BaseComponent
|
|
5
|
+
def initialize(**attrs)
|
|
6
|
+
@attrs = attrs
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def view_template(&block)
|
|
10
|
+
ul(**mix({ class: "pk-sidebar-menu-sub" }, @attrs), &block)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# The interactive element of a SidebarMenuSubItem, ported from shadcn/ui's
|
|
3
|
+
# SidebarMenuSubButton. `as:` is :a (their default — pass `href:`) or :button;
|
|
4
|
+
# `active: true` highlights with the accent role (shadcn subs use accent, not
|
|
5
|
+
# primary). Kwarg rather than attr for the same mix-merge reason as
|
|
6
|
+
# SidebarMenuButton. See sidebar.rb.
|
|
7
|
+
class SidebarMenuSubButton < BaseComponent
|
|
8
|
+
def initialize(as: :a, active: false, **attrs)
|
|
9
|
+
@as = as
|
|
10
|
+
@active = active
|
|
11
|
+
@attrs = attrs
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def view_template(&block)
|
|
15
|
+
send(@as, **mix({ class: "pk-sidebar-menu-sub-button", "data-active": (@active ? "true" : "false") }, @attrs), &block)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# An item within a SidebarMenuSub, ported from shadcn/ui's SidebarMenuSubItem.
|
|
3
|
+
# See sidebar.rb.
|
|
4
|
+
class SidebarMenuSubItem < BaseComponent
|
|
5
|
+
def initialize(**attrs)
|
|
6
|
+
@attrs = attrs
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def view_template(&block)
|
|
10
|
+
li(**mix({ class: "pk-sidebar-menu-sub-item" }, @attrs), &block)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|