phlex_kit 0.2.1 → 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.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +81 -6
  3. data/THIRD_PARTY_LICENSES +104 -0
  4. data/app/assets/stylesheets/phlex_kit/_tokens.css +72 -0
  5. data/app/assets/stylesheets/phlex_kit/phlex_kit.css +1 -1
  6. data/app/assets/stylesheets/phlex_kit/themes/claude.css +86 -0
  7. data/app/assets/stylesheets/phlex_kit/themes/neutral.css +95 -0
  8. data/app/assets/stylesheets/phlex_kit/themes/zinc.css +85 -0
  9. data/app/components/phlex_kit/accordion/accordion.css +13 -0
  10. data/app/components/phlex_kit/accordion/accordion.rb +11 -2
  11. data/app/components/phlex_kit/accordion/accordion_icon.rb +1 -3
  12. data/app/components/phlex_kit/accordion/accordion_item.rb +5 -2
  13. data/app/components/phlex_kit/alert_dialog/alert_dialog.css +45 -4
  14. data/app/components/phlex_kit/alert_dialog/alert_dialog_action.rb +3 -2
  15. data/app/components/phlex_kit/alert_dialog/alert_dialog_content.rb +6 -2
  16. data/app/components/phlex_kit/alert_dialog/alert_dialog_media.rb +14 -0
  17. data/app/components/phlex_kit/attachment/attachment.css +102 -2
  18. data/app/components/phlex_kit/attachment/attachment.rb +18 -6
  19. data/app/components/phlex_kit/attachment/attachment_action.rb +1 -6
  20. data/app/components/phlex_kit/attachment/attachment_group.rb +21 -0
  21. data/app/components/phlex_kit/attachment/attachment_media.rb +13 -4
  22. data/app/components/phlex_kit/attachment/attachment_trigger.rb +22 -0
  23. data/app/components/phlex_kit/breadcrumb/breadcrumb_ellipsis.rb +1 -5
  24. data/app/components/phlex_kit/breadcrumb/breadcrumb_separator.rb +1 -1
  25. data/app/components/phlex_kit/calendar/calendar_next.rb +1 -15
  26. data/app/components/phlex_kit/calendar/calendar_prev.rb +1 -15
  27. data/app/components/phlex_kit/card/card.css +50 -14
  28. data/app/components/phlex_kit/card/card.rb +6 -2
  29. data/app/components/phlex_kit/card/card_action.rb +14 -0
  30. data/app/components/phlex_kit/carousel/carousel_next.rb +1 -15
  31. data/app/components/phlex_kit/carousel/carousel_previous.rb +1 -15
  32. data/app/components/phlex_kit/codeblock/codeblock.css +1 -1
  33. data/app/components/phlex_kit/combobox/combobox_clear_button.rb +1 -15
  34. data/app/components/phlex_kit/combobox/combobox_input_trigger.rb +1 -12
  35. data/app/components/phlex_kit/combobox/combobox_item_indicator.rb +1 -15
  36. data/app/components/phlex_kit/combobox/combobox_search_input.rb +1 -14
  37. data/app/components/phlex_kit/combobox/combobox_trigger.rb +1 -14
  38. data/app/components/phlex_kit/command/command.css +2 -2
  39. data/app/components/phlex_kit/command/command_input.rb +1 -13
  40. data/app/components/phlex_kit/context_menu/context_menu_item.rb +1 -3
  41. data/app/components/phlex_kit/data_table/data_table_column_toggle.rb +1 -13
  42. data/app/components/phlex_kit/data_table/data_table_expand_toggle.rb +1 -12
  43. data/app/components/phlex_kit/data_table/data_table_sort_head.rb +2 -28
  44. data/app/components/phlex_kit/dialog/dialog.css +8 -2
  45. data/app/components/phlex_kit/dialog/dialog_content.rb +1 -3
  46. data/app/components/phlex_kit/drawer/drawer.css +1 -0
  47. data/app/components/phlex_kit/dropdown_menu/dropdown_menu.css +64 -0
  48. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_checkbox_item.rb +31 -0
  49. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_group.rb +11 -0
  50. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_item.rb +5 -2
  51. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_radio_group.rb +10 -0
  52. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_radio_item.rb +31 -0
  53. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_shortcut.rb +10 -0
  54. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_sub.rb +11 -0
  55. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_sub_content.rb +12 -0
  56. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_sub_trigger.rb +18 -0
  57. data/app/components/phlex_kit/icon/icon.rb +51 -0
  58. data/app/components/phlex_kit/marker/marker.css +25 -0
  59. data/app/components/phlex_kit/marker/marker.rb +18 -0
  60. data/app/components/phlex_kit/marker/marker_content.rb +7 -0
  61. data/app/components/phlex_kit/marker/marker_icon.rb +7 -0
  62. data/app/components/phlex_kit/native_select/native_select_icon.rb +1 -10
  63. data/app/components/phlex_kit/navigation_menu/navigation_menu_trigger.rb +1 -5
  64. data/app/components/phlex_kit/pagination/pagination_ellipsis.rb +1 -5
  65. data/app/components/phlex_kit/select/select_item.rb +1 -11
  66. data/app/components/phlex_kit/select/select_trigger.rb +1 -14
  67. data/app/components/phlex_kit/sheet/sheet.css +1 -1
  68. data/app/components/phlex_kit/sheet/sheet_content.rb +1 -3
  69. data/app/components/phlex_kit/sidebar/sidebar.css +129 -11
  70. data/app/components/phlex_kit/sidebar/sidebar.rb +19 -6
  71. data/app/components/phlex_kit/sidebar/sidebar_group_action.rb +14 -0
  72. data/app/components/phlex_kit/sidebar/sidebar_group_content.rb +13 -0
  73. data/app/components/phlex_kit/sidebar/sidebar_group_label.rb +13 -0
  74. data/app/components/phlex_kit/sidebar/sidebar_input.rb +14 -0
  75. data/app/components/phlex_kit/sidebar/sidebar_menu_action.rb +13 -0
  76. data/app/components/phlex_kit/sidebar/sidebar_menu_badge.rb +13 -0
  77. data/app/components/phlex_kit/sidebar/sidebar_menu_skeleton.rb +19 -0
  78. data/app/components/phlex_kit/sidebar/sidebar_menu_sub.rb +13 -0
  79. data/app/components/phlex_kit/sidebar/sidebar_menu_sub_button.rb +18 -0
  80. data/app/components/phlex_kit/sidebar/sidebar_menu_sub_item.rb +13 -0
  81. data/app/components/phlex_kit/sidebar/sidebar_separator.rb +14 -0
  82. data/app/components/phlex_kit/spinner/spinner.rb +5 -10
  83. data/app/components/phlex_kit/toast/toast_close.rb +1 -15
  84. data/app/components/phlex_kit/toast/toast_icon.rb +12 -36
  85. data/app/components/phlex_kit/typography/heading.rb +1 -1
  86. data/app/components/phlex_kit/typography/typography.css +7 -3
  87. data/app/javascript/phlex_kit/controllers/accordion_controller.js +17 -1
  88. data/app/javascript/phlex_kit/controllers/index.js +2 -0
  89. data/app/javascript/phlex_kit/controllers/scroll_fade_controller.js +31 -0
  90. data/lib/phlex_kit/configuration.rb +7 -0
  91. data/lib/phlex_kit/icons/lucide.rb +88 -0
  92. data/lib/phlex_kit/icons/phosphor.rb +88 -0
  93. data/lib/phlex_kit/icons/remix.rb +88 -0
  94. data/lib/phlex_kit/icons/tabler.rb +88 -0
  95. data/lib/phlex_kit/icons.rb +40 -0
  96. data/lib/phlex_kit/version.rb +1 -1
  97. data/lib/phlex_kit.rb +1 -0
  98. metadata +45 -8
  99. data/app/components/phlex_kit/stars/stars.css +0 -4
  100. data/app/components/phlex_kit/stars/stars.rb +0 -19
@@ -36,17 +36,7 @@ module PhlexKit
36
36
  private
37
37
 
38
38
  def check_icon
39
- svg(
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
- svg(
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(2px); }
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
- svg(width: "15", height: "15", viewbox: "0 0 15 15", fill: "none", xmlns: "http://www.w3.org/2000/svg") do |s|
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 / Menu / MenuItem / MenuButton / Inset). ruby_ui's structure at
3
- collapsible:none, Tailwind vanilla CSS on the palette tokens. The collapsible/
4
- offcanvas/icon modes (+ their Stimulus/cookie/Sheet) are deliberately not ported.
5
- Theme tokens come from the global stylesheet. */
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: 14rem;
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
- .pk-sidebar-menu-button[data-active="true"] { background: var(--pk-brand); color: var(--pk-brand-ink); font-weight: 600; }
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. Compose with SidebarWrapper (the page root) +
7
- # SidebarHeader / SidebarContent / SidebarMenu (+ Item / Button) / SidebarFooter,
8
- # alongside a SidebarInset for the main content. Tailwind vanilla `.pk-sidebar*`
9
- # (sidebar.css).
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
- def initialize(**attrs)
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: "pk-sidebar" }, @attrs), &block)
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
@@ -0,0 +1,14 @@
1
+ module PhlexKit
2
+ # Divider between sidebar sections, ported from shadcn/ui's SidebarSeparator:
3
+ # the kit Separator inset to the sidebar's padding and tinted with the
4
+ # sidebar border role. See sidebar.rb.
5
+ class SidebarSeparator < BaseComponent
6
+ def initialize(**attrs)
7
+ @attrs = attrs
8
+ end
9
+
10
+ def view_template
11
+ render Separator.new(**mix({ class: "pk-sidebar-separator" }, @attrs))
12
+ end
13
+ end
14
+ end
@@ -10,18 +10,13 @@ module PhlexKit
10
10
  end
11
11
 
12
12
  def view_template
13
- svg(**mix({
14
- xmlns: "http://www.w3.org/2000/svg",
15
- viewbox: "0 0 24 24",
16
- fill: "none",
17
- stroke: "currentColor",
18
- "stroke-width": "2",
19
- "stroke-linecap": "round",
20
- "stroke-linejoin": "round",
13
+ # A visible loader, not decoration: role=status + label, aria-hidden off.
14
+ render Icon.new(:loader, size: nil, **mix({
21
15
  class: [ "pk-spinner", SIZES.fetch(@size) ].compact.join(" "),
22
16
  role: "status",
23
- aria: { label: "Loading" }
24
- }, @attrs)) { |s| s.path(d: "M21 12a9 9 0 1 1-6.219-8.56") }
17
+ aria: { label: "Loading" },
18
+ "aria-hidden": false
19
+ }, @attrs))
25
20
  end
26
21
  end
27
22
  end
@@ -13,21 +13,7 @@ module PhlexKit
13
13
  aria: { label: "Close toast" },
14
14
  data: { slot: "close", action: "click->phlex-kit--toast#dismiss" }
15
15
  }, @attrs)) do
16
- svg(
17
- xmlns: "http://www.w3.org/2000/svg",
18
- width: "14",
19
- height: "14",
20
- viewbox: "0 0 24 24",
21
- fill: "none",
22
- stroke: "currentColor",
23
- "stroke-width": "2",
24
- "stroke-linecap": "round",
25
- "stroke-linejoin": "round",
26
- "aria-hidden": "true"
27
- ) do |s|
28
- s.path(d: "M18 6 6 18")
29
- s.path(d: "m6 6 12 12")
30
- end
16
+ render Icon.new(:x, size: 14)
31
17
  span(class: "pk-sr-only") { "Close" }
32
18
  end
33
19
  end
@@ -9,55 +9,31 @@ module PhlexKit
9
9
  @attrs = attrs
10
10
  end
11
11
 
12
+ # Status → canonical glyph. Error is circle_x (octagon-x has no equivalent
13
+ # outside Lucide, so the cross-library semantic name wins).
14
+ GLYPHS = {
15
+ success: :circle_check,
16
+ error: :circle_x,
17
+ warning: :triangle_alert,
18
+ info: :info,
19
+ loading: :loader
20
+ }.freeze
21
+
12
22
  def view_template
13
23
  return unless renderable?
14
24
  span(**mix({ class: "pk-toast-icon", data: { slot: "icon" } }, @attrs)) do
15
- svg(
16
- xmlns: "http://www.w3.org/2000/svg",
17
- width: "16",
18
- height: "16",
19
- viewbox: "0 0 24 24",
20
- fill: "none",
21
- stroke: "currentColor",
22
- "stroke-width": "2",
23
- "stroke-linecap": "round",
24
- "stroke-linejoin": "round",
25
- class: svg_classes,
26
- "aria-hidden": "true"
27
- ) { |s| paths(s) }
25
+ render Icon.new(GLYPHS.fetch(@variant), size: 16, class: svg_classes)
28
26
  end
29
27
  end
30
28
 
31
29
  private
32
30
 
33
31
  def renderable?
34
- %i[success error warning info loading].include?(@variant)
32
+ GLYPHS.key?(@variant)
35
33
  end
36
34
 
37
35
  def svg_classes
38
36
  [ "pk-toast-icon-svg", ("spin" if @variant == :loading) ].compact.join(" ")
39
37
  end
40
-
41
- def paths(s)
42
- case @variant
43
- when :success
44
- s.circle(cx: "12", cy: "12", r: "10")
45
- s.path(d: "m9 12 2 2 4-4")
46
- when :error
47
- s.path(d: "M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z")
48
- s.path(d: "m15 9-6 6")
49
- s.path(d: "m9 9 6 6")
50
- when :warning
51
- s.path(d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3")
52
- s.path(d: "M12 9v4")
53
- s.path(d: "M12 17h.01")
54
- when :info
55
- s.circle(cx: "12", cy: "12", r: "10")
56
- s.path(d: "M12 16v-4")
57
- s.path(d: "M12 8h.01")
58
- when :loading
59
- s.path(d: "M21 12a9 9 0 1 1-6.219-8.56")
60
- end
61
- end
62
38
  end
63
39
  end
@@ -32,7 +32,7 @@ module PhlexKit
32
32
  end
33
33
 
34
34
  def classes
35
- "pk-heading ui-heading-#{size_token}"
35
+ "pk-heading pk-heading-#{size_token}"
36
36
  end
37
37
  end
38
38
  end
@@ -4,7 +4,7 @@
4
4
  tokens come from the global stylesheet. */
5
5
 
6
6
  /* Heading */
7
- .pk-heading { margin: 0 0 .6rem; font-weight: 700; line-height: 1.2; letter-spacing: -.01em; }
7
+ .pk-heading { margin: 0 0 .6rem; font-weight: 600; line-height: 1.2; letter-spacing: -.025em; }
8
8
  .pk-heading-1 { font-size: .75rem; }
9
9
  .pk-heading-2 { font-size: .875rem; }
10
10
  .pk-heading-3 { font-size: 1rem; }
@@ -34,7 +34,7 @@
34
34
 
35
35
  /* Inline code */
36
36
  .pk-inline-code {
37
- font-family: ui-monospace, monospace;
37
+ font-family: var(--pk-font-mono, ui-monospace, monospace);
38
38
  font-size: .875rem;
39
39
  background: var(--pk-bg);
40
40
  border: 1px solid var(--pk-border);
@@ -47,4 +47,8 @@
47
47
  .pk-inline-link:hover { text-decoration: underline; }
48
48
 
49
49
  /* Blockquote */
50
- .pk-blockquote { margin: 1rem 0; border-left: 2px solid var(--pk-border); padding-left: 1rem; font-style: italic; }
50
+ .pk-blockquote { margin: 1.5rem 0; border-left: 2px solid var(--pk-border); padding-left: 1.5rem; font-style: italic; }
51
+
52
+ /* Prose list (shadcn typography's list style). */
53
+ .pk-list { margin: 1.5rem 0; padding-left: 1.5rem; list-style: disc; }
54
+ .pk-list > li { margin-top: .5rem; }
@@ -18,7 +18,23 @@ export default class extends Controller {
18
18
  this.animationDurationValue = d
19
19
  }
20
20
 
21
- toggle() { this.openValue = !this.openValue }
21
+ toggle() {
22
+ const next = !this.openValue
23
+ if (next) this.closeSiblings()
24
+ this.openValue = next
25
+ }
26
+
27
+ // In a type="single" accordion (shadcn's default), opening one item closes
28
+ // the rest.
29
+ closeSiblings() {
30
+ const root = this.element.closest(".pk-accordion")
31
+ if (!root || root.dataset.type !== "single") return
32
+ root.querySelectorAll('[data-controller~="phlex-kit--accordion"]').forEach((el) => {
33
+ if (el === this.element) return
34
+ const ctrl = this.application.getControllerForElementAndIdentifier(el, "phlex-kit--accordion")
35
+ if (ctrl && ctrl.openValue) ctrl.openValue = false
36
+ })
37
+ }
22
38
  openValueChanged(isOpen) { isOpen ? this.open() : this.close() }
23
39
 
24
40
  open() {
@@ -34,6 +34,7 @@ import MenubarController from "phlex_kit/controllers/menubar_controller"
34
34
  import MessageScrollerController from "phlex_kit/controllers/message_scroller_controller"
35
35
  import PopoverController from "phlex_kit/controllers/popover_controller"
36
36
  import ResizableController from "phlex_kit/controllers/resizable_controller"
37
+ import ScrollFadeController from "phlex_kit/controllers/scroll_fade_controller"
37
38
  import SelectController from "phlex_kit/controllers/select_controller"
38
39
  import SelectItemController from "phlex_kit/controllers/select_item_controller"
39
40
  import SheetController from "phlex_kit/controllers/sheet_controller"
@@ -73,6 +74,7 @@ export function registerPhlexKitControllers(application) {
73
74
  application.register("phlex-kit--message-scroller", MessageScrollerController)
74
75
  application.register("phlex-kit--popover", PopoverController)
75
76
  application.register("phlex-kit--resizable", ResizableController)
77
+ application.register("phlex-kit--scroll-fade", ScrollFadeController)
76
78
  application.register("phlex-kit--select", SelectController)
77
79
  application.register("phlex-kit--select-item", SelectItemController)
78
80
  application.register("phlex-kit--sheet", SheetController)