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.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +210 -49
  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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d165b8e5ecbb24ef238be1c9472db2d19cdcb30079c85e2e839019547d5324fa
4
- data.tar.gz: 89f8058734e7d2745b3ea96e32cea6845e721c4f2e28a8621bc7c4978183c510
3
+ metadata.gz: 0f6988a4f8e3f47462f9f83824ed6542cafded9d656adb10340e7eb618156063
4
+ data.tar.gz: ca293b01074cfa84962df3c80683e3df5ae36cf5a919b1e8c0bcd1861948b52c
5
5
  SHA512:
6
- metadata.gz: 2c1b8608a4175ae249ed98ecae2c0ffccbb1c8a5f07d9fea298bf29e39d0eee3f401f6accd822da8fb42cc194560690fa4ed72e389bd448e89d2f6f008f494de
7
- data.tar.gz: 87773e1fa4628d51cdeef7d93684fe3c91d728fe77f80d3e093fbcfeb1d210d1e176d6fd0f3d89901bb7c2c4ffc67db04104fc44437a017ee7df8cd5d9ca3037
6
+ metadata.gz: fd26ed3b6aef6bf015c7fbdf574516a68e8eb10fe1cfe1356a4292420d01917977428d2b35a301b65e6a8ff19c73daf5ac9ffdfb9af6abae6f96afd4818d7c81
7
+ data.tar.gz: 6d6c3d42730c2ce500a6f8c8e49afab8e11ec192f87bf9d87434761d334b40e1a762cc157ad7b38b2914edbffeae9fea501235f1cdc0ec6937c88a8f843399fa
data/README.md CHANGED
@@ -1,18 +1,28 @@
1
1
  # PhlexKit
2
2
 
3
- A [ruby_ui](https://ruby-ui.com)-style component kit for [Phlex](https://phlex.fun),
4
- styled with **vanilla CSS + design tokens instead of Tailwind**, and built so
5
- [phlex-reactive](https://github.com/mhenrixon/phlex-reactive) is an *optional,
6
- per-component* integration — never a required dependency.
7
-
8
- - **No build step.** Components ship as plain Ruby classes with co-located vanilla
9
- CSS. No Tailwind, no Node, no PostCSS. Assets are precompiled-static in the gem.
10
- - **Theme with CSS custom properties.** Every component reads `--pk-*` tokens via
11
- `var()` + `color-mix()`. Redefine `:root` and the whole kit re-themes live —
12
- including dark/light/system with no rebuild.
13
- - **Reactive when you want it.** Components pass `**attrs` straight through Phlex's
14
- `mix`, so a phlex-reactive `**on(:event)` bundle composes onto the root element
15
- with zero coupling. Non-reactive components never touch phlex-reactive.
3
+ [![Gem Version](https://img.shields.io/gem/v/phlex_kit)](https://rubygems.org/gems/phlex_kit)
4
+ [![CI](https://github.com/MatthewKennedy/phlex_kit/actions/workflows/ci.yml/badge.svg)](https://github.com/MatthewKennedy/phlex_kit/actions/workflows/ci.yml)
5
+
6
+ A [shadcn/ui](https://ui.shadcn.com)-grade component kit for
7
+ [Phlex](https://phlex.fun) + Rails, styled with **vanilla CSS + design tokens
8
+ instead of Tailwind**. The full [ruby_ui](https://ruby-ui.com) catalog plus
9
+ shadcn's own additions 70 component families, 38 Stimulus controllers
10
+ with the default theme lifted verbatim from shadcn/ui's current token system.
11
+
12
+ - **No build step.** Components are plain Ruby classes with co-located vanilla
13
+ CSS, served precompiled-static through Propshaft. No Tailwind, no Node, no
14
+ PostCSS.
15
+ - **The shadcn look, themeable.** Every component reads `--pk-*` custom
16
+ properties. The default palette, radii, and control geometry match
17
+ ui.shadcn.com; redefine `:root` and the whole kit re-themes live — dark,
18
+ light, and system included.
19
+ - **`@hotwired/stimulus` only.** Every interactive component works with one
20
+ registration call. The JS dependencies the upstream kits lean on
21
+ (floating-ui, embla, fuse.js, vaul, Radix, chart.js, …) are all replaced
22
+ with small vanilla equivalents — or, for charts, left to the host.
23
+ - **Reactive when you want it.** Components pass `**attrs` straight through
24
+ Phlex's `mix`, so a [phlex-reactive](https://github.com/mhenrixon/phlex-reactive)
25
+ `**on(:event)` bundle composes onto the root element with zero coupling.
16
26
 
17
27
  ## Install
18
28
 
@@ -28,7 +38,19 @@ bin/rails g phlex_kit:install
28
38
 
29
39
  The installer adds `@import url("phlex_kit/phlex_kit.css");` to your
30
40
  `application.css`, drops `config/initializers/phlex_kit.rb`, and prints the
31
- Stimulus wiring. That's it no Tailwind config, no content globs.
41
+ Stimulus wiring. Then register the controllers once in your Stimulus
42
+ entrypoint:
43
+
44
+ ```js
45
+ // app/javascript/application.js
46
+ import { Application } from "@hotwired/stimulus"
47
+ import { registerPhlexKitControllers } from "phlex_kit/controllers"
48
+
49
+ const application = Application.start()
50
+ registerPhlexKitControllers(application)
51
+ ```
52
+
53
+ That's it — no Tailwind config, no content globs, no bundler.
32
54
 
33
55
  ## Usage
34
56
 
@@ -38,14 +60,41 @@ render PhlexKit::Button.new(variant: :primary, size: :lg) { "Save changes" }
38
60
  render PhlexKit::Card.new do
39
61
  render PhlexKit::CardHeader.new do
40
62
  render PhlexKit::CardTitle.new { "Team" }
63
+ render PhlexKit::CardDescription.new { "Invite and manage members." }
41
64
  end
42
65
  render PhlexKit::CardContent.new { "…" }
43
66
  end
44
67
 
45
- render PhlexKit::Badge.new(variant: :success) { "Live" }
68
+ render PhlexKit::Badge.new(variant: :secondary) { "Live" }
69
+ ```
70
+
71
+ A form field with client-side live validation (fills the error once the
72
+ browser flags the control invalid, clears it as the user types):
73
+
74
+ ```ruby
75
+ render PhlexKit::Form.new(action: "/users", method: "post") do
76
+ render PhlexKit::FormField.new do
77
+ render PhlexKit::FormFieldLabel.new(for: "email") { "Email" }
78
+ render PhlexKit::Input.new(
79
+ type: :email, id: "email", name: "email", required: true,
80
+ data: { value_missing: "Email is required." }
81
+ )
82
+ render PhlexKit::FormFieldError.new
83
+ end
84
+ div(class: "pk-form-actions") do
85
+ render PhlexKit::Button.new(type: :submit) { "Create account" }
86
+ end
87
+ end
88
+ ```
89
+
90
+ A toast, spawned from anywhere (mount `PhlexKit::ToastRegion.new(flash: flash)`
91
+ once at the end of `<body>` — server flash messages render as toasts too):
92
+
93
+ ```js
94
+ PhlexKit.toast.success("Saved", { description: "Your changes are live." })
46
95
  ```
47
96
 
48
- Prefer revue-style `UI::Button`? Turn on the alias in the initializer:
97
+ Prefer `UI::Button`? Turn on the alias in the initializer:
49
98
 
50
99
  ```ruby
51
100
  PhlexKit.configure { |c| c.define_ui_alias = true } # UI == PhlexKit
@@ -53,42 +102,93 @@ PhlexKit.configure { |c| c.define_ui_alias = true } # UI == PhlexKit
53
102
 
54
103
  ## Theming
55
104
 
56
- The gem ships a default dark/light/system token set (`_tokens.css`). Override any
57
- token in your own stylesheet — your app's CSS sorts ahead of the gem's, so you win:
105
+ The gem ships shadcn/ui's default (neutral) token set dark by default, light
106
+ via `<html data-theme="light">`, OS-following via `data-theme="system"`.
107
+ Override any token in your own stylesheet; your app's CSS sorts ahead of the
108
+ gem's, so you win:
58
109
 
59
110
  ```css
60
111
  @import url("phlex_kit/phlex_kit.css");
61
112
 
62
113
  :root {
63
- --pk-brand: #3b5bdb;
64
- --pk-radius: 6px;
114
+ --pk-brand: #3b5bdb; /* primary buttons, selected states, slider fill */
115
+ --pk-radius: 0.375rem; /* every component radius derives from this */
65
116
  }
66
117
  ```
67
118
 
68
- Want a completely custom palette? Delete the `_tokens` import line from your
69
- manifest and define the `--pk-*` properties yourself. Every component has a
70
- literal fallback in `var(--pk-*, …)`, so nothing breaks if a token is missing.
119
+ The full token surface: `--pk-bg`, `--pk-surface`, `--pk-surface-2`,
120
+ `--pk-accent` (hover fills), `--pk-border`, `--pk-input` (control borders),
121
+ `--pk-ring` (focus rings), `--pk-text`, `--pk-text-2`, `--pk-muted`,
122
+ `--pk-brand`, `--pk-brand-ink`, `--pk-green`, `--pk-amber`, `--pk-red`,
123
+ `--pk-chart-1..5`, `--pk-radius`, plus `--pk-font-sans` / `--pk-font-mono`
124
+ (system stacks by default — override them after an @font-face to adopt Geist
125
+ like shadcn, as the docs site does). Want a fully custom palette? Drop the
126
+ `_tokens.css` import and define them all yourself.
71
127
 
72
- ## Interactive components (Stimulus)
128
+ Prefer a ready-made palette? The gem ships theme files under
129
+ `app/assets/stylesheets/phlex_kit/themes/` (`neutral`, `zinc`, `claude`) —
130
+ link one after the manifest and the cascade re-themes everything:
73
131
 
74
- Dialog, Dropdown, Select, and Avatar ship plain Stimulus controllers (no
75
- phlex-reactive needed). Register them once in your Stimulus entrypoint:
132
+ ```ruby
133
+ stylesheet_link_tag "phlex_kit/phlex_kit"
134
+ stylesheet_link_tag "phlex_kit/themes/neutral"
135
+ ```
76
136
 
77
- ```js
78
- import { registerPhlexKitControllers } from "phlex_kit/controllers"
79
- registerPhlexKitControllers(application)
137
+ The three knobs from ui.shadcn.com/create map directly: **Radius** →
138
+ `--pk-radius`; **Icon Library** `PhlexKit.config.icon_library` (see Icons
139
+ below); **Menu** → `PhlexKit::Sidebar.new(menu: :default | :solid)`.
140
+
141
+ ## Icons
142
+
143
+ Every built-in glyph renders through `PhlexKit::Icon`, which draws from a
144
+ vendored icon library — pick one kit-wide in an initializer:
145
+
146
+ ```ruby
147
+ PhlexKit.configure do |c|
148
+ c.icon_library = :tabler # :lucide (default), :tabler, :phosphor, :remix
149
+ end
80
150
  ```
81
151
 
82
- ## phlex-reactive (optional)
152
+ Use it directly with canonical semantic names (the per-library vocabulary is
153
+ pre-resolved — `:chevron_down` is Phosphor's `caret-down`, Remix's
154
+ `arrow-down-s-line`, and so on):
155
+
156
+ ```ruby
157
+ render PhlexKit::Icon.new(:calendar) # 16px, configured library
158
+ render PhlexKit::Icon.new(:search, size: 24)
159
+ render PhlexKit::Icon.new(:check, library: :phosphor) # per-instance override
160
+ ```
83
161
 
84
- For components that own server-state behavior (live counters, moderation queues,
85
- cross-tab updates), add phlex-reactive and include its mixin in a component:
162
+ `PhlexKit::Icons.names` lists the full catalog (~70 glyphs, all guaranteed to
163
+ resolve in all four libraries); unknown names fail loud. HugeIcons is not
164
+ included — its free set forbids redistributing the artwork inside a gem. Icon
165
+ path data licenses ship in `THIRD_PARTY_LICENSES` (Lucide ISC, Tabler MIT,
166
+ Phosphor MIT, Remix Apache-2.0).
167
+
168
+ ## Charts
169
+
170
+ `PhlexKit::Chart` is deliberately a thin wrapper — **no charting library is
171
+ bundled**. Expose [Chart.js](https://www.chartjs.org) as `window.Chart` (a
172
+ vendored UMD file and one `javascript_include_tag` is enough) and the kit
173
+ builds the chart with shadcn-style theming: series colored from
174
+ `--pk-chart-1..5`, translucent area fills, hairline grids, re-rendering on
175
+ theme change.
86
176
 
87
177
  ```ruby
88
- # Gemfile
89
- gem "phlex-reactive"
178
+ render PhlexKit::Chart.new(options: {
179
+ type: "line",
180
+ data: { labels: %w[Jan Feb Mar], datasets: [ { label: "Orders", data: [3, 7, 4], fill: true } ] }
181
+ })
90
182
  ```
91
183
 
184
+ No `window.Chart`? The controller dispatches `phlex-kit--chart:connect` with
185
+ `{ canvas, options }` so you can drive any other library.
186
+
187
+ ## phlex-reactive (optional)
188
+
189
+ For components that own server-state behavior, add phlex-reactive and include
190
+ its mixin — PhlexKit does **not** depend on it:
191
+
92
192
  ```ruby
93
193
  class MyCounter < PhlexKit::BaseComponent
94
194
  include Phlex::Reactive::Component
@@ -97,8 +197,7 @@ class MyCounter < PhlexKit::BaseComponent
97
197
  end
98
198
  ```
99
199
 
100
- PhlexKit does **not** depend on phlex-reactive. `PhlexKit.reactive?` auto-detects
101
- it; set `config.reactive` to force it on/off.
200
+ `PhlexKit.reactive?` auto-detects the gem; set `config.reactive` to force it.
102
201
 
103
202
  ## Ejecting components (shadcn-style)
104
203
 
@@ -111,25 +210,87 @@ bin/rails g phlex_kit:component button
111
210
  This copies `button.rb` + `button.css` into `app/components/phlex_kit/button/`
112
211
  and wires its `@import`. Your copy shadows the gem's.
113
212
 
114
- ## How the asset wiring works
213
+ ## The docs site
115
214
 
116
- Three engine initializers reproduce the pattern proven in production (revue):
215
+ The dummy Rails app is a full shadcn-style docs site: a sidebar menu with one
216
+ page per component, every use case demoed with a Preview/Code toggle (the code
217
+ shown is extracted from the running example source, so it can never drift).
218
+ A one-page kitchen-sink gallery lives at /gallery:
117
219
 
118
- 1. `app/components` and the stylesheet dir go on Propshaft's load path so CSS can
119
- sit beside each `.rb`.
120
- 2. Component folders are Zeitwerk-`collapse`d, so `button/button.rb` is
121
- `PhlexKit::Button` (not `PhlexKit::Button::Button`) and `card/card_header.rb`
122
- is `PhlexKit::CardHeader`.
123
- 3. A private-method guard keeps Propshaft from serving Ruby source out of
124
- `public/assets/` (covered by `test/assets/asset_load_path_test.rb`).
220
+ ```bash
221
+ git clone https://github.com/MatthewKennedy/phlex_kit && cd phlex_kit
222
+ bundle install
223
+ bundle exec puma -p 3999 test/dummy/config.ru
224
+ # open http://127.0.0.1:3999
225
+ ```
226
+
227
+ By default the docs site renders the shadcn-parity baseline (default tokens,
228
+ lucide icons). Two env vars re-skin the whole site, handy for eyeballing a
229
+ theme or icon set across every component:
230
+
231
+ ```bash
232
+ # Boot with a bundled theme (neutral, zinc, or claude)
233
+ PK_THEME=claude bundle exec puma -p 3999 test/dummy/config.ru
234
+
235
+ # Boot with a different icon library (lucide is the default; tabler, phosphor, remix)
236
+ PK_ICONS=tabler bundle exec puma -p 3999 test/dummy/config.ru
237
+
238
+ # Or both
239
+ PK_THEME=zinc PK_ICONS=phosphor bundle exec puma -p 3999 test/dummy/config.ru
240
+ ```
125
241
 
126
- Only the `@import url("…")` form is fingerprinted by Propshaft — a bare
127
- `@import "…"` ships un-digested and 404s. The manifest always uses `url()`.
242
+ `PK_THEME` links the matching file from
243
+ `app/assets/stylesheets/phlex_kit/themes/` after the manifest the same
244
+ mechanism a host app uses. Light/dark/system switching is in the site's
245
+ header regardless of theme.
128
246
 
129
247
  ## Components
130
248
 
131
- See [ROADMAP.md](ROADMAP.md) for the full inventory and porting status.
249
+ Everything in ruby_ui (53/53) plus shadcn/ui's own catalog: accordion, alert,
250
+ alert dialog, aspect ratio, attachment, avatar (+ group), badge, breadcrumb,
251
+ bubble, button, button group, calendar, card, carousel, chart, checkbox,
252
+ clipboard, codeblock, collapsible, combobox (button/input/badge-chip
253
+ triggers), command palette, context menu, data table, date picker, dialog,
254
+ drawer, dropdown menu, empty, form + form field, hover card, input, input
255
+ group, input OTP, item, kbd, label, link, masked input, menubar, message,
256
+ message scroller, native select, navigation menu, pagination, popover,
257
+ marker, progress, radio button + radio group, resizable, scroll area, select,
258
+ separator, sheet, shortcut key, sidebar, skeleton, slider, spinner,
259
+ switch, table, tabs, textarea, theme toggle, toast (sonner-style), toggle,
260
+ toggle group, tooltip, typography.
261
+
262
+ Plus two utility classes: `.pk-shimmer` (gradient sweep for loading text) and
263
+ `.pk-scroll-fade` (edge-masked scroll containers, position-aware via the
264
+ `phlex-kit--scroll-fade` controller).
265
+
266
+ Inventory and porting notes: [ROADMAP.md](ROADMAP.md). Architecture and
267
+ conventions: [docs/](docs/).
268
+
269
+ ## RTL
270
+
271
+ shadcn's `Direction` is a React context provider; PhlexKit needs none — set
272
+ `dir="rtl"` on `<html>` and browser layout handles the flex/grid flows. Note
273
+ the caveat: popover-style panels anchor with physical `left`/`top` offsets, so
274
+ fully mirrored overlay alignment in RTL may need small host-side overrides.
275
+
276
+ ## How the asset wiring works
277
+
278
+ Four engine initializers make the zero-build story hold:
279
+
280
+ 1. `app/components`, the stylesheet dir, and `app/javascript` go on
281
+ Propshaft's load path, so CSS sits beside each `.rb` and the importmap pins
282
+ resolve.
283
+ 2. Component folders are Zeitwerk-`collapse`d, so `button/button.rb` is
284
+ `PhlexKit::Button` and `card/card_header.rb` is `PhlexKit::CardHeader`.
285
+ 3. A guard keeps Propshaft from serving Ruby source out of `public/assets/`.
286
+ 4. The gem's importmap (pinning every controller) is appended to the host's.
287
+
288
+ Manifest imports use the `@import url("…")` form (Propshaft only fingerprints
289
+ `url()`) with paths relative to the manifest's own directory — both guarded by
290
+ tests.
132
291
 
133
292
  ## License
134
293
 
135
- MIT.
294
+ MIT. Component design ported from [ruby_ui](https://github.com/ruby-ui/ruby_ui)
295
+ and [shadcn/ui](https://ui.shadcn.com) (both MIT), with attribution comments
296
+ retained per component.
@@ -0,0 +1,104 @@
1
+ Third-party licenses for icon path data vendored under lib/phlex_kit/icons/
2
+ (generated by scripts/generate_icons.rb; the gem's own code is MIT — see
3
+ MIT-LICENSE). HugeIcons is deliberately not included: its free set's usage
4
+ terms forbid editing and redistribution of the artwork.
5
+
6
+ --------------------------------------------------------------------------------
7
+ Lucide (lib/phlex_kit/icons/lucide.rb)
8
+ Source: https://github.com/lucide-icons/lucide
9
+ License: ISC
10
+
11
+ ISC License
12
+
13
+ Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part
14
+ of Feather (MIT). All other copyright (c) for Lucide are held by Lucide
15
+ Contributors 2022.
16
+
17
+ Permission to use, copy, modify, and/or distribute this software for any
18
+ purpose with or without fee is hereby granted, provided that the above
19
+ copyright notice and this permission notice appear in all copies.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
22
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
23
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
24
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
25
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
26
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
27
+ PERFORMANCE OF THIS SOFTWARE.
28
+
29
+ --------------------------------------------------------------------------------
30
+ Tabler Icons (lib/phlex_kit/icons/tabler.rb)
31
+ Source: https://github.com/tabler/tabler-icons
32
+ License: MIT
33
+
34
+ MIT License
35
+
36
+ Copyright (c) 2020-2024 Paweł Kuna
37
+
38
+ Permission is hereby granted, free of charge, to any person obtaining a copy
39
+ of this software and associated documentation files (the "Software"), to deal
40
+ in the Software without restriction, including without limitation the rights
41
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
42
+ copies of the Software, and to permit persons to whom the Software is
43
+ furnished to do so, subject to the following conditions:
44
+
45
+ The above copyright notice and this permission notice shall be included in all
46
+ copies or substantial portions of the Software.
47
+
48
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
49
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
50
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
51
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
52
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
53
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
54
+ SOFTWARE.
55
+
56
+ --------------------------------------------------------------------------------
57
+ Phosphor Icons (lib/phlex_kit/icons/phosphor.rb)
58
+ Source: https://github.com/phosphor-icons/core
59
+ License: MIT
60
+
61
+ MIT License
62
+
63
+ Copyright (c) 2023 Phosphor Icons
64
+
65
+ Permission is hereby granted, free of charge, to any person obtaining a copy
66
+ of this software and associated documentation files (the "Software"), to deal
67
+ in the Software without restriction, including without limitation the rights
68
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
69
+ copies of the Software, and to permit persons to whom the Software is
70
+ furnished to do so, subject to the following conditions:
71
+
72
+ The above copyright notice and this permission notice shall be included in all
73
+ copies or substantial portions of the Software.
74
+
75
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
76
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
77
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
78
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
79
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
80
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
81
+ SOFTWARE.
82
+
83
+ --------------------------------------------------------------------------------
84
+ Remix Icon (lib/phlex_kit/icons/remix.rb)
85
+ Source: https://github.com/Remix-Design/RemixIcon
86
+ License: Apache License 2.0
87
+
88
+ Copyright RemixIcon contributors.
89
+
90
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
91
+ use the vendored icon data except in compliance with the License. You may
92
+ obtain a copy of the License at
93
+
94
+ http://www.apache.org/licenses/LICENSE-2.0
95
+
96
+ Unless required by applicable law or agreed to in writing, software
97
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
98
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
99
+ License for the specific language governing permissions and limitations under
100
+ the License.
101
+
102
+ NOTICE (statement of changes, per Apache-2.0 §4): the vendored data is
103
+ extracted SVG path geometry only, converted to Ruby literals and keyed by
104
+ PhlexKit's canonical glyph names; visual geometry is unmodified.
@@ -1,6 +1,69 @@
1
1
  /* Kit utilities used by the interactive controllers (Stimulus toggles these). */
2
2
  .pk-hidden { display: none !important; }
3
3
 
4
+ /* Shimmer — shadcn's shimmer util: a highlight band sweeping across text, for
5
+ loading / "thinking…" states. Pure CSS, built on currentColor: the text keeps
6
+ its own color (any color) and the highlight derives from it via relative
7
+ color syntax, so it brightens automatically in dark mode. Tune per element
8
+ with inline custom properties:
9
+ --pk-shimmer-color highlight (default: brightened currentColor)
10
+ --pk-shimmer-duration one sweep (default 2s)
11
+ --pk-shimmer-spread band width (default 3ch + 40px)
12
+ --pk-shimmer-angle band tilt (default 20deg)
13
+ Modifiers: .once (single sweep), .reverse. Honors prefers-reduced-motion and
14
+ falls back to plain text in browsers without relative color support. */
15
+ .pk-shimmer {
16
+ --pk-shimmer-color: oklch(from currentColor calc(l + .25) c h);
17
+ --pk-shimmer-duration: 2s;
18
+ --pk-shimmer-spread: calc(3ch + 40px);
19
+ --pk-shimmer-angle: 20deg;
20
+ background-image:
21
+ linear-gradient(calc(90deg + var(--pk-shimmer-angle)),
22
+ transparent calc(50% - var(--pk-shimmer-spread) / 2),
23
+ var(--pk-shimmer-color) 50%,
24
+ transparent calc(50% + var(--pk-shimmer-spread) / 2)),
25
+ linear-gradient(currentColor, currentColor);
26
+ background-size: 250% 100%, auto;
27
+ background-repeat: no-repeat;
28
+ -webkit-background-clip: text;
29
+ background-clip: text;
30
+ -webkit-text-fill-color: transparent;
31
+ animation: pk-shimmer var(--pk-shimmer-duration) linear infinite;
32
+ }
33
+ .pk-shimmer.once { animation-iteration-count: 1; animation-fill-mode: forwards; }
34
+ .pk-shimmer.reverse { animation-direction: reverse; }
35
+ [dir="rtl"] .pk-shimmer { animation-direction: reverse; }
36
+ [dir="rtl"] .pk-shimmer.reverse { animation-direction: normal; }
37
+ @keyframes pk-shimmer {
38
+ from { background-position: 250% 0, 0 0; }
39
+ to { background-position: -150% 0, 0 0; }
40
+ }
41
+ @media (prefers-reduced-motion: reduce) {
42
+ .pk-shimmer { animation: none; background-image: none; -webkit-text-fill-color: currentColor; }
43
+ }
44
+ @supports not (color: oklch(from white l c h)) {
45
+ .pk-shimmer { animation: none; background-image: none; -webkit-text-fill-color: currentColor; }
46
+ }
47
+
48
+ /* Scroll fade — shadcn's scroll-fade util: masks the edges of a scrollable
49
+ container while there is more content in that direction. Pair with the
50
+ phlex-kit--scroll-fade controller, which toggles data-at-start/data-at-end. */
51
+ .pk-scroll-fade {
52
+ --pk-scroll-fade-size: 2rem;
53
+ overflow-y: auto;
54
+ mask-image: linear-gradient(to bottom,
55
+ transparent 0, black var(--pk-scroll-fade-size),
56
+ black calc(100% - var(--pk-scroll-fade-size)), transparent 100%);
57
+ }
58
+ .pk-scroll-fade[data-at-start] {
59
+ mask-image: linear-gradient(to bottom,
60
+ black calc(100% - var(--pk-scroll-fade-size)), transparent 100%);
61
+ }
62
+ .pk-scroll-fade[data-at-end] {
63
+ mask-image: linear-gradient(to bottom, transparent 0, black var(--pk-scroll-fade-size));
64
+ }
65
+ .pk-scroll-fade[data-at-start][data-at-end] { mask-image: none; }
66
+
4
67
  /* Kit elements size predictably: width includes padding + border, so
5
68
  `width: 100%` controls (inputs, triggers, option rows) never overflow their
6
69
  container. Scoped to pk-* classes and their subtrees — the host's own box
@@ -22,6 +85,13 @@
22
85
  var(--pk-*), so redefining :root after the import re-themes the whole kit
23
86
  with no rebuild.
24
87
 
88
+ Optional sidebar overrides (shadcn sidebar-* parity) — deliberately NOT
89
+ defined here: --pk-sidebar / -text / -accent / -border / -primary /
90
+ -primary-ink. sidebar.css consumes them via token→token fallbacks
91
+ (e.g. var(--pk-sidebar-primary, var(--pk-brand))), so unset they fall back
92
+ to surface/text/accent/border/brand/brand-ink; a theme sets one only when
93
+ its value differs from the shared token.
94
+
25
95
  Dark is the default; light is opt-in via <html data-theme="light"> (set it
26
96
  server-side to avoid a flash). "system" follows the OS via prefers-color-scheme.
27
97
  Keep the two light token sets in sync. */
@@ -47,6 +117,8 @@
47
117
  --pk-chart-4: #1d4ed8;
48
118
  --pk-chart-5: #1e40af;
49
119
  --pk-radius: 0.625rem;
120
+ --pk-font-sans: ui-sans-serif, system-ui, -apple-system, sans-serif;
121
+ --pk-font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
50
122
  color-scheme: dark;
51
123
  }
52
124
 
@@ -50,6 +50,7 @@
50
50
  @import url("kbd/kbd.css");
51
51
  @import url("label/label.css");
52
52
  @import url("link/link.css");
53
+ @import url("marker/marker.css");
53
54
  @import url("menubar/menubar.css");
54
55
  @import url("message/message.css");
55
56
  @import url("message_scroller/message_scroller.css");
@@ -70,7 +71,6 @@
70
71
  @import url("skeleton/skeleton.css");
71
72
  @import url("slider/slider.css");
72
73
  @import url("spinner/spinner.css");
73
- @import url("stars/stars.css");
74
74
  @import url("switch/switch.css");
75
75
  @import url("table/table.css");
76
76
  @import url("tabs/tabs.css");
@@ -0,0 +1,86 @@
1
+ /* PhlexKit theme: Claude — warm ivory/charcoal neutrals + terracotta brand,
2
+ after the claude.ai palette. Pure token override: load AFTER the
3
+ phlex_kit.css manifest (stylesheet_link_tag "phlex_kit/themes/claude")
4
+ and the cascade re-themes every component; no component CSS is touched.
5
+
6
+ Same contract as _tokens.css: dark is the default, light is opt-in via
7
+ <html data-theme="light">, "system" follows the OS. Keep the two light
8
+ blocks in sync.
9
+
10
+ Anthropic's brand faces (Styrene, Tiempos/Copernicus) are licensed and
11
+ not shipped; --pk-font-serif falls back through Georgia. A host that has
12
+ the real fonts re-points the token. */
13
+ :root {
14
+ --pk-bg: #262624;
15
+ --pk-surface: #30302e;
16
+ --pk-surface-2: #3a3a37;
17
+ --pk-accent: #45453f;
18
+ --pk-border: #faf9f51f;
19
+ --pk-input: #faf9f52e;
20
+ --pk-ring: #d97757;
21
+ --pk-text: #f5f4ee;
22
+ --pk-text-2: #d9d8d0;
23
+ --pk-muted: #a3a094;
24
+ --pk-brand: #d97757;
25
+ --pk-brand-ink: #ffffff;
26
+ --pk-green: #22c55e;
27
+ --pk-amber: #f59e0b;
28
+ --pk-red: #ff6568;
29
+ --pk-chart-1: #ecb4a0;
30
+ --pk-chart-2: #e19477;
31
+ --pk-chart-3: #d97757;
32
+ --pk-chart-4: #b85c3e;
33
+ --pk-chart-5: #8f4630;
34
+ --pk-radius: 0.75rem;
35
+ --pk-font-serif: "Copernicus", "Tiempos Text", Georgia, "Times New Roman", serif;
36
+ color-scheme: dark;
37
+ }
38
+
39
+ :root[data-theme="light"] {
40
+ --pk-bg: #faf9f5;
41
+ --pk-surface: #ffffff;
42
+ --pk-surface-2: #f0eee6;
43
+ --pk-accent: #f0eee6;
44
+ --pk-border: #e5e2d9;
45
+ --pk-input: #d9d6ca;
46
+ --pk-ring: #d97757;
47
+ --pk-text: #1f1e1d;
48
+ --pk-text-2: #4a4844;
49
+ --pk-muted: #83817b;
50
+ --pk-brand: #d97757;
51
+ --pk-brand-ink: #ffffff;
52
+ --pk-green: #16a34a;
53
+ --pk-amber: #d97706;
54
+ --pk-red: #d13415;
55
+ color-scheme: light;
56
+ }
57
+
58
+ @media (prefers-color-scheme: light) {
59
+ :root[data-theme="system"] {
60
+ --pk-bg: #faf9f5;
61
+ --pk-surface: #ffffff;
62
+ --pk-surface-2: #f0eee6;
63
+ --pk-accent: #f0eee6;
64
+ --pk-border: #e5e2d9;
65
+ --pk-input: #d9d6ca;
66
+ --pk-ring: #d97757;
67
+ --pk-text: #1f1e1d;
68
+ --pk-text-2: #4a4844;
69
+ --pk-muted: #83817b;
70
+ --pk-brand: #d97757;
71
+ --pk-brand-ink: #ffffff;
72
+ --pk-green: #16a34a;
73
+ --pk-amber: #d97706;
74
+ --pk-red: #d13415;
75
+ color-scheme: light;
76
+ }
77
+ }
78
+
79
+ /* The one non-token rule: Claude's signature serif display over a sans body.
80
+ Serifs carry less inherent weight than the sans they replace, so headings
81
+ ease off the tracking the sans sizes wanted. */
82
+ .pk-heading {
83
+ font-family: var(--pk-font-serif);
84
+ font-weight: 500;
85
+ letter-spacing: 0;
86
+ }