view_primitives 0.1.3 → 0.2.0
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/CHANGELOG.md +40 -0
- data/README.md +57 -2
- data/lib/generators/view_primitives/add/add_generator.rb +8 -62
- data/lib/generators/view_primitives/add/templates/accordion/accordion_item_component.rb.tt +30 -11
- data/lib/generators/view_primitives/add/templates/alert/alert_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/alert_dialog/alert_dialog_component.rb.tt +9 -9
- data/lib/generators/view_primitives/add/templates/aspect_ratio/aspect_ratio_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/audio/audio_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/avatar/avatar_component.rb.tt +8 -4
- data/lib/generators/view_primitives/add/templates/badge/badge_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/banner/banner_component.rb.tt +6 -6
- data/lib/generators/view_primitives/add/templates/bottom_nav/bottom_nav_component.rb.tt +11 -4
- data/lib/generators/view_primitives/add/templates/breadcrumb/breadcrumb_component.rb.tt +2 -2
- data/lib/generators/view_primitives/add/templates/button/button_component.rb.tt +8 -5
- data/lib/generators/view_primitives/add/templates/button_group/button_group_component.rb.tt +5 -5
- data/lib/generators/view_primitives/add/templates/calendar/calendar_component.rb.tt +18 -16
- data/lib/generators/view_primitives/add/templates/card/card_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/card/card_footer_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/carousel/carousel_component.rb.tt +26 -13
- data/lib/generators/view_primitives/add/templates/chart/chart_component.rb.tt +10 -4
- data/lib/generators/view_primitives/add/templates/chart/chart_controller.js +26 -3
- data/lib/generators/view_primitives/add/templates/chat_bubble/chat_bubble_component.rb.tt +4 -4
- data/lib/generators/view_primitives/add/templates/checkbox/checkbox_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/collapsible/collapsible_component.rb.tt +12 -5
- data/lib/generators/view_primitives/add/templates/combobox/combobox_component.rb.tt +3 -6
- data/lib/generators/view_primitives/add/templates/command/command_component.rb.tt +22 -18
- data/lib/generators/view_primitives/add/templates/command/command_controller.js +50 -0
- data/lib/generators/view_primitives/add/templates/context_menu/context_menu_component.rb.tt +9 -8
- data/lib/generators/view_primitives/add/templates/data_table/data_table_component.rb.tt +60 -29
- data/lib/generators/view_primitives/add/templates/data_table/data_table_controller.js +2 -2
- data/lib/generators/view_primitives/add/templates/date_picker/date_picker_component.rb.tt +8 -8
- data/lib/generators/view_primitives/add/templates/device_mockup/device_mockup_component.rb.tt +94 -21
- data/lib/generators/view_primitives/add/templates/dialog/dialog_component.rb.tt +13 -10
- data/lib/generators/view_primitives/add/templates/dialog/dialog_controller.js +52 -0
- data/lib/generators/view_primitives/add/templates/drawer/drawer_component.rb.tt +8 -7
- data/lib/generators/view_primitives/add/templates/dropdown_menu/dropdown_menu_component.rb.tt +5 -6
- data/lib/generators/view_primitives/add/templates/embed/embed_component.rb.tt +2 -2
- data/lib/generators/view_primitives/add/templates/figure/figure_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/file_input/file_input_component.rb.tt +3 -12
- data/lib/generators/view_primitives/add/templates/floating_label/floating_label_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/footer/footer_component.rb.tt +5 -4
- data/lib/generators/view_primitives/add/templates/form_field/form_field_component.rb.tt +18 -5
- data/lib/generators/view_primitives/add/templates/gallery/gallery_component.rb.tt +3 -3
- data/lib/generators/view_primitives/add/templates/gallery/gallery_controller.js +1 -1
- data/lib/generators/view_primitives/add/templates/hover_card/hover_card_component.rb.tt +6 -5
- data/lib/generators/view_primitives/add/templates/iframe/iframe_component.rb.tt +6 -4
- data/lib/generators/view_primitives/add/templates/image/image_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/indicator/indicator_component.rb.tt +5 -4
- data/lib/generators/view_primitives/add/templates/input/input_component.rb.tt +2 -13
- data/lib/generators/view_primitives/add/templates/input_otp/input_otp_component.rb.tt +22 -10
- data/lib/generators/view_primitives/add/templates/kbd/kbd_component.rb.tt +3 -1
- data/lib/generators/view_primitives/add/templates/list_group/list_group_component.rb.tt +6 -2
- data/lib/generators/view_primitives/add/templates/list_group/list_group_item_component.rb.tt +6 -4
- data/lib/generators/view_primitives/add/templates/map_area/map_area_component.rb.tt +3 -2
- data/lib/generators/view_primitives/add/templates/mega_menu/mega_menu_component.rb.tt +9 -9
- data/lib/generators/view_primitives/add/templates/menubar/menubar_component.rb.tt +5 -5
- data/lib/generators/view_primitives/add/templates/menubar/menubar_menu_component.rb.tt +4 -5
- data/lib/generators/view_primitives/add/templates/navbar/navbar_component.rb.tt +51 -11
- data/lib/generators/view_primitives/add/templates/navbar/navbar_controller.js +8 -3
- data/lib/generators/view_primitives/add/templates/navigation_menu/navigation_menu_component.rb.tt +12 -16
- data/lib/generators/view_primitives/add/templates/number_input/number_input_component.rb.tt +4 -11
- data/lib/generators/view_primitives/add/templates/pagination/pagination_component.rb.tt +4 -3
- data/lib/generators/view_primitives/add/templates/picture/picture_component.rb.tt +2 -1
- data/lib/generators/view_primitives/add/templates/popover/popover_component.rb.tt +1 -2
- data/lib/generators/view_primitives/add/templates/progress/progress_component.rb.tt +3 -1
- data/lib/generators/view_primitives/add/templates/qr_code/qr_code_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/radio_group/radio_group_component.rb.tt +8 -5
- data/lib/generators/view_primitives/add/templates/range/range_component.rb.tt +2 -3
- data/lib/generators/view_primitives/add/templates/rating/rating_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/rating_input/rating_controller.js +1 -1
- data/lib/generators/view_primitives/add/templates/rating_input/rating_input_component.rb.tt +4 -3
- data/lib/generators/view_primitives/add/templates/resizable/resizable_component.rb.tt +27 -15
- data/lib/generators/view_primitives/add/templates/scroll_area/scroll_area_component.rb.tt +10 -11
- data/lib/generators/view_primitives/add/templates/search_input/search_input_component.rb.tt +2 -11
- data/lib/generators/view_primitives/add/templates/select/select_component.rb.tt +25 -6
- data/lib/generators/view_primitives/add/templates/separator/separator_component.rb.tt +6 -3
- data/lib/generators/view_primitives/add/templates/sheet/sheet_component.rb.tt +25 -21
- data/lib/generators/view_primitives/add/templates/sidebar/sidebar_component.rb.tt +27 -21
- data/lib/generators/view_primitives/add/templates/skeleton/skeleton_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/speed_dial/speed_dial_component.rb.tt +8 -9
- data/lib/generators/view_primitives/add/templates/spinner/spinner_component.rb.tt +15 -6
- data/lib/generators/view_primitives/add/templates/stepper/stepper_component.rb.tt +17 -16
- data/lib/generators/view_primitives/add/templates/switch/switch_component.rb.tt +27 -14
- data/lib/generators/view_primitives/add/templates/tabs/tabs_component.html.erb +13 -7
- data/lib/generators/view_primitives/add/templates/tags_input/tags_input_component.rb.tt +136 -0
- data/lib/generators/view_primitives/add/templates/tags_input/tags_input_controller.js +90 -0
- data/lib/generators/view_primitives/add/templates/textarea/textarea_component.rb.tt +2 -11
- data/lib/generators/view_primitives/add/templates/timeline/timeline_component.rb.tt +9 -7
- data/lib/generators/view_primitives/add/templates/timepicker/timepicker_component.rb.tt +19 -15
- data/lib/generators/view_primitives/add/templates/toaster/toaster_component.rb.tt +10 -10
- data/lib/generators/view_primitives/add/templates/toaster/toaster_controller.js +6 -6
- data/lib/generators/view_primitives/add/templates/toggle/toggle_component.rb.tt +10 -3
- data/lib/generators/view_primitives/add/templates/toggle_group/toggle_group_component.rb.tt +6 -6
- data/lib/generators/view_primitives/add/templates/tooltip/tooltip_component.rb.tt +7 -6
- data/lib/generators/view_primitives/add/templates/video/video_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/wysiwyg/wysiwyg_component.rb.tt +9 -3
- data/lib/generators/view_primitives/component_copier.rb +96 -0
- data/lib/generators/view_primitives/components.rb +16 -2
- data/lib/generators/view_primitives/install/install_generator.rb +13 -3
- data/lib/generators/view_primitives/install/templates/application_component.rb.tt +7 -0
- data/lib/generators/view_primitives/install/templates/styles.rb.tt +26 -0
- data/lib/generators/view_primitives/install/templates/view_primitives/themes/default.css +79 -0
- data/lib/generators/view_primitives/install/templates/view_primitives/themes/rose.css +57 -0
- data/lib/generators/view_primitives/install/templates/view_primitives/tokens.css +46 -0
- data/lib/generators/view_primitives/install/templates/view_primitives/utilities.css +64 -0
- data/lib/generators/view_primitives/install/templates/view_primitives.css +6 -66
- data/lib/generators/view_primitives/list/list_generator.rb +3 -1
- data/lib/generators/view_primitives/theme/theme_generator.rb +79 -0
- data/lib/generators/view_primitives/update/update_generator.rb +112 -0
- data/lib/view_primitives/class_helper.rb +4 -1
- data/lib/view_primitives/railtie.rb +1 -1
- data/lib/view_primitives/version.rb +1 -1
- metadata +12 -4
- data/lib/generators/view_primitives/add/templates/drawer/drawer_controller.js +0 -15
- data/lib/generators/view_primitives/add/templates/sheet/sheet_controller.js +0 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cc1d6eea996ab223f1175ccb81a475051e949ee3a7bf1f0c9d9102b5b744a8f8
|
|
4
|
+
data.tar.gz: b41ca1269df44e350e40e87d039b5ac2c97385bdf638e37a89da40ed8b463895
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 32adf0c91b8b894420a2441b53ea82c25bc0e8fc542e89b33aec7c64cdaa3ef292d0b3550e9fbc83924ec0873560b41686c1d6c6ac4198cacdeadb43b19e3c31
|
|
7
|
+
data.tar.gz: 6b669fe4ecb2c66b42f952da057e658a1a6d0f20c71550a10fca288b2af91259f6609d3e2925a901bef0bd3e10f4604496f1c2e0a31a8363e7ea1e0b8b4a3a30
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.0] - 2026-06-08
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **TagsInput** — multi-select input that renders selected values as removable chips; submits as `name[]`; supports pre-selected `values:`, keyboard navigation (Enter to select, Backspace to remove last chip, Escape to close), and filterable dropdown
|
|
15
|
+
- `rails g view_primitives:update` — refresh installed components, CSS bundle, and `UI::Styles` from gem templates; supports `--only`, `--skip-components`, `--skip-css`, `--skip-styles`, `--force`
|
|
16
|
+
- `rails g view_primitives:theme <name>` — install an optional color theme (e.g. `rose`) and enable its `@import` in `view_primitives.css`
|
|
17
|
+
- `UI::Styles` module (`app/components/ui/styles.rb`) — shared primitive class names: `FOCUS_RING`, `BORDER`, `OVERLAY`, `INPUT`, `MENU_SEPARATOR`, `FIELD_PANEL`, `PICKER_TRIGGER`, etc.
|
|
18
|
+
- CSS bundle split into `tokens.css`, `utilities.css` (`vp-*` primitives), and `themes/` — run `rails g view_primitives:update --skip-components` to pull in the new structure
|
|
19
|
+
- `--force` flag on both `add` and `update` generators — skips the per-file overwrite confirmation prompt
|
|
20
|
+
- Focus trap in `dialog`, `alert_dialog`, `sheet`, `drawer`, and `command` Stimulus controllers
|
|
21
|
+
- Mobile nav panel in `NavbarComponent` with working hamburger toggle
|
|
22
|
+
- Optional `tailwind_merge` support in `cn()` when the gem is present in the project
|
|
23
|
+
- [docs/components/README.md](docs/components/README.md) — shared prerequisites guide
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- ~45 component templates migrated to `UI::Styles` constants and `vp-*` utilities for consistent theming
|
|
28
|
+
- `add` generator prompts for confirmation before overwriting an existing file (previously overwrote silently); use `--force` to restore previous behaviour
|
|
29
|
+
- `update` generator default changed to prompt before overwriting; use `--force` to overwrite unconditionally
|
|
30
|
+
- Sheet and drawer share `dialog_controller.js` instead of maintaining separate Stimulus controllers
|
|
31
|
+
- **DataTable**, **Accordion**, **DeviceMockup**, **Navbar**, **BottomNav**, **Footer**, **Drawer**, **Sheet**, **ListGroup**, and menu components visually realigned with the shadcn/ui reference
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- `add` and `update` generators recorded a failed component copy as success when the template directory was missing
|
|
36
|
+
- `add` generator printed a misleading "Copied" summary line for components with a missing template directory
|
|
37
|
+
- Setup notes (e.g. Chart.js importmap instructions) were never printed when the component list also contained an unknown name, because `abort` fired before `report_setup_notes` ran
|
|
38
|
+
- `ComponentCopier#with_source_root` used `source_paths.shift` in `ensure` — replaced with `source_paths.delete(path)` to always remove the correct entry
|
|
39
|
+
- `Components.supported` included non-directory entries from `Dir.children`; now filters to directories only
|
|
40
|
+
- `copy_js_controller` did not guard against an empty stimulus name (edge case: file named `_controller.js`)
|
|
41
|
+
- **Avatar** `lg` size accidentally reduced from `size-12` to `size-10` in the data-attribute refactor; restored to 48 px
|
|
42
|
+
- **Avatar** emitted `data-size="default"` on every default-size avatar; attribute is now omitted for `:default`
|
|
43
|
+
- **AlertDialog** content div lost `text-sm text-muted-foreground`; restored
|
|
44
|
+
- **Breadcrumb** inactive links lost `text-muted-foreground`; restored
|
|
45
|
+
- Sheet and drawer Stimulus actions used `sheet#` / `drawer#` targets while the controller was `dialog`; corrected to `dialog#open` / `dialog#close`
|
|
46
|
+
- `install --force` skipped `ApplicationComponent` regeneration; fixed
|
|
47
|
+
- Bare `border` / `border-b` / `border-t` classes without a color token rendered as black lines on some themes; replaced with `border-border`
|
|
48
|
+
|
|
10
49
|
## [0.1.3] - 2026-06-04
|
|
11
50
|
|
|
12
51
|
### Fixed
|
|
@@ -102,4 +141,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
102
141
|
- `view_primitives:add` exits with status 1 on unknown components; prints copy summary
|
|
103
142
|
- Requires `view_component >= 4.0` and Rails `>= 7.1`
|
|
104
143
|
|
|
144
|
+
[0.2.0]: https://github.com/alec-c4/view_primitives/releases/tag/v0.2.0
|
|
105
145
|
[0.1.0]: https://github.com/alec-c4/view_primitives/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -28,11 +28,13 @@ Then run the install generator:
|
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
30
|
rails g view_primitives:install
|
|
31
|
+
rails g view_primitives:install --force # refresh ApplicationComponent and CSS on upgrade
|
|
31
32
|
```
|
|
32
33
|
|
|
33
34
|
This will:
|
|
34
|
-
- Create `app/components/application_component.rb` with `ViewPrimitives::ClassHelper`
|
|
35
|
-
- Create `app/
|
|
35
|
+
- Create `app/components/application_component.rb` with `ViewPrimitives::ClassHelper`, `cn()`, and `extract_html_attrs` (skipped if you already have one — use `--force` to overwrite)
|
|
36
|
+
- Create `app/components/ui/styles.rb` with shared Tailwind primitive class names (`UI::Styles::*`)
|
|
37
|
+
- Create `app/assets/stylesheets/view_primitives.css` (entry file) plus `view_primitives/tokens.css`, `utilities.css`, and `themes/default.css`
|
|
36
38
|
|
|
37
39
|
Then import it in your Tailwind CSS entry point:
|
|
38
40
|
|
|
@@ -52,6 +54,31 @@ Components live under `UI::` (files in `app/components/ui/`). The gem registers
|
|
|
52
54
|
|
|
53
55
|
That's it — no `tailwind.config.js` required. Tailwind 4 reads the `@theme inline` block directly from the CSS.
|
|
54
56
|
|
|
57
|
+
### CSS layout
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
app/assets/stylesheets/
|
|
61
|
+
view_primitives.css # entry — @import tokens, utilities, themes
|
|
62
|
+
view_primitives/
|
|
63
|
+
tokens.css # @theme inline design tokens
|
|
64
|
+
utilities.css # vp-* shared primitives
|
|
65
|
+
themes/
|
|
66
|
+
default.css # :root and .dark color variables
|
|
67
|
+
rose.css # optional — via view_primitives:theme
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Generators
|
|
71
|
+
|
|
72
|
+
| Generator | Purpose |
|
|
73
|
+
|-----------|---------|
|
|
74
|
+
| `view_primitives:install` | Base setup — ApplicationComponent, styles.rb, CSS bundle |
|
|
75
|
+
| `view_primitives:add` | Copy one or more components into the app |
|
|
76
|
+
| `view_primitives:update` | Refresh installed components, CSS, and styles.rb from the gem |
|
|
77
|
+
| `view_primitives:theme` | Install an optional color theme |
|
|
78
|
+
| `view_primitives:list` | List available and installed components |
|
|
79
|
+
|
|
80
|
+
See [docs/components/README.md](docs/components/README.md) for setup details, Stimulus notes, and `class:` overrides.
|
|
81
|
+
|
|
55
82
|
## Adding components
|
|
56
83
|
|
|
57
84
|
```bash
|
|
@@ -62,6 +89,32 @@ rails g view_primitives:add button alert accordion # multiple at once
|
|
|
62
89
|
|
|
63
90
|
Each component is copied into `app/components/ui/` as plain Ruby and ERB files you own and can modify freely. Re-running `add` overwrites existing files (a warning is printed). Unknown component names fail with a non-zero exit code.
|
|
64
91
|
|
|
92
|
+
Component docs live in [docs/components/](docs/components/). Start with [component setup](docs/components/README.md) if you have not run `install` yet.
|
|
93
|
+
|
|
94
|
+
### Updating installed components
|
|
95
|
+
|
|
96
|
+
After upgrading the gem, refresh every component you have already copied:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
rails g view_primitives:update
|
|
100
|
+
rails g view_primitives:update --only button input # selected components
|
|
101
|
+
rails g view_primitives:update --skip-css # components only
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The update generator overwrites installed component files, the CSS bundle, and `styles.rb` from the latest gem templates. Back up local edits first.
|
|
105
|
+
|
|
106
|
+
### Optional color themes
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
rails g view_primitives:theme rose
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
This copies the theme CSS and enables its `@import` in `view_primitives.css`. Apply it with `data-theme="rose"` on your layout root (`.dark` still works for dark mode).
|
|
113
|
+
|
|
114
|
+
### Optional `tailwind_merge`
|
|
115
|
+
|
|
116
|
+
`cn()` deduplicates class strings. For Tailwind-aware conflict resolution, add the `tailwind_merge` gem to your Gemfile — ViewPrimitives picks it up automatically when present.
|
|
117
|
+
|
|
65
118
|
## View helpers
|
|
66
119
|
|
|
67
120
|
ViewPrimitives adds the `ui` helper to views and mailers:
|
|
@@ -139,6 +192,7 @@ ViewPrimitives adds the `ui` helper to views and mailers:
|
|
|
139
192
|
| Menubar | Horizontal application-style menu bar | [docs](docs/components/menubar.md) |
|
|
140
193
|
| Command | Modal command palette with live search filtering | [docs](docs/components/command.md) |
|
|
141
194
|
| Combobox | Autocomplete select with live search | [docs](docs/components/combobox.md) |
|
|
195
|
+
| Tags Input | Multi-select input that renders selected values as removable chips | [docs](docs/components/tags_input.md) |
|
|
142
196
|
| Calendar | Date picker calendar grid | [docs](docs/components/calendar.md) |
|
|
143
197
|
| Date Picker | Input that opens a Calendar popover | [docs](docs/components/date_picker.md) |
|
|
144
198
|
| Timepicker | Input for selecting a time value | [docs](docs/components/timepicker.md) |
|
|
@@ -174,6 +228,7 @@ See [ROADMAP.md](ROADMAP.md) for the full component list organised by phase.
|
|
|
174
228
|
See **[docs/customization.md](docs/customization.md)** for the full guide covering:
|
|
175
229
|
|
|
176
230
|
- Design tokens (OKLCH colors, radius) — change the whole palette in one file
|
|
231
|
+
- Shared `UI::Styles` primitives and border conventions (`border-border`, `MENU_SEPARATOR`)
|
|
177
232
|
- Editing component constants — add variants, change classes
|
|
178
233
|
- Per-instance `class:` overrides — append utilities without touching the file
|
|
179
234
|
- Full brand theming example
|
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../components"
|
|
4
|
+
require_relative "../component_copier"
|
|
4
5
|
require_relative "../detector"
|
|
5
6
|
|
|
6
7
|
module ViewPrimitives
|
|
7
8
|
module Generators
|
|
8
9
|
class AddGenerator < Rails::Generators::Base
|
|
9
10
|
include Detector
|
|
11
|
+
include ComponentCopier
|
|
10
12
|
|
|
11
13
|
source_root File.expand_path("templates", __dir__)
|
|
12
14
|
|
|
13
15
|
argument :components, type: :array
|
|
14
16
|
|
|
17
|
+
class_option :force, type: :boolean, default: false,
|
|
18
|
+
desc: "Overwrite existing files without prompting"
|
|
19
|
+
|
|
15
20
|
def copy_components
|
|
16
21
|
@copied = []
|
|
17
22
|
@unknown = []
|
|
18
23
|
|
|
19
24
|
components.each do |name|
|
|
20
25
|
if Components.supported.include?(name)
|
|
21
|
-
copy_component(name)
|
|
22
|
-
@copied << name
|
|
26
|
+
@copied << name if copy_component(name)
|
|
23
27
|
else
|
|
24
28
|
@unknown << name
|
|
25
29
|
say " Unknown component: #{name}. Supported: #{Components.supported.join(", ")}", :red
|
|
@@ -34,7 +38,7 @@ module ViewPrimitives
|
|
|
34
38
|
|
|
35
39
|
say " Failed: #{@unknown.join(", ")} (unknown)", :red
|
|
36
40
|
say " Run `rails g view_primitives:list` to see all available components.", :cyan
|
|
37
|
-
|
|
41
|
+
@abort_after_notes = true
|
|
38
42
|
end
|
|
39
43
|
|
|
40
44
|
def report_setup_notes
|
|
@@ -47,65 +51,7 @@ module ViewPrimitives
|
|
|
47
51
|
note.each_line { |line| say " #{line.chomp}", :cyan }
|
|
48
52
|
say ""
|
|
49
53
|
end
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
no_tasks do
|
|
53
|
-
def template(source, *args, **options, &block)
|
|
54
|
-
destination = args.first || options[:to]
|
|
55
|
-
warn_overwrite(destination) if destination
|
|
56
|
-
super
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def copy_file(source, *args, **options)
|
|
60
|
-
destination = args.first || options[:to]
|
|
61
|
-
warn_overwrite(destination) if destination
|
|
62
|
-
super
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
private
|
|
67
|
-
|
|
68
|
-
def copy_component(name)
|
|
69
|
-
dir = File.join(self.class.source_root, name)
|
|
70
|
-
Dir.each_child(dir).sort.each { |file| copy_template_file(name, file) }
|
|
71
|
-
copy_extra_stimulus(name)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def copy_template_file(component, file)
|
|
75
|
-
source = "#{component}/#{file}"
|
|
76
|
-
|
|
77
|
-
case file
|
|
78
|
-
when /\.rb\.tt\z/
|
|
79
|
-
template source, "app/components/ui/#{file.delete_suffix(".tt")}"
|
|
80
|
-
when /\.html\.erb\z/
|
|
81
|
-
copy_file source, "app/components/ui/#{file}"
|
|
82
|
-
when /_controller\.js\z/
|
|
83
|
-
copy_js_controller source, file.delete_suffix("_controller.js")
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def copy_extra_stimulus(name)
|
|
88
|
-
config = Components::EXTRA_STIMULUS[name]
|
|
89
|
-
copy_js_controller(config[:source], config[:name]) if config
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def copy_js_controller(source, stimulus_name)
|
|
93
|
-
dir = js_controllers_dir
|
|
94
|
-
unless dir
|
|
95
|
-
say " Could not detect a JS controllers directory.", :yellow
|
|
96
|
-
say " Copy #{source} manually and register Stimulus `#{stimulus_name}`.", :cyan
|
|
97
|
-
return
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
dest = "#{dir}/#{stimulus_name}_controller.js"
|
|
101
|
-
copy_file source, dest
|
|
102
|
-
say " Stimulus `#{stimulus_name}` → #{dest}", :green
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def warn_overwrite(destination)
|
|
106
|
-
return unless File.exist?(File.join(destination_root, destination))
|
|
107
|
-
|
|
108
|
-
say " #{destination} already exists — overwriting.", :yellow
|
|
54
|
+
abort if @abort_after_notes
|
|
109
55
|
end
|
|
110
56
|
end
|
|
111
57
|
end
|
|
@@ -2,11 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
module UI
|
|
4
4
|
class AccordionItemComponent < ApplicationComponent
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
ITEM_CLASSES = "border-b border-border last:border-b-0 group"
|
|
6
|
+
|
|
7
|
+
SUMMARY_CLASSES = "flex flex-1 cursor-pointer list-none items-start justify-between gap-4 rounded-md py-4 " \
|
|
8
|
+
"text-left text-sm font-medium transition-all outline-none hover:underline " \
|
|
9
|
+
"[&::-webkit-details-marker]:hidden #{UI::Styles::FOCUS_RING} " \
|
|
8
10
|
"disabled:pointer-events-none disabled:opacity-50"
|
|
9
11
|
|
|
12
|
+
TITLE_CLASSES = "flex-1 text-left font-medium leading-none"
|
|
13
|
+
|
|
14
|
+
CONTENT_CLASSES = "overflow-hidden pb-4 pt-0 text-sm"
|
|
15
|
+
|
|
16
|
+
CHEVRON_CLASSES = "pointer-events-none size-4 shrink-0 translate-y-0.5 text-muted-foreground " \
|
|
17
|
+
"transition-transform duration-200 group-open:rotate-180"
|
|
18
|
+
|
|
10
19
|
def initialize(title:, open: false, **html_attrs)
|
|
11
20
|
@title = title
|
|
12
21
|
@open = open
|
|
@@ -21,7 +30,7 @@ module UI
|
|
|
21
30
|
private
|
|
22
31
|
|
|
23
32
|
def details_attrs
|
|
24
|
-
attrs = @html_attrs.merge(class: cn(
|
|
33
|
+
attrs = @html_attrs.merge(class: cn(ITEM_CLASSES, @extra_class))
|
|
25
34
|
attrs[:open] = true if @open
|
|
26
35
|
attrs
|
|
27
36
|
end
|
|
@@ -29,19 +38,29 @@ module UI
|
|
|
29
38
|
def details_content
|
|
30
39
|
safe_join([
|
|
31
40
|
content_tag(:summary, summary_content, class: SUMMARY_CLASSES),
|
|
32
|
-
content_tag(:div, content, class:
|
|
41
|
+
content_tag(:div, content, class: CONTENT_CLASSES)
|
|
33
42
|
])
|
|
34
43
|
end
|
|
35
44
|
|
|
36
45
|
def summary_content
|
|
37
46
|
safe_join([
|
|
38
|
-
@title,
|
|
39
|
-
|
|
40
|
-
xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24",
|
|
41
|
-
fill: "none", stroke: "currentColor", stroke_width: "2",
|
|
42
|
-
stroke_linecap: "round", stroke_linejoin: "round",
|
|
43
|
-
class: "pointer-events-none size-4 shrink-0 translate-y-0.5 text-muted-foreground transition-transform duration-200 group-open:rotate-180")
|
|
47
|
+
content_tag(:span, @title, class: TITLE_CLASSES),
|
|
48
|
+
chevron_icon
|
|
44
49
|
])
|
|
45
50
|
end
|
|
51
|
+
|
|
52
|
+
def chevron_icon
|
|
53
|
+
content_tag(:svg,
|
|
54
|
+
content_tag(:path, nil, d: "m6 9 6 6 6-6"),
|
|
55
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
56
|
+
viewBox: "0 0 24 24",
|
|
57
|
+
fill: "none",
|
|
58
|
+
stroke: "currentColor",
|
|
59
|
+
"stroke-width": "2",
|
|
60
|
+
"stroke-linecap": "round",
|
|
61
|
+
"stroke-linejoin": "round",
|
|
62
|
+
class: CHEVRON_CLASSES,
|
|
63
|
+
"aria-hidden": "true")
|
|
64
|
+
end
|
|
46
65
|
end
|
|
47
66
|
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module UI
|
|
4
4
|
class AlertComponent < ApplicationComponent
|
|
5
|
-
OUTER_CLASSES = "relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg
|
|
5
|
+
OUTER_CLASSES = "relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg #{UI::Styles::BORDER} " \
|
|
6
6
|
"px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] " \
|
|
7
7
|
"has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current"
|
|
8
8
|
|
|
@@ -5,10 +5,10 @@ module UI
|
|
|
5
5
|
renders_one :trigger
|
|
6
6
|
renders_one :footer
|
|
7
7
|
|
|
8
|
-
OVERLAY =
|
|
9
|
-
PANEL = "fixed
|
|
10
|
-
"translate-x-[-50%] translate-y-[-50%] " \
|
|
11
|
-
"rounded-lg
|
|
8
|
+
OVERLAY = UI::Styles::OVERLAY
|
|
9
|
+
PANEL = "fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] " \
|
|
10
|
+
"translate-x-[-50%] translate-y-[-50%] gap-4 " \
|
|
11
|
+
"rounded-lg #{UI::Styles::BORDER} bg-background p-6 shadow-lg duration-200 sm:max-w-lg"
|
|
12
12
|
|
|
13
13
|
def initialize(title: nil, description: nil, **html_attrs)
|
|
14
14
|
@title = title
|
|
@@ -37,8 +37,8 @@ module UI
|
|
|
37
37
|
"aria-modal": "true",
|
|
38
38
|
"aria-label": @title) {
|
|
39
39
|
concat header_area
|
|
40
|
-
concat content_tag(:div, content, class: "
|
|
41
|
-
concat content_tag(:div, footer, class: "
|
|
40
|
+
concat content_tag(:div, content, class: "text-sm text-muted-foreground") unless content.blank?
|
|
41
|
+
concat content_tag(:div, footer, class: "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end") if footer
|
|
42
42
|
}
|
|
43
43
|
end
|
|
44
44
|
end
|
|
@@ -46,9 +46,9 @@ module UI
|
|
|
46
46
|
def header_area
|
|
47
47
|
return "" if @title.nil? && @description.nil?
|
|
48
48
|
|
|
49
|
-
content_tag(:div, class: "
|
|
50
|
-
concat content_tag(:h2, @title, class: "text-lg
|
|
51
|
-
concat content_tag(:p, @description, class: "
|
|
49
|
+
content_tag(:div, class: "flex flex-col gap-2 text-center sm:text-left") do
|
|
50
|
+
concat content_tag(:h2, @title, class: "text-lg leading-none font-semibold") if @title
|
|
51
|
+
concat content_tag(:p, @description, class: "text-sm text-muted-foreground") if @description
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
54
|
end
|
|
@@ -22,7 +22,7 @@ module UI
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def call
|
|
25
|
-
attrs = { preload: @preload, class: @extra_class }
|
|
25
|
+
attrs = { preload: @preload, class: cn("w-full rounded-md", @extra_class) }
|
|
26
26
|
attrs[:controls] = true if @controls
|
|
27
27
|
attrs[:autoplay] = true if @autoplay
|
|
28
28
|
attrs[:muted] = true if @muted
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module UI
|
|
4
4
|
class AvatarComponent < ApplicationComponent
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
BASE = "group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full select-none " \
|
|
6
|
+
"data-[size=sm]:size-6 data-[size=lg]:size-12"
|
|
7
7
|
IMAGE = "aspect-square size-full object-cover"
|
|
8
|
-
FALLBACK = "flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground"
|
|
8
|
+
FALLBACK = "flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground " \
|
|
9
|
+
"group-data-[size=sm]/avatar:text-xs"
|
|
9
10
|
|
|
10
11
|
def initialize(src: nil, alt: "", fallback: nil, size: :default, **html_attrs)
|
|
11
12
|
@src = src
|
|
@@ -17,7 +18,10 @@ module UI
|
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def call
|
|
20
|
-
content_tag(:div,
|
|
21
|
+
content_tag(:div,
|
|
22
|
+
class: cn(BASE, @extra_class),
|
|
23
|
+
"data-size": (@size == :default ? nil : @size.to_s),
|
|
24
|
+
**@html_attrs) do
|
|
21
25
|
if @src
|
|
22
26
|
content_tag(:img, nil, src: @src, alt: @alt, class: IMAGE)
|
|
23
27
|
else
|
|
@@ -5,7 +5,7 @@ module UI
|
|
|
5
5
|
BASE = "inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full " \
|
|
6
6
|
"border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap " \
|
|
7
7
|
"transition-[color,box-shadow] " \
|
|
8
|
-
"
|
|
8
|
+
"#{UI::Styles::FOCUS_RING} " \
|
|
9
9
|
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 " \
|
|
10
10
|
"[&>svg]:pointer-events-none [&>svg]:size-3"
|
|
11
11
|
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
module UI
|
|
4
4
|
class BannerComponent < ApplicationComponent
|
|
5
|
-
BASE = "flex items-center gap-3 rounded-lg
|
|
5
|
+
BASE = "relative flex w-full items-center gap-3 rounded-lg #{UI::Styles::BORDER} px-4 py-3 text-sm"
|
|
6
6
|
|
|
7
7
|
VARIANTS = {
|
|
8
|
-
default: "bg-
|
|
9
|
-
info: "border-
|
|
10
|
-
warning: "border-
|
|
11
|
-
destructive: "border-destructive/40 bg-
|
|
12
|
-
success: "border-
|
|
8
|
+
default: "bg-card text-card-foreground",
|
|
9
|
+
info: "border-border bg-muted/50 text-foreground",
|
|
10
|
+
warning: "border-border bg-chart-4/10 text-foreground",
|
|
11
|
+
destructive: "border-destructive/40 bg-card text-destructive",
|
|
12
|
+
success: "border-border bg-chart-2/10 text-foreground"
|
|
13
13
|
}.freeze
|
|
14
14
|
|
|
15
15
|
def initialize(message = nil, variant: :default, **html_attrs)
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module UI
|
|
4
4
|
class BottomNavComponent < ApplicationComponent
|
|
5
|
-
BASE = "fixed bottom-0 left-0 z-50 w-full border-t bg-background"
|
|
5
|
+
BASE = "fixed bottom-0 left-0 z-50 w-full border-t border-border bg-background/95 shadow-xs " \
|
|
6
|
+
"backdrop-blur supports-[backdrop-filter]:bg-background/80"
|
|
6
7
|
|
|
7
8
|
# items: [{ label:, href:, active: (optional), icon: (optional HTML string) }]
|
|
8
9
|
def initialize(items: [], **html_attrs)
|
|
@@ -12,8 +13,11 @@ module UI
|
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
def call
|
|
15
|
-
content_tag(:nav,
|
|
16
|
-
|
|
16
|
+
content_tag(:nav,
|
|
17
|
+
class: cn(BASE, @extra_class),
|
|
18
|
+
"aria-label": "Bottom navigation",
|
|
19
|
+
**@html_attrs) do
|
|
20
|
+
content_tag(:div, class: "mx-auto flex h-14 max-w-lg items-center justify-around") do
|
|
17
21
|
safe_join(@items.map { |item| nav_item(item) })
|
|
18
22
|
end
|
|
19
23
|
end
|
|
@@ -26,7 +30,10 @@ module UI
|
|
|
26
30
|
content_tag(:a,
|
|
27
31
|
href: item[:href],
|
|
28
32
|
class: cn(
|
|
29
|
-
"flex flex-col items-center justify-center gap-1 px-
|
|
33
|
+
"flex flex-col items-center justify-center gap-1 px-3 py-2 text-xs font-medium " \
|
|
34
|
+
"transition-colors outline-none",
|
|
35
|
+
UI::Styles::FOCUS_RING,
|
|
36
|
+
"rounded-md",
|
|
30
37
|
active ? "text-primary" : "text-muted-foreground hover:text-foreground"
|
|
31
38
|
),
|
|
32
39
|
"aria-current": (active ? "page" : nil)) do
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module UI
|
|
4
4
|
class BreadcrumbComponent < ApplicationComponent
|
|
5
|
-
LINK
|
|
6
|
-
CURRENT = "text-foreground
|
|
5
|
+
LINK = "text-muted-foreground transition-colors hover:text-foreground"
|
|
6
|
+
CURRENT = "font-normal text-foreground"
|
|
7
7
|
|
|
8
8
|
# items: [{ label:, href: }, ..., { label: }] — last item is the current page (no href)
|
|
9
9
|
def initialize(items: [], separator: "/", **html_attrs)
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
module UI
|
|
4
4
|
class ButtonComponent < ApplicationComponent
|
|
5
5
|
BASE_CLASSES = "inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm " \
|
|
6
|
-
"font-medium transition-all
|
|
7
|
-
"
|
|
6
|
+
"font-medium transition-all " \
|
|
7
|
+
"#{UI::Styles::FOCUS_RING} " \
|
|
8
8
|
"disabled:pointer-events-none disabled:opacity-50 " \
|
|
9
9
|
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 " \
|
|
10
10
|
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
|
@@ -13,8 +13,8 @@ module UI
|
|
|
13
13
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
14
14
|
destructive: "bg-destructive text-white hover:bg-destructive/90 " \
|
|
15
15
|
"focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
|
|
16
|
-
outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground " \
|
|
17
|
-
"dark:
|
|
16
|
+
outline: "border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground " \
|
|
17
|
+
"dark:bg-input/30 dark:hover:bg-input/50",
|
|
18
18
|
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
19
19
|
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
20
20
|
link: "text-primary underline-offset-4 hover:underline"
|
|
@@ -25,7 +25,10 @@ module UI
|
|
|
25
25
|
xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
26
26
|
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
|
|
27
27
|
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
28
|
-
icon: "size-9"
|
|
28
|
+
icon: "size-9",
|
|
29
|
+
"icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
|
|
30
|
+
"icon-sm": "size-8",
|
|
31
|
+
"icon-lg": "size-10"
|
|
29
32
|
}.freeze
|
|
30
33
|
|
|
31
34
|
# label — positional or keyword shorthand for plain-text buttons without a block.
|
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module UI
|
|
4
4
|
class ButtonGroupComponent < ApplicationComponent
|
|
5
|
-
BASE = "
|
|
6
|
-
"[&>*]:
|
|
7
|
-
"[&>*:first-child]:rounded-l-
|
|
8
|
-
"[&>*:last-child]:rounded-r-
|
|
9
|
-
"[&>*:not(:first-child)]:-ml-px"
|
|
5
|
+
BASE = "flex w-fit items-stretch " \
|
|
6
|
+
"[&>*]:focus-visible:relative [&>*]:focus-visible:z-10 " \
|
|
7
|
+
"[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 " \
|
|
8
|
+
"[&>*:not(:last-child)]:rounded-r-none"
|
|
10
9
|
|
|
11
10
|
def initialize(**html_attrs)
|
|
12
11
|
@extra_class = html_attrs.delete(:class)
|
|
@@ -17,6 +16,7 @@ module UI
|
|
|
17
16
|
content_tag(:div, content,
|
|
18
17
|
class: cn(BASE, @extra_class),
|
|
19
18
|
role: "group",
|
|
19
|
+
"data-slot": "button-group",
|
|
20
20
|
**@html_attrs)
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -9,21 +9,23 @@ module UI
|
|
|
9
9
|
# name: form field name for the hidden input (if used in a form)
|
|
10
10
|
# min/max: Date bounds for disabled days
|
|
11
11
|
|
|
12
|
-
CONTAINER = "w-fit
|
|
13
|
-
HEADER_CLS = "mb-
|
|
14
|
-
MONTH_CLS = "font-medium
|
|
15
|
-
NAV_BTN = "inline-flex size-
|
|
16
|
-
"text-muted-foreground hover:bg-accent hover:text-accent-foreground " \
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
12
|
+
CONTAINER = "group/calendar w-fit #{UI::Styles::FIELD_PANEL} p-3 text-sm"
|
|
13
|
+
HEADER_CLS = "relative mb-2 flex items-center justify-between"
|
|
14
|
+
MONTH_CLS = "flex h-8 w-full items-center justify-center text-sm font-medium select-none"
|
|
15
|
+
NAV_BTN = "inline-flex size-8 shrink-0 items-center justify-center rounded-md p-0 " \
|
|
16
|
+
"font-normal text-muted-foreground hover:bg-accent hover:text-accent-foreground " \
|
|
17
|
+
"#{UI::Styles::FOCUS_RING} " \
|
|
18
|
+
"disabled:pointer-events-none disabled:opacity-50 transition"
|
|
19
|
+
GRID_CLS = "grid w-full grid-cols-7"
|
|
20
|
+
DOW_CLS = "flex-1 rounded-md py-0 text-center text-[0.8rem] font-normal text-muted-foreground select-none"
|
|
21
|
+
DAY_BASE = "inline-flex aspect-square size-8 items-center justify-center rounded-md p-0 " \
|
|
22
|
+
"text-sm font-normal transition-colors outline-none " \
|
|
23
|
+
"#{UI::Styles::FOCUS_RING}"
|
|
24
|
+
DAY_NORMAL = "hover:bg-accent hover:text-accent-foreground dark:hover:text-accent-foreground"
|
|
25
|
+
DAY_TODAY = "rounded-md bg-accent text-accent-foreground"
|
|
26
|
+
DAY_SEL = "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground"
|
|
27
|
+
DAY_MUTED = "text-muted-foreground opacity-50"
|
|
28
|
+
DAY_DISABLED = "pointer-events-none opacity-50"
|
|
27
29
|
|
|
28
30
|
DAYS_OF_WEEK = %w[Su Mo Tu We Th Fr Sa].freeze
|
|
29
31
|
CHEVRON_L = "m15 18-6-6 6-6"
|
|
@@ -42,7 +44,7 @@ module UI
|
|
|
42
44
|
def call
|
|
43
45
|
content_tag(:div,
|
|
44
46
|
class: cn(CONTAINER, @extra_class),
|
|
45
|
-
data: { controller: "calendar", calendar_month_value: @month.iso8601 },
|
|
47
|
+
data: { slot: "calendar", controller: "calendar", calendar_month_value: @month.iso8601 },
|
|
46
48
|
**@html_attrs) do
|
|
47
49
|
concat hidden_input if @name && @selected
|
|
48
50
|
concat header_row
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module UI
|
|
4
4
|
class CardComponent < ApplicationComponent
|
|
5
|
-
BASE = "
|
|
5
|
+
BASE = "flex flex-col gap-6 rounded-xl #{UI::Styles::BORDER} bg-card py-6 text-card-foreground shadow-sm"
|
|
6
6
|
|
|
7
7
|
def initialize(**html_attrs)
|
|
8
8
|
@extra_class = html_attrs.delete(:class)
|