shadcn-ui 0.0.4 → 0.0.8

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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +13 -314
  3. data/Rakefile +90 -0
  4. data/app/assets/stylesheets/shadcn.css +9 -1
  5. data/app/controllers/components_controller.rb +1 -0
  6. data/app/controllers/users_controller.rb +19 -0
  7. data/app/form_builders/shadcn_form_builder.rb +43 -0
  8. data/app/helpers/application_helper.rb +5 -0
  9. data/app/helpers/components/alert_dialog_helper.rb +19 -3
  10. data/app/helpers/components/alert_helper.rb +7 -3
  11. data/app/helpers/components/badge_helper.rb +4 -1
  12. data/app/helpers/components/combobox_helper.rb +10 -0
  13. data/app/helpers/components/forms_helper.rb +13 -0
  14. data/app/helpers/components/input_helper.rb +14 -4
  15. data/app/helpers/components/{seperator_helper.rb → separator_helper.rb} +1 -1
  16. data/app/helpers/components/slider_helper.rb +1 -1
  17. data/app/helpers/components/switch_helper.rb +5 -0
  18. data/app/helpers/components/tabs_helper.rb +29 -0
  19. data/app/helpers/components/toast_helper.rb +4 -1
  20. data/app/helpers/components_helper.rb +11 -5
  21. data/app/javascript/controllers/ui/dialog_controller.js +1 -1
  22. data/app/javascript/controllers/ui/dropdown_controller.js +1 -1
  23. data/app/javascript/controllers/ui/hover-card_controller.js +2 -3
  24. data/app/javascript/controllers/ui/popover_controller.js +2 -2
  25. data/app/javascript/controllers/ui/sheet_controller.js +1 -1
  26. data/app/javascript/controllers/ui/switch_controller.js +25 -0
  27. data/app/javascript/controllers/{tabs_controller.js → ui/tabs_controller.js} +2 -2
  28. data/app/javascript/controllers/ui/toast_controller.js +50 -23
  29. data/app/javascript/controllers/ui/transition_controller.js +1 -1
  30. data/app/models/user.rb +9 -0
  31. data/app/views/application/index.html.erb +1 -1
  32. data/app/views/components/ui/_alert_dialog.html.erb +10 -6
  33. data/app/views/components/ui/_combobox.html.erb +11 -0
  34. data/app/views/components/ui/_dialog.html.erb +0 -26
  35. data/app/views/components/ui/_filter.html.erb +1 -1
  36. data/app/views/components/ui/_input.html.erb +3 -0
  37. data/app/views/components/ui/_label.html.erb +1 -1
  38. data/app/views/components/ui/_popover.html.erb +1 -1
  39. data/app/views/components/ui/_slider.html.erb +5 -1
  40. data/app/views/components/ui/_switch.html.erb +25 -0
  41. data/app/views/components/ui/_tabs.html.erb +13 -0
  42. data/app/views/components/ui/_toast.html.erb +8 -4
  43. data/app/views/components/ui/tabs/_panel.html.erb +8 -0
  44. data/app/views/components/ui/tabs/_tab.html.erb +10 -0
  45. data/app/views/documentation/installation.html.md +1 -1
  46. data/app/views/examples/components/accordion/code/_preview.erb +11 -3
  47. data/app/views/examples/components/accordion/code/_usage.erb +2 -2
  48. data/app/views/examples/components/alert/code/_no_icon.erb +1 -1
  49. data/app/views/examples/components/alert-dialog/_usage.html.erb +24 -0
  50. data/app/views/examples/components/alert-dialog/code/_preview.erb +22 -6
  51. data/app/views/examples/components/alert-dialog/code/_usage.erb +13 -0
  52. data/app/views/examples/components/alert-dialog.html.erb +7 -1
  53. data/app/views/examples/components/card/code/_usage.erb +1 -1
  54. data/app/views/examples/components/combobox/_usage.html.erb +11 -0
  55. data/app/views/examples/components/combobox/code/_preview.erb +7 -0
  56. data/app/views/examples/components/combobox/code/_usage.erb +4 -0
  57. data/app/views/examples/components/combobox.html.erb +27 -0
  58. data/app/views/examples/components/forms/_usage.html.erb +28 -0
  59. data/app/views/examples/components/forms/code/_preview.erb +13 -0
  60. data/app/views/examples/components/forms/code/_usage.erb +6 -0
  61. data/app/views/examples/components/forms.html.erb +23 -0
  62. data/app/views/examples/components/slider/code/_preview.erb +2 -1
  63. data/app/views/examples/components/switch/_usage.html.erb +11 -0
  64. data/app/views/examples/components/switch/code/_preview.erb +4 -0
  65. data/app/views/examples/components/switch/code/_usage.erb +1 -0
  66. data/app/views/examples/components/switch.html.erb +27 -0
  67. data/app/views/examples/components/tabs/_usage.html.erb +15 -0
  68. data/app/views/examples/components/tabs/code/_account.html.erb +32 -0
  69. data/app/views/examples/components/tabs/code/_password.html.erb +19 -0
  70. data/app/views/examples/components/tabs/code/_preview.erb +14 -0
  71. data/app/views/examples/components/tabs/code/_usage.erb +12 -0
  72. data/app/views/examples/components/tabs.html.erb +27 -0
  73. data/app/views/examples/components/toast/code/_destructive.erb +6 -0
  74. data/app/views/examples/components/toast/code/_preview.erb +4 -3
  75. data/app/views/examples/components/toast.html.erb +17 -2
  76. data/app/views/layouts/application.html.erb +26 -4
  77. data/app/views/layouts/documentation/_component_header.html.erb +5 -4
  78. data/app/views/layouts/documentation/_examples.html.erb +7 -9
  79. data/app/views/layouts/documentation/_preview.html.erb +5 -5
  80. data/app/views/layouts/shared/_components.html.erb +6 -6
  81. data/config/routes.rb +3 -0
  82. data/lib/components.json +28 -13
  83. data/lib/generators/shadcn-ui_generator.rb +1 -1
  84. data/lib/shadcn-ui/version.rb +1 -1
  85. metadata +53 -9
  86. data/.env +0 -1
  87. data/app/views/components/ui/_badge.html.erb +0 -1
  88. data/app/views/components/ui/_separator.html.erb +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c7353625f737a5deeed84e83a9718a6201e151541995c11bedbeaa8b2f49234d
4
- data.tar.gz: 6399387bda38280d3d1be1208f5ac7796d7370cc902d8a658c742c1bcb64186b
3
+ metadata.gz: d1a6d106b3e241c5c7a418478bde0b7c4e4e50ce27c0999a1963d7a40b543dd2
4
+ data.tar.gz: 47a2ff41f7186f01c3cdef384b2b602b39868fbd9f2c2f15f7a3d8c2e0d3a72c
5
5
  SHA512:
6
- metadata.gz: bb839cd93e13cf9ea774d8d7dd1596bedd48ba02d570c65c8a125be616cbdae81a9f29fd46de71c13150b4a35c8709b5d57b813d21b74ee33076ead527cff827
7
- data.tar.gz: e9a6a20f2f23bbc542800bb1bfe6cd1c6bbc08ed6f586590e61f956dff5973aab0b711c912e1b9305c06c019f1045a9ba74a3174d34a3b46ce933bb47a9ec8cb
6
+ metadata.gz: 0b040f697a8b80b085a5d4a595bb20ed7b78b9f26d90082831977feb7acb80389787c8b23983f2094cde67212f5bf0a682f206c79171231d5b023ca22e11716e
7
+ data.tar.gz: 1e44cfd6682738fc2df66f886ba9d2c6de2b044c6828f43ff9e2b67aef0f37f62a01087f78bd4158911083eee8cb45ef59f18ad64e0044d824b7b6075ccb6966
data/README.md CHANGED
@@ -2,8 +2,11 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/shadcn-ui.svg)](https://badge.fury.io/rb/shadcn-ui)
4
4
 
5
- Accessible and customizable components that you can copy and paste into your apps. Free. Open
6
- Source. **Use this to build your own component library**.
5
+ [Shadcn on Rails](https://shadcn.rails-components.com) provides customizable components that you can
6
+ copy and paste into your apps. Free. Open Source. **Use this to build your own component library**.
7
+
8
+ **If you're using this, [please let me know](https://twitter.com/aviflombaum) so I keep developing
9
+ it.**
7
10
 
8
11
  ## About
9
12
 
@@ -21,321 +24,17 @@ Use this as a reference to build your own component libraries.
21
24
 
22
25
  ![hero](public/og.jpg)
23
26
 
24
- ## Alpha Usage/Installation
25
-
26
- Prior to the initial gem release, you can use this as an alpha by cloning this repository and
27
- starting up the app as you would a standard rails app.
28
-
29
- ```
30
- git clone https://github.com/aviflombaum/shadcn-rails.git
31
- cd shadcn-rails
32
- bundle install
33
- ./bin/dev
34
- ```
35
-
36
- There are very few dependencies and no database so it should just boot up. Visit
37
- http://localhost:3000 to see the demo app which is also the documentation. You'll be able to browse
38
- the components at http://localhost:3000/components.
39
-
40
- If there's a component you want to try in your app, you will be copying the code from this app to
41
- yours. There's a few other steps you'll need.
42
-
43
- ## Installing a Component
44
-
45
- ### Add Tailwind CSS
46
-
47
- Components are styled using Tailwind CSS. You need to install Tailwind CSS in your project.
48
-
49
- [Follow the Tailwind CSS installation instructions to get started.](https://tailwindcss.com/docs/installation)
50
-
51
- ### Add dependencies
52
-
53
- If you haven't already, install Tailwind into your rails application by adding `tailwindcss-rails`
54
- to your `Gemfile` and install tailwind into your app:
55
-
56
- ```sh
57
- ./bin/bundle add tailwindcss-rails
58
- ./bin/rails tailwindcss:install
59
- ```
60
-
61
- Then install ./bin/rails tailwindcss:install
62
-
63
- ### Configure tailwind.config.js
64
-
65
- Here's what my `tailwind.config.js` file looks like:
66
-
67
- ```js title="tailwind.config.js"
68
- const defaultTheme = require("tailwindcss/defaultTheme");
69
-
70
- module.exports = {
71
- darkMode: ["class"],
72
- content: [
73
- "./public/*.html",
74
- "./app/helpers/**/*.rb",
75
- "./app/javascript/**/*.js",
76
- "./app/views/**/*.{erb,haml,html,slim}",
77
- ],
78
- theme: {
79
- container: {
80
- center: true,
81
- padding: "2rem",
82
- screens: {
83
- "2xl": "1400px",
84
- },
85
- },
86
- extend: {
87
- colors: {
88
- border: "hsl(var(--border))",
89
- input: "hsl(var(--input))",
90
- ring: "hsl(var(--ring))",
91
- background: "hsl(var(--background))",
92
- foreground: "hsl(var(--foreground))",
93
- primary: {
94
- DEFAULT: "hsl(var(--primary))",
95
- foreground: "hsl(var(--primary-foreground))",
96
- },
97
- secondary: {
98
- DEFAULT: "hsl(var(--secondary))",
99
- foreground: "hsl(var(--secondary-foreground))",
100
- },
101
- destructive: {
102
- DEFAULT: "hsl(var(--destructive))",
103
- foreground: "hsl(var(--destructive-foreground))",
104
- },
105
- muted: {
106
- DEFAULT: "hsl(var(--muted))",
107
- foreground: "hsl(var(--muted-foreground))",
108
- },
109
- accent: {
110
- DEFAULT: "hsl(var(--accent))",
111
- foreground: "hsl(var(--accent-foreground))",
112
- },
113
- popover: {
114
- DEFAULT: "hsl(var(--popover))",
115
- foreground: "hsl(var(--popover-foreground))",
116
- },
117
- card: {
118
- DEFAULT: "hsl(var(--card))",
119
- foreground: "hsl(var(--card-foreground))",
120
- },
121
- },
122
- borderRadius: {
123
- lg: `var(--radius)`,
124
- md: `calc(var(--radius) - 2px)`,
125
- sm: "calc(var(--radius) - 4px)",
126
- },
127
- fontFamily: {
128
- sans: ["var(--font-sans)", ...defaultTheme.fontFamily.sans],
129
- },
130
- keyframes: {
131
- "accordion-down": {
132
- from: { height: 0 },
133
- to: { height: "var(--radix-accordion-content-height)" },
134
- },
135
- "accordion-up": {
136
- from: { height: "var(--radix-accordion-content-height)" },
137
- to: { height: 0 },
138
- },
139
- },
140
- animation: {
141
- "accordion-down": "accordion-down 0.2s ease-out",
142
- "accordion-up": "accordion-up 0.2s ease-out",
143
- },
144
- },
145
- },
146
- plugins: [
147
- require("@tailwindcss/forms"),
148
- require("@tailwindcss/aspect-ratio"),
149
- require("@tailwindcss/typography"),
150
- require("@tailwindcss/container-queries"),
151
- require("tailwindcss-animate"),
152
- ],
153
- };
154
- ```
155
-
156
- ### Configure styles
157
-
158
- Add the following to your app/assets/stylesheets/application.tailwind.css file.
159
-
160
- ```css title="application.tailwind.css"
161
- @tailwind base;
162
- @tailwind components;
163
- @tailwind utilities;
164
-
165
- @layer base {
166
- :root {
167
- --background: 0 0% 100%;
168
- --foreground: 222.2 47.4% 11.2%;
169
-
170
- --muted: 210 40% 96.1%;
171
- --muted-foreground: 215.4 16.3% 46.9%;
172
-
173
- --popover: 0 0% 100%;
174
- --popover-foreground: 222.2 47.4% 11.2%;
175
-
176
- --border: 214.3 31.8% 91.4%;
177
- --input: 214.3 31.8% 91.4%;
178
-
179
- --card: 0 0% 100%;
180
- --card-foreground: 222.2 47.4% 11.2%;
181
-
182
- --primary: 222.2 47.4% 11.2%;
183
- --primary-foreground: 210 40% 98%;
184
-
185
- --secondary: 210 40% 96.1%;
186
- --secondary-foreground: 222.2 47.4% 11.2%;
187
-
188
- --accent: 210 40% 96.1%;
189
- --accent-foreground: 222.2 47.4% 11.2%;
190
-
191
- --destructive: 0 100% 50%;
192
- --destructive-foreground: 210 40% 98%;
193
-
194
- --ring: 215 20.2% 65.1%;
195
-
196
- --radius: 0.5rem;
197
- }
198
-
199
- .dark {
200
- --background: 224 71% 4%;
201
- --foreground: 213 31% 91%;
202
-
203
- --muted: 223 47% 11%;
204
- --muted-foreground: 215.4 16.3% 56.9%;
205
-
206
- --accent: 216 34% 17%;
207
- --accent-foreground: 210 40% 98%;
208
-
209
- --popover: 224 71% 4%;
210
- --popover-foreground: 215 20.2% 65.1%;
211
-
212
- --border: 216 34% 17%;
213
- --input: 216 34% 17%;
214
-
215
- --card: 224 71% 4%;
216
- --card-foreground: 213 31% 91%;
217
-
218
- --primary: 210 40% 98%;
219
- --primary-foreground: 222.2 47.4% 1.2%;
220
-
221
- --secondary: 222.2 47.4% 11.2%;
222
- --secondary-foreground: 210 40% 98%;
223
-
224
- --destructive: 0 63% 31%;
225
- --destructive-foreground: 210 40% 98%;
226
-
227
- --ring: 216 34% 17%;
228
-
229
- --radius: 0.5rem;
230
- }
231
- }
232
-
233
- @layer base {
234
- * {
235
- @apply border-border;
236
- }
237
- body {
238
- @apply bg-background text-foreground;
239
- font-feature-settings:
240
- "rlig" 1,
241
- "calt" 1;
242
- }
243
- }
244
- ```
245
-
246
- ### Copy a a component's files to your application
247
-
248
- For example, if you want to use the Accordion component, you would copy the following files to your
249
- application:
250
-
251
- - `app/javascript/controllers/components/ui/accordion_controller.js` The Stimulus controller for any
252
- component that requires javascript.
253
- - `app/helpers/components/accordion_helper.rb` The helper for the component that allows for easy
254
- rendering within views.
255
- - `app/views/components/ui/_accordion.html.erb` The html for the component.
256
-
257
- Once those are copied in your application you can render an accordion with:
258
-
259
- ```erb
260
- <%= render_accordion title: "Did you know?", description: "You can wrap shadcn helpers in any
261
- component library you want!" %>
262
- <%= render_accordion title: "Use the generators.", description: "Add components with #{code("rails g shadcn_ui add accordion")}".html_safe %>
263
- ```
264
-
265
- See the component's demo page in `app/views/examples/components/accordion.html.erb` for more
266
- examples.
267
-
268
- This will be similar for each component.
269
-
270
- ## Documentation
271
-
272
- Visit https://avi.nyc/shadcn-on-rails to view the documentation.
273
-
274
- ## Contributing
275
-
276
- I am desperately seeking contributors to this project as it is in the very early stages.
277
-
278
- ### Contributing with Issues
279
-
280
- I am looking for people to start documenting issues in the project. The issues I'm interested in
281
- are:
282
-
283
- 1. What components are missing? Just start listing out the components that have yet to be
284
- implemented, better yet, open a PR with a branch for that issue and we can all start adding to
285
- it.
286
- 2. What components are not accessible? I am not an accessibility expert, so I need help with this.
287
- If you see something that is not accessible, please open an issue and let me know. This might
288
- mean the aria labels are hardcoded and not customizable or that the labels are simply missing.
289
- 3. What components are not customizable? I am trying to make all components as customizable as
290
- possible. If you see something that is not customizable, please open an issue and let me know.
291
- All the attributes of a component, like aria labels or classes or id or name, etc, should be
292
- customizeable by passing attributes into their helper functions that are passed down to the
293
- component's partial to be rendered.
294
- 4. Suggestions for the API of the components. I am trying to make the API as simple as possible. If
295
- you have any suggestions for how to make the API simpler, please open an issue and let me know. I
296
- am open to any and all suggestions.
297
-
298
- These are 3 main areas that would make the project easier for people to contribute to. They all make
299
- for great opportunities for someone new to open source to both file the issue and even begin to
300
- slowly implement them.
301
-
302
- ### Setup
303
-
304
- 1. Fork and clone the repo.
305
- 2. Run `bundle install` to install dependencies, there aren't many as this is currently a standard
306
- Rails applications.
307
- 3. `/bin/dev` to start the application.
308
-
309
- ### App Structure
310
-
311
- For now this is a standard Rails 7 application using propshaft and **importmaps**. This will soon be
312
- extracted into a gem that provides the components to be installed (copied) into the including
313
- application.
314
-
315
- ### Components
316
-
317
- The goal of this project is to provide a set of components that can be copied into your application.
318
- The components are built using [TailwindCSS](https://tailwindcss.com/) and
319
- [Stimulus](https://stimulus.hotwire.dev/). Each component follows the same structure:
27
+ ## Installation
320
28
 
321
- 1. There is a component partial located in `app/views/components/ui` that is rendered by the
322
- component helper. Ex `app/views/components/ui/_button.html.erb` provides the markup for the
323
- button component.
324
- 2. There is a component helper named after the component that is responsible for rendering the
325
- component and taking in arguments to customize the component located in `app/helpers/components`.
326
- Ex `app/helpers/components/button.rb` provides the `render_button` helper that accepts arguments
327
- such as `variant` which describes the kind of button and passes the classes for that `variant` to
328
- the partial.
329
- 3. When needed there is a stimulus controller for the component that provides the javascript
330
- required to make the component interactive. Ex. `app/javascript/controllers/toast_controller.js`
331
- provides the javascript for the toast component to display it and then hide it after a certain
332
- amount of time.
29
+ Refer to
30
+ [Installation](https://github.com/aviflombaum/shadcn-rails/blob/main/app/views/documentation/installation.html.md)
31
+ or the [Installation](https://shadcn.rails-components.com/docs/installation) page on the demo site.
333
32
 
334
- This demo application also included examples of how to use the components in
335
- `app/views/examples/components`. This is used to create the documentation site that this application
336
- provides with examples of teh components rendered.
33
+ ## Development
337
34
 
338
- For now, this convention should be followed when developing new components.
35
+ Clone the repo and run `bin/setup` to install dependencies. Then, run `bin/dev` to start the
36
+ tailwind watcher and then run `rails s`. I have to run the server and tailwind separately to keep
37
+ debuggers working.
339
38
 
340
39
  ## [shadcn-ui](https://ui.shadcn.com)
341
40
 
data/Rakefile CHANGED
@@ -1,6 +1,7 @@
1
1
  # Add your own tasks in files placed in lib/tasks ending in .rake,
2
2
  # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
3
  require "bundler/gem_tasks"
4
+ require 'fileutils'
4
5
 
5
6
  if ENV["RAILS_ENV"] != "production"
6
7
  require "rspec/core/rake_task"
@@ -13,3 +14,92 @@ end
13
14
  require_relative "config/application"
14
15
 
15
16
  Rails.application.load_tasks
17
+ # Rake task
18
+
19
+ HELPER_TEMPLATE = <<~TEMPLATE
20
+ module Components::<%= component_name %>Helper
21
+ end
22
+ TEMPLATE
23
+
24
+ STIMULUS_TEMPLATE = <<~TEMPLATE
25
+ import { Controller } from "@hotwired/stimulus";
26
+
27
+ export default class extends Controller {
28
+ }
29
+ TEMPLATE
30
+
31
+ def documentation_erb(name)
32
+ content = <<~TEMPLATE
33
+ <%= render "layouts/documentation/component_header",
34
+ title: "#{name.capitalize}",
35
+ description: "" %>
36
+
37
+ <% content_for :preview, flush: true do %>
38
+ <div class="w-full flex justify-center">
39
+ <%= render_code_preview('#{name}') %>
40
+ </div>
41
+ <% end %>
42
+
43
+ <% content_for :code, flush: true do %>
44
+ <div class="w-full flex justify-center">
45
+ <%= code_partial("#{name}/preview", :erb) %>
46
+ </div>
47
+ <% end %>
48
+
49
+ <%= render_preview %>
50
+
51
+ <h2 class="font-heading scroll-m-20 border-b pb-2 text-2xl font-semibold tracking-tight first:mt-0" id="installation">Installation</h2>
52
+ <%= code_sample(language: "sh") do %>
53
+ rails generate shadcn-ui #{name}
54
+ <% end %>
55
+
56
+ <h2 class="font-heading mt-12 scroll-m-20 border-b pb-2 mb-2 text-2xl font-semibold tracking-tight first:mt-0" id="usage">Usage</h2>
57
+ <%= code_partial("#{name}/usage", :erb) %>
58
+
59
+ <%= render_usage("#{name}") %>
60
+ TEMPLATE
61
+ end
62
+
63
+ namespace :component do
64
+ desc "Generate a new component"
65
+ task :generate => :environment do
66
+ name = ENV['name']
67
+ abort("No component name provided. Use `rake component:generate name=component_name`") unless name.present?
68
+
69
+ puts "Do you want to include a Stimulus controller? (yes/no)"
70
+ response = STDIN.gets.chomp.downcase
71
+
72
+ component_view_path = Rails.root.join("app", "views", "components", "ui", "_#{name}.html.erb")
73
+ helper_path = Rails.root.join("app", "helpers", "components", "#{name}_helper.rb")
74
+ controller_path = Rails.root.join("app", "javascript", "controllers", "ui", "#{name}_controller.js")
75
+
76
+ documentation_path = Rails.root.join("app", "views", "examples", "components", "#{name}.html.erb")
77
+ preview_path = Rails.root.join("app", "views", "examples", "components", name, "code", "_preview.html.erb")
78
+ preview_directory_path = Rails.root.join("app", "views", "examples", "components", name, "code")
79
+ preview_file_path = "#{preview_directory_path}/_preview.erb"
80
+ sample_usage_path = "#{preview_directory_path}/_usage.erb"
81
+ usage_instructions_path = Rails.root.join("app", "views", "examples", "components", name, "_usage.html.erb")
82
+
83
+ camelized_name = name.camelize
84
+ helper_template = ERB.new(HELPER_TEMPLATE)
85
+ helper_content = helper_template.result_with_hash(component_name: camelized_name)
86
+
87
+ stimulus_template = ERB.new(STIMULUS_TEMPLATE)
88
+ stimulus_content = stimulus_template.result
89
+
90
+ File.new(component_view_path, "w")
91
+ File.open(helper_path, "w") { |file| file.write(helper_content) }
92
+
93
+ File.open(documentation_path, "w") { |file| file.write(documentation_erb(name)) }
94
+ FileUtils.mkdir_p(preview_directory_path)
95
+ File.new(preview_file_path, "w")
96
+ File.new(sample_usage_path, "w")
97
+ File.new(usage_instructions_path, "w")
98
+
99
+ if ["yes", "y"].include?(response.downcase)
100
+ File.open(controller_path, "w") { |file| file.write(stimulus_content) }
101
+ end
102
+
103
+ puts "Component #{name} was created successfully!"
104
+ end
105
+ end
@@ -121,7 +121,7 @@ input[type="number"]:focus {
121
121
  input[type="range"] {
122
122
  -webkit-appearance: none;
123
123
  margin-right: 15px;
124
- width: 200px;
124
+ min-width: 200px;
125
125
  height: 7px;
126
126
  background: #f4f4f5;
127
127
  border-radius: 5px;
@@ -214,3 +214,11 @@ input[type="range"]::-ms-track {
214
214
  overflow: hidden;
215
215
  transition: all 0.2s;
216
216
  }
217
+
218
+ input.error {
219
+ @apply border-red-400;
220
+ }
221
+
222
+ label.error {
223
+ @apply text-destructive;
224
+ }
@@ -2,6 +2,7 @@ class ComponentsController < ActionController::Base
2
2
  layout "component"
3
3
 
4
4
  def show
5
+ @user = User.new # REFACTOR: For the forms example
5
6
  render "examples/components/#{params[:component]}"
6
7
  end
7
8
  end
@@ -0,0 +1,19 @@
1
+ class UsersController < ApplicationController
2
+ layout "component"
3
+
4
+ def create
5
+ params[:component] = "forms"
6
+ @user = User.new(user_params)
7
+ if @user.valid?
8
+ # Toast message
9
+ else
10
+ render "examples/components/forms", status: 422
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def user_params
17
+ params.require(:user).permit(:email, :password)
18
+ end
19
+ end
@@ -0,0 +1,43 @@
1
+ class ShadcnFormBuilder < ActionView::Helpers::FormBuilder
2
+ def label(method, options = {})
3
+ error_class = @object.errors[method].any? ? "error" : ""
4
+ options[:class] = @template.tw("#{options[:class]} #{error_class}")
5
+ @template.render_label(name: "#{object_name}[#{method}]", label: method.capitalize, **options)
6
+ end
7
+
8
+ def text_field(method, options = {})
9
+ options[:class] << " error" if @object.errors[method].any?
10
+ @template.render_input(
11
+ name: "#{object_name}[#{method}]",
12
+ id: "#{object_name}_#{method}",
13
+ value: @object.send(method),
14
+ type: "text", **options
15
+ )
16
+ end
17
+
18
+ def password_field(method, options = {})
19
+ error_class = @object.errors[method].any? ? "error" : ""
20
+ options[:class] = @template.tw("#{options[:class]} #{error_class}")
21
+ @template.render_input(
22
+ name: "#{object_name}[#{method}]",
23
+ id: "#{object_name}_#{method}",
24
+ value: @object.send(method),
25
+ type: "password", **options
26
+ )
27
+ end
28
+
29
+ def email_field(method, options = {})
30
+ error_class = @object.errors[method].any? ? "error" : ""
31
+ options[:class] = @template.tw("#{options[:class]} #{error_class}")
32
+ @template.render_input(
33
+ name: "#{object_name}[#{method}]",
34
+ id: "#{object_name}_#{method}",
35
+ value: @object.send(method),
36
+ type: "email", **options
37
+ )
38
+ end
39
+
40
+ def submit(value = nil, options = {})
41
+ @template.render_button(value, **options)
42
+ end
43
+ end
@@ -6,6 +6,11 @@ module ApplicationHelper
6
6
  @page_title << "#{component_name} - " if component_name.present?
7
7
  end
8
8
  @page_title << "shadcn/ui on Rails"
9
+
10
+ set_meta_tags(
11
+ title: @page_title
12
+ )
13
+
9
14
  @page_title
10
15
  end
11
16
 
@@ -1,6 +1,22 @@
1
1
  module Components::AlertDialogHelper
2
- def render_alert_dialog(trigger:, label:, description:, continue:, **options)
3
- cancel = options[:cancel] || render_button("Cancel", class: "mt-2 sm:mt-0", data: {action: "ui--dialog#close"})
4
- render "components/ui/alert_dialog", trigger:, label:, description:, continue:, cancel:, **options
2
+ def render_alert_dialog(**options, &block)
3
+ content = capture(&block) if block
4
+ render "components/ui/alert_dialog", content: content, **options
5
+ end
6
+
7
+ def alert_dialog_trigger(&block)
8
+ content_for :alert_dialog_trigger, capture(&block), flush: true
9
+ end
10
+
11
+ def alert_dialog_content(&block)
12
+ content_for :alert_dialog_content, capture(&block), flush: true
13
+ end
14
+
15
+ def alert_dialog_continue(&block)
16
+ content_for :alert_dialog_continue, capture(&block), flush: true
17
+ end
18
+
19
+ def alert_dialog_cancel(&block)
20
+ content_for :alert_dialog_cancel, capture(&block), flush: true
5
21
  end
6
22
  end
@@ -1,12 +1,16 @@
1
1
  module Components::AlertHelper
2
- def render_alert(title:, description:, variant: :default, icon: true)
2
+ def render_alert(title:, description: nil, variant: :default, icon: true)
3
3
  alert_classes = case variant.to_sym
4
4
  when :default
5
5
  "[&>svg]:text-foreground bg-background text-foreground"
6
6
  when :error, :danger, :alert, :destructive
7
7
  "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive"
8
- else
9
- "border-#{variant}/50 text-#{variant} dark:border-#{variant} [&>svg]:text-#{variant}"
8
+ when :success
9
+ "border-success/50 text-success dark:border-success [&>svg]:text-success"
10
+ when :info
11
+ "border-info/50 text-info dark:border-info [&>svg]:text-info"
12
+ when :attention
13
+ "border-attention/50 text-attention dark:border-attention [&>svg]:text-attention"
10
14
  end
11
15
  render "components/ui/alert", title:, description:, alert_classes:, variant:, icon:
12
16
  end
@@ -15,6 +15,9 @@ module Components::BadgeHelper
15
15
  end
16
16
  badge_classes << " #{varianet_classes}"
17
17
  text = label if label.present?
18
- render "components/ui/badge", text:, badge_classes:, data:, **options
18
+
19
+ content_tag :div, class: badge_classes do
20
+ text
21
+ end
19
22
  end
20
23
  end
@@ -0,0 +1,10 @@
1
+ module Components::ComboboxHelper
2
+ def render_combobox(items, &block)
3
+ content = capture(&block) if block
4
+ render "components/ui/combobox", items:, content:
5
+ end
6
+
7
+ def combobox_trigger(&block)
8
+ content_for :trigger, capture(&block) if block
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ module Components::FormsHelper
2
+ def render_form_with(**opts)
3
+ form_with(**opts.merge(builder: ShadcnFormBuilder)) do |form|
4
+ yield form
5
+ end
6
+ end
7
+
8
+ def render_form_for(obj, **opts)
9
+ form_for(obj, **opts.merge(builder: ShadcnFormBuilder), html: opts) do |form|
10
+ yield form
11
+ end
12
+ end
13
+ end
@@ -1,14 +1,24 @@
1
1
  module Components::InputHelper
2
2
  def render_input(name:, label: false, id: nil, type: :text, value: nil, **options)
3
- options[:class] = "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 #{options[:class]} "
3
+ options[:class] = "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm transition-colors ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 #{options[:class]} "
4
4
  options[:class] << case options[:variant]
5
5
  when :borderless
6
6
  " border-0 focus-visible:outline-none focus-visible:shadow-none focus-visible:ring-transparent"
7
7
  else
8
- " focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:border-muted"
8
+ "shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:border-muted"
9
9
  end
10
- options.reverse_merge!(required: false, disabled: false,
11
- readonly: false, label: false, placeholder: "Type here...")
10
+ options[:class] = tw(options[:class])
11
+
12
+ options.reverse_merge!(
13
+ label: (options[:lable] || false),
14
+ required: (options[:required] || false),
15
+ disabled: (options[:disabled] || false),
16
+ readonly: (options[:readonly] || false),
17
+ placeholder: (options[:placeholder] || ""),
18
+ autocomplete: (options[:autocomplete] || ""),
19
+ autocapitalize: (options[:autocapitalize] || nil),
20
+ autocorrect: (options[:autocorrect] || nil)
21
+ )
12
22
  render partial: "components/ui/input", locals: {
13
23
  type:,
14
24
  label:,