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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0f6988a4f8e3f47462f9f83824ed6542cafded9d656adb10340e7eb618156063
|
|
4
|
+
data.tar.gz: ca293b01074cfa84962df3c80683e3df5ae36cf5a919b1e8c0bcd1861948b52c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fd26ed3b6aef6bf015c7fbdf574516a68e8eb10fe1cfe1356a4292420d01917977428d2b35a301b65e6a8ff19c73daf5ac9ffdfb9af6abae6f96afd4818d7c81
|
|
7
|
+
data.tar.gz: 6d6c3d42730c2ce500a6f8c8e49afab8e11ec192f87bf9d87434761d334b40e1a762cc157ad7b38b2914edbffeae9fea501235f1cdc0ec6937c88a8f843399fa
|
data/README.md
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
# PhlexKit
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
3
|
+
[](https://rubygems.org/gems/phlex_kit)
|
|
4
|
+
[](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.
|
|
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: :
|
|
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
|
|
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
|
|
57
|
-
|
|
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:
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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
|
-
|
|
75
|
-
|
|
132
|
+
```ruby
|
|
133
|
+
stylesheet_link_tag "phlex_kit/phlex_kit"
|
|
134
|
+
stylesheet_link_tag "phlex_kit/themes/neutral"
|
|
135
|
+
```
|
|
76
136
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
85
|
-
|
|
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
|
-
|
|
89
|
-
|
|
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
|
|
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
|
-
##
|
|
213
|
+
## The docs site
|
|
115
214
|
|
|
116
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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
|
+
}
|