snazzy_ui 0.1.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 +7 -0
- data/README.md +186 -0
- data/app/components/snazzy/array_input_component.html.slim +19 -0
- data/app/components/snazzy/array_input_component.rb +17 -0
- data/app/components/snazzy/badge_component.rb +21 -0
- data/app/components/snazzy/badge_component.slim +3 -0
- data/app/components/snazzy/base_component.rb +7 -0
- data/app/components/snazzy/copy_button_component.rb +13 -0
- data/app/components/snazzy/copy_button_component.slim +10 -0
- data/app/components/snazzy/pill_component.rb +20 -0
- data/app/components/snazzy/pill_component.slim +3 -0
- data/app/components/snazzy/tag_cloud_component.rb +9 -0
- data/app/components/snazzy/tag_cloud_component.slim +3 -0
- data/app/components/snazzy/tags/grey_tag_component.rb +10 -0
- data/app/components/snazzy/tags/grey_tag_component.slim +4 -0
- data/app/components/snazzy/tooltip_component.rb +9 -0
- data/app/components/snazzy/tooltip_component.slim +5 -0
- data/app/javascript/controllers/snazzy/clipboard_controller.js +20 -0
- data/app/javascript/controllers/snazzy/index.js +10 -0
- data/app/javascript/controllers/snazzy/tags_controller.js +134 -0
- data/app/javascript/controllers/snazzy/tooltip_controller.js +44 -0
- data/lib/snazzy/engine.rb +8 -0
- data/lib/snazzy/version.rb +5 -0
- data/lib/snazzy_ui.rb +7 -0
- data/lib/tasks/install.rake +28 -0
- metadata +126 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5d9e58dccd42693202eede049d9164d90fa664090b8d6b751cb195561d2e0907
|
4
|
+
data.tar.gz: bf9cc243639027de0c29ef1a4963653e6be08ef70ceb7f770ef11ed08bf11058
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 15d6953ce227735508245df1b0c5ca5ac8e6901261e765e8db714357ea873d8ab9002c794b0771ff7a8c39a6c0eccd710632ceb543b098ec8562a238aa900e0e
|
7
|
+
data.tar.gz: 3bfb82fa19e5a264e692678b5559cac54c4320b4eb4d49261eec5e6330cd20652f278047ec99ca37a78e11fc25d847582fbef015882cadba72f0bb7c97f7ba13
|
data/README.md
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
# Snazzy 🎩✨
|
2
|
+
|
3
|
+
**Make your Rails app Snazzy!**
|
4
|
+
|
5
|
+
Because life's too short for boring UI components. Snazzy brings the pizzazz your ViewComponents have been craving. It's like a fashion makeover for your Rails app, but with less reality TV drama and more actual functionality.
|
6
|
+
|
7
|
+
## What's in the box? 📦
|
8
|
+
|
9
|
+
Snazzy is a collection of reusable UI components built with the holy trinity of modern Rails:
|
10
|
+
- 🎨 **ViewComponent** - Because global partials are so 2010
|
11
|
+
- 💅 **TailwindCSS** - Making things pretty without the CSS therapy sessions
|
12
|
+
- ⚡ **Hotwired** - JavaScript that sparks joy (and actually works)
|
13
|
+
- 🏃♂️ **Slim Templates** - Because life's too short for closing tags
|
14
|
+
|
15
|
+
## Components Gallery 🎪
|
16
|
+
|
17
|
+
### Available Components
|
18
|
+
|
19
|
+
#### 🏷️ BadgeComponent
|
20
|
+
Little badges of honor for your UI. Perfect for status indicators, counts, or just making things look official.
|
21
|
+
```ruby
|
22
|
+
= render Snazzy::BadgeComponent.new("New", color: "green", size: "small")
|
23
|
+
```
|
24
|
+
|
25
|
+
#### 💊 PillComponent
|
26
|
+
Like badges, but rounder. Because sometimes you need that smooth, pharmaceutical aesthetic.
|
27
|
+
```ruby
|
28
|
+
= render Snazzy::PillComponent.new("42 users", color: "blue", hoverable: true)
|
29
|
+
```
|
30
|
+
|
31
|
+
#### 💬 TooltipComponent
|
32
|
+
Hover hints that actually work. No more cryptic UI elements!
|
33
|
+
```ruby
|
34
|
+
= render Snazzy::TooltipComponent.new("I'm helpful text!") do
|
35
|
+
= link_to "Hover me!", "#"
|
36
|
+
```
|
37
|
+
|
38
|
+
#### 📋 CopyButtonComponent
|
39
|
+
One-click copying that gives users that satisfying "Copied!" feedback.
|
40
|
+
```ruby
|
41
|
+
= render Snazzy::CopyButtonComponent.new("secret-api-key", label: "Copy API Key")
|
42
|
+
```
|
43
|
+
|
44
|
+
#### 🏷️ TagCloudComponent & ArrayInputComponent
|
45
|
+
For when you need tags that users can add and remove. Includes built-in validations!
|
46
|
+
```ruby
|
47
|
+
= render Snazzy::ArrayInputComponent.new(tag_labels: ["ruby", "rails", "snazzy"])
|
48
|
+
```
|
49
|
+
|
50
|
+
## Installation 🚀
|
51
|
+
|
52
|
+
Add this line to your application's Gemfile:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
gem 'snazzy_ui'
|
56
|
+
# or if you're living on the edge with a local copy:
|
57
|
+
# gem 'snazzy_ui', path: '../snazzy_ui'
|
58
|
+
```
|
59
|
+
|
60
|
+
Then execute:
|
61
|
+
|
62
|
+
```bash
|
63
|
+
$ bundle install
|
64
|
+
$ bin/rails snazzy:install
|
65
|
+
```
|
66
|
+
|
67
|
+
The install task will:
|
68
|
+
1. Copy all the Stimulus controllers to `app/javascript/controllers/snazzy/`
|
69
|
+
2. Add the import to your `app/javascript/controllers/index.js`
|
70
|
+
3. Make your app instantly more snazzy ✨
|
71
|
+
|
72
|
+
### JavaScript Dependencies 📦
|
73
|
+
|
74
|
+
Some components need a little extra JavaScript love:
|
75
|
+
|
76
|
+
**For TooltipComponent** - Add Popper.js for positioning magic:
|
77
|
+
```bash
|
78
|
+
$ yarn add @popperjs/core
|
79
|
+
# or
|
80
|
+
$ npm install @popperjs/core
|
81
|
+
```
|
82
|
+
|
83
|
+
## Usage 🎯
|
84
|
+
|
85
|
+
### Basic Usage
|
86
|
+
|
87
|
+
After installation, all components are available under the `Snazzy::` namespace:
|
88
|
+
|
89
|
+
```erb
|
90
|
+
<!-- In your views -->
|
91
|
+
<%= render Snazzy::BadgeComponent.new("Beta", color: "yellow") %>
|
92
|
+
<%= render Snazzy::PillComponent.new("5 items", color: "green", size: "large") %>
|
93
|
+
|
94
|
+
<!-- With blocks -->
|
95
|
+
<%= render Snazzy::TooltipComponent.new("This button deletes everything!") do %>
|
96
|
+
<button class="btn-danger">Don't Click Me</button>
|
97
|
+
<% end %>
|
98
|
+
```
|
99
|
+
|
100
|
+
### Stimulus Controllers
|
101
|
+
|
102
|
+
All Stimulus controllers are namespaced with `snazzy--` to avoid conflicts:
|
103
|
+
|
104
|
+
```html
|
105
|
+
<div data-controller="snazzy--tooltip">
|
106
|
+
<button data-action="mouseover->snazzy--tooltip#show">
|
107
|
+
Hover for tooltip
|
108
|
+
</button>
|
109
|
+
</div>
|
110
|
+
```
|
111
|
+
|
112
|
+
### Styling
|
113
|
+
|
114
|
+
Components use TailwindCSS classes. Make sure your TailwindCSS configuration includes the snazzy gem's view paths:
|
115
|
+
|
116
|
+
```javascript
|
117
|
+
// tailwind.config.js
|
118
|
+
module.exports = {
|
119
|
+
content: [
|
120
|
+
// ... your other paths
|
121
|
+
'./node_modules/snazzy/app/components/**/*.{erb,html,slim}',
|
122
|
+
],
|
123
|
+
// ... rest of config
|
124
|
+
}
|
125
|
+
```
|
126
|
+
|
127
|
+
## Component Options 🎛️
|
128
|
+
|
129
|
+
### BadgeComponent & PillComponent
|
130
|
+
- `text` (required) - The content to display
|
131
|
+
- `color` - Color variant (e.g., "red", "green", "blue")
|
132
|
+
- `size` - Size variant ("small", "medium", "large")
|
133
|
+
- `hoverable` - Boolean for hover effects
|
134
|
+
- `iff` - Conditional rendering (defaults to true)
|
135
|
+
|
136
|
+
### TooltipComponent
|
137
|
+
- `text` (required) - The tooltip content
|
138
|
+
- Uses Popper.js for smart positioning
|
139
|
+
|
140
|
+
### CopyButtonComponent
|
141
|
+
- `copyable` (required) - The text to copy
|
142
|
+
- `label` - Button text (default: "Copy")
|
143
|
+
- `tooltip` - Optional tooltip text
|
144
|
+
- `icon` - Icon path (default: "icons/clipboard.svg")
|
145
|
+
- `css_classes` - Additional CSS classes
|
146
|
+
|
147
|
+
## Development 🛠️
|
148
|
+
|
149
|
+
After checking out the repo:
|
150
|
+
|
151
|
+
```bash
|
152
|
+
$ bin/setup # Install dependencies
|
153
|
+
$ bundle exec rspec # Run the tests
|
154
|
+
$ bin/console # Interactive prompt for experimentation
|
155
|
+
```
|
156
|
+
|
157
|
+
To install this gem onto your local machine:
|
158
|
+
|
159
|
+
```bash
|
160
|
+
$ bundle exec rake install
|
161
|
+
```
|
162
|
+
|
163
|
+
## Contributing 🤝
|
164
|
+
|
165
|
+
Found a bug? Want to add a sparkly new component? Pull requests are welcome!
|
166
|
+
|
167
|
+
1. Fork it
|
168
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
169
|
+
3. Make it snazzy
|
170
|
+
4. Commit your changes (`git commit -am 'Add some snazziness'`)
|
171
|
+
5. Push to the branch (`git push origin my-new-feature`)
|
172
|
+
6. Create a new Pull Request
|
173
|
+
|
174
|
+
## Why "Snazzy"? 🤔
|
175
|
+
|
176
|
+
Because naming things is hard, and "AnotherUIComponentGem" was taken. Plus, who doesn't want their app to be snazzy? It's fun to say, easy to type, and makes your README 23% more entertaining.
|
177
|
+
|
178
|
+
## License 📄
|
179
|
+
|
180
|
+
MIT License © 2024-Present Dan Brown
|
181
|
+
|
182
|
+
In other words: Use it, love it, make it even more snazzy. Just don't blame us if your app becomes too stylish for its own good.
|
183
|
+
|
184
|
+
---
|
185
|
+
|
186
|
+
*Remember: A snazzy app is a happy app!* ✨
|
@@ -0,0 +1,19 @@
|
|
1
|
+
- name = "#{@model_name}[#{@field}][]"
|
2
|
+
|
3
|
+
.input-group data-controller="snazzy--tags" data-snazzy--tags-validations-value=@validations
|
4
|
+
label = @label_text
|
5
|
+
input placeholder=@placeholder data-snazzy--tags-target="input" data-deletable-name=name name=name data-action="keydown->snazzy--tags#handleInput"
|
6
|
+
.mt-2
|
7
|
+
= "Press "
|
8
|
+
code.bg-gray-100.text-red-500.px-2.py-1.border.border-solid.border-gray-500.rounded-md Enter
|
9
|
+
= " or "
|
10
|
+
code.bg-gray-100.text-red-500.px-2.py-1.border.border-solid.border-gray-500.rounded-md return
|
11
|
+
= " after each #{@field.to_s.singularize}."
|
12
|
+
.border.border-solid.border-red-300.bg-red-50.text-red-500.px-2.py-1.mt-2.hidden data-snazzy--tags-target="errorContainer"
|
13
|
+
.input-wrapper.relative.mt-1
|
14
|
+
.TagCloud.inline-flex.flex-wrap.items-center data-snazzy--tags-target="tagCloud"
|
15
|
+
- @object.send(@field).each do |lab|
|
16
|
+
= render(Snazzy::Tags::GreyTagComponent.new(lab, deletable_name: name))
|
17
|
+
div data-snazzy--tags-target="hiddenInputContainer"
|
18
|
+
- @object.send(@field).each do |value|
|
19
|
+
= @form.hidden_field @field.to_sym, value: value, name:, id: nil
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Snazzy
|
4
|
+
class ArrayInputComponent < BaseComponent
|
5
|
+
delegate :singularize, to: :helpers
|
6
|
+
|
7
|
+
def initialize(object:, form:, field:, label_text:, placeholder: "Add a tag...", validations: [])
|
8
|
+
@object = object
|
9
|
+
@model_name = object.class.name.underscore
|
10
|
+
@form = form
|
11
|
+
@field = field
|
12
|
+
@label_text = label_text
|
13
|
+
@placeholder = placeholder
|
14
|
+
@validations = validations
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Snazzy
|
4
|
+
class BadgeComponent < BaseComponent
|
5
|
+
def initialize(text, color: "", hoverable: false, size: nil, iff: true)
|
6
|
+
@text = text
|
7
|
+
@color = color
|
8
|
+
@extra_classes = extra_classes(color, size, hoverable)
|
9
|
+
@iff = iff
|
10
|
+
end
|
11
|
+
|
12
|
+
def extra_classes(color, size, hoverable)
|
13
|
+
extra_classes = []
|
14
|
+
|
15
|
+
extra_classes << color if color
|
16
|
+
extra_classes << "--#{size}" if size
|
17
|
+
extra_classes << "--hoverable" if hoverable
|
18
|
+
extra_classes.join(" ")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Snazzy
|
4
|
+
class CopyButtonComponent < BaseComponent
|
5
|
+
def initialize(copyable, label: "Copy", tooltip: nil, icon: "icons/clipboard.svg", css_classes: "")
|
6
|
+
@copyable = copyable
|
7
|
+
@label = label
|
8
|
+
@tooltip = tooltip
|
9
|
+
@icon = icon
|
10
|
+
@css_classes = css_classes
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
- if @tooltip
|
2
|
+
= render Snazzy::TooltipComponent.new(@tooltip) do
|
3
|
+
= link_to "", data: { action: "snazzy--clipboard#oneTimeCopy", content: @copyable }, class: "inline-block p-2 hover:bg-yellow-100 rounded-full transition-colors duration-150 #{@css_classes}" do
|
4
|
+
- if respond_to?(:inline_svg_tag)
|
5
|
+
= inline_svg_tag @icon, class: "pointer-events-none"
|
6
|
+
- else
|
7
|
+
span.pointer-events-none = @icon
|
8
|
+
- else
|
9
|
+
button.inline-block.ml-2.bg-primary-100.px-2.py-1.rounded-md.hover:bg-primary-300.text-white data-action="snazzy--clipboard#oneTimeCopy" data-content=@copyable
|
10
|
+
= @label
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Snazzy
|
4
|
+
class PillComponent < BaseComponent
|
5
|
+
def initialize(text, color: nil, hoverable: false, size: nil, iff: true)
|
6
|
+
@text = text
|
7
|
+
@extra_classes = extra_classes(color, size, hoverable)
|
8
|
+
@iff = iff
|
9
|
+
end
|
10
|
+
|
11
|
+
def extra_classes(color, size, hoverable)
|
12
|
+
extra_classes = []
|
13
|
+
|
14
|
+
extra_classes << color if color
|
15
|
+
extra_classes << "--#{size}" if size
|
16
|
+
extra_classes << "--hoverable" if hoverable
|
17
|
+
extra_classes.join(" ")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,4 @@
|
|
1
|
+
span.inline-flex.items-center.rounded-md.bg-gray-50.px-2.py-1.text-xs.font-medium.text-gray-700.ring-1.ring-inset.ring-gray-600/20.mr-2.my-1
|
2
|
+
= @label
|
3
|
+
- if @deletable_name
|
4
|
+
span.ml-2.cursor-pointer data-action="click->snazzy--tags#removeTag" data-deletable-name=@deletable_name X
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
|
3
|
+
export default class SnazzyClipboardController extends Controller {
|
4
|
+
static targets = [ "source" ]
|
5
|
+
|
6
|
+
oneTimeCopy() {
|
7
|
+
event.preventDefault()
|
8
|
+
navigator.clipboard.writeText(event.target.dataset.content)
|
9
|
+
event.target.style.cursor = "default"
|
10
|
+
event.target.removeAttribute("data-action")
|
11
|
+
event.target.classList.add("pointer-events-none")
|
12
|
+
event.target.classList.remove("bg-primary-100", "hover:bg-primary-300")
|
13
|
+
if (event.target.innerText.length > 0) {
|
14
|
+
event.target.innerText = "Copied!"
|
15
|
+
event.target.classList.add("bg-emerald-400")
|
16
|
+
} else {
|
17
|
+
event.target.innerHTML = "✔"
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import { application } from "../application"
|
2
|
+
|
3
|
+
import SnazzyTagsController from "./tags_controller"
|
4
|
+
import SnazzyTooltipController from "./tooltip_controller"
|
5
|
+
import SnazzyClipboardController from "./clipboard_controller"
|
6
|
+
|
7
|
+
application.register("snazzy--tags", SnazzyTagsController)
|
8
|
+
application.register("snazzy--tooltip", SnazzyTooltipController)
|
9
|
+
application.register("snazzy--clipboard", SnazzyClipboardController)
|
10
|
+
|
@@ -0,0 +1,134 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
|
3
|
+
export default class SnazzyTagsController extends Controller {
|
4
|
+
static targets = ["input", "tagCloud", "errorContainer", "hiddenInputContainer"];
|
5
|
+
static values = {
|
6
|
+
validations: Array
|
7
|
+
}
|
8
|
+
|
9
|
+
handleInput(event) {
|
10
|
+
if (event.key === 'Enter') {
|
11
|
+
event.preventDefault();
|
12
|
+
let errors = this.performValidations()
|
13
|
+
if (errors.length === 0) {
|
14
|
+
this.hideErrorsContainer()
|
15
|
+
this.addTag();
|
16
|
+
} else {
|
17
|
+
this.displayErrors(errors)
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
hideErrorsContainer() {
|
23
|
+
this.errorContainerTarget.innerHTML = ""
|
24
|
+
this.errorContainerTarget.classList.add("hidden")
|
25
|
+
}
|
26
|
+
|
27
|
+
performValidations() {
|
28
|
+
let errors = []
|
29
|
+
this.validationsValue.forEach((validationName) => {
|
30
|
+
let error = this.callValidation(validationName);
|
31
|
+
if (error) {
|
32
|
+
errors.push(error);
|
33
|
+
}
|
34
|
+
})
|
35
|
+
|
36
|
+
return errors
|
37
|
+
}
|
38
|
+
|
39
|
+
callValidation(validationName) {
|
40
|
+
const validationHandler = 'validate' + validationName.charAt(0).toUpperCase() + validationName.slice(1);
|
41
|
+
const value = this.inputTarget.value;
|
42
|
+
if (typeof this[validationHandler] === 'function') {
|
43
|
+
return this[validationHandler](value);
|
44
|
+
} else {
|
45
|
+
console.error(`${validationHandler} does not exist`);
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
validateEmail(value) {
|
50
|
+
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
51
|
+
if (!emailPattern.test(value)) {
|
52
|
+
return `${value} is not a valid email address.`
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
validateUniqueness(value) {
|
57
|
+
const inputs = this.hiddenInputContainerTarget.querySelectorAll("input")
|
58
|
+
const duplicateFound = Array.from(inputs).some(i => i.value == value)
|
59
|
+
|
60
|
+
if(duplicateFound) {
|
61
|
+
this.inputTarget.value = ""
|
62
|
+
return `${value} is already in the list.`
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
displayErrors(errors) {
|
67
|
+
this.errorContainerTarget.innerHTML = ""
|
68
|
+
errors.forEach((error) => {
|
69
|
+
let li = document.createElement('li');
|
70
|
+
li.textContent = error;
|
71
|
+
this.errorContainerTarget.appendChild(li);
|
72
|
+
this.errorContainerTarget.classList.remove("hidden")
|
73
|
+
})
|
74
|
+
}
|
75
|
+
|
76
|
+
addTag() {
|
77
|
+
const value = this.inputTarget.value.trim();
|
78
|
+
|
79
|
+
if (value) {
|
80
|
+
const tagElement = this.generateTagElement(value, this.inputTarget.dataset.deletableName);
|
81
|
+
this.tagCloudTarget.append(tagElement);
|
82
|
+
this.addHiddenInput(value)
|
83
|
+
this.inputTarget.value = "";
|
84
|
+
this.inputTarget.focus();
|
85
|
+
}
|
86
|
+
}
|
87
|
+
|
88
|
+
removeTag(event) {
|
89
|
+
let parent = event.currentTarget.parentElement
|
90
|
+
let obsoleteValue = parent.innerHTML.split("<span")[0]
|
91
|
+
let obsoleteInput = document.querySelector("input[name='" + event.currentTarget.dataset.deletableName + "'][value='" + obsoleteValue + "']")
|
92
|
+
parent.remove();
|
93
|
+
obsoleteInput.remove();
|
94
|
+
|
95
|
+
this.handleLastTagRemoval();
|
96
|
+
}
|
97
|
+
|
98
|
+
addHiddenInput(value) {
|
99
|
+
const hiddenInput = document.createElement("input")
|
100
|
+
hiddenInput.name = this.inputTarget.dataset.deletableName
|
101
|
+
hiddenInput.autocomplete = "off"
|
102
|
+
hiddenInput.type = "hidden"
|
103
|
+
hiddenInput.value = value
|
104
|
+
this.hiddenInputContainerTarget.appendChild(hiddenInput)
|
105
|
+
}
|
106
|
+
|
107
|
+
handleLastTagRemoval() {
|
108
|
+
if (this.tagCloudTarget.children.length === 0) {
|
109
|
+
this.addEmptyHiddenInput();
|
110
|
+
}
|
111
|
+
}
|
112
|
+
|
113
|
+
addEmptyHiddenInput() {
|
114
|
+
const hiddenInput = document.createElement("input");
|
115
|
+
hiddenInput.name = this.inputTarget.dataset.deletableName;
|
116
|
+
hiddenInput.type = "hidden";
|
117
|
+
this.hiddenInputContainerTarget.appendChild(hiddenInput);
|
118
|
+
}
|
119
|
+
|
120
|
+
generateTagElement(content, deletableName) {
|
121
|
+
const tag = document.createElement("span");
|
122
|
+
tag.className = "inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-600/20 mr-2 my-1";
|
123
|
+
tag.textContent = content;
|
124
|
+
|
125
|
+
const closeButton = document.createElement("span");
|
126
|
+
closeButton.dataset.action = "click->snazzy--tags#removeTag"
|
127
|
+
closeButton.classList.add("ml-2", "cursor-pointer")
|
128
|
+
closeButton.dataset.deletableName = deletableName
|
129
|
+
closeButton.textContent = "X";
|
130
|
+
|
131
|
+
tag.appendChild(closeButton);
|
132
|
+
return tag;
|
133
|
+
}
|
134
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
import { createPopper } from "@popperjs/core"
|
3
|
+
|
4
|
+
export default class SnazzyTooltipController extends Controller {
|
5
|
+
static targets = ["source", "content"]
|
6
|
+
|
7
|
+
connect() {
|
8
|
+
this.popper = createPopper(this.sourceTarget, this.contentTarget, {
|
9
|
+
placement: 'top',
|
10
|
+
modifiers: [
|
11
|
+
{
|
12
|
+
name: 'flip',
|
13
|
+
options: {
|
14
|
+
fallbackPlacements: ['bottom'],
|
15
|
+
},
|
16
|
+
},
|
17
|
+
{
|
18
|
+
name: 'preventOverflow',
|
19
|
+
options: {
|
20
|
+
padding: 8,
|
21
|
+
},
|
22
|
+
},
|
23
|
+
{
|
24
|
+
name: 'offset',
|
25
|
+
options: {
|
26
|
+
offset: [0, 8],
|
27
|
+
},
|
28
|
+
},
|
29
|
+
],
|
30
|
+
});
|
31
|
+
|
32
|
+
this.contentTarget.classList.add('hidden');
|
33
|
+
}
|
34
|
+
|
35
|
+
show() {
|
36
|
+
this.contentTarget.classList.remove('hidden');
|
37
|
+
this.popper.update();
|
38
|
+
}
|
39
|
+
|
40
|
+
hide() {
|
41
|
+
clearTimeout(this.timeout);
|
42
|
+
this.contentTarget.classList.add('hidden');
|
43
|
+
}
|
44
|
+
}
|
data/lib/snazzy_ui.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
namespace :snazzy do
|
2
|
+
desc "Install Snazzy JavaScript controllers into the Rails application"
|
3
|
+
task install: :environment do
|
4
|
+
require "fileutils"
|
5
|
+
|
6
|
+
gem_path = File.expand_path("../..", __dir__)
|
7
|
+
stimulus_controllers_dest = "app/javascript/controllers/snazzy"
|
8
|
+
source_path = File.join(gem_path, stimulus_controllers_dest)
|
9
|
+
dest_path = Rails.root.join(stimulus_controllers_dest)
|
10
|
+
FileUtils.mkdir_p(dest_path) unless File.directory?(dest_path)
|
11
|
+
FileUtils.cp_r("#{source_path}/.", dest_path)
|
12
|
+
puts "Snazzy controllers have been installed successfully."
|
13
|
+
|
14
|
+
parent_js_path = "app/javascript/controllers/index.js"
|
15
|
+
|
16
|
+
content = File.readlines(parent_js_path)
|
17
|
+
|
18
|
+
if content.any? { |line| line.strip == 'import "./snazzy"' }
|
19
|
+
puts 'The line "import "./snazzy"" is already present in the file.'
|
20
|
+
else
|
21
|
+
File.open(parent_js_path, "a") do |file|
|
22
|
+
file.puts 'import "./snazzy"'
|
23
|
+
end
|
24
|
+
|
25
|
+
puts "Updated the index.js file successfully."
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: snazzy_ui
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dan Brown
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-07-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '7.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '7.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: slim-rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.6'
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 3.6.3
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '3.6'
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 3.6.3
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: view_component
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '3.10'
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 3.10.0
|
57
|
+
type: :runtime
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - "~>"
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '3.10'
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 3.10.0
|
67
|
+
description: Snazzy provides a suite of stylish, reusable UI components for Ruby on
|
68
|
+
Rails applications that leverage GitHub's ViewComponents, TailwindCSS and Rails.
|
69
|
+
email:
|
70
|
+
- dbrown@occameducation.com
|
71
|
+
executables: []
|
72
|
+
extensions: []
|
73
|
+
extra_rdoc_files: []
|
74
|
+
files:
|
75
|
+
- README.md
|
76
|
+
- app/components/snazzy/array_input_component.html.slim
|
77
|
+
- app/components/snazzy/array_input_component.rb
|
78
|
+
- app/components/snazzy/badge_component.rb
|
79
|
+
- app/components/snazzy/badge_component.slim
|
80
|
+
- app/components/snazzy/base_component.rb
|
81
|
+
- app/components/snazzy/copy_button_component.rb
|
82
|
+
- app/components/snazzy/copy_button_component.slim
|
83
|
+
- app/components/snazzy/pill_component.rb
|
84
|
+
- app/components/snazzy/pill_component.slim
|
85
|
+
- app/components/snazzy/tag_cloud_component.rb
|
86
|
+
- app/components/snazzy/tag_cloud_component.slim
|
87
|
+
- app/components/snazzy/tags/grey_tag_component.rb
|
88
|
+
- app/components/snazzy/tags/grey_tag_component.slim
|
89
|
+
- app/components/snazzy/tooltip_component.rb
|
90
|
+
- app/components/snazzy/tooltip_component.slim
|
91
|
+
- app/javascript/controllers/snazzy/clipboard_controller.js
|
92
|
+
- app/javascript/controllers/snazzy/index.js
|
93
|
+
- app/javascript/controllers/snazzy/tags_controller.js
|
94
|
+
- app/javascript/controllers/snazzy/tooltip_controller.js
|
95
|
+
- lib/snazzy/engine.rb
|
96
|
+
- lib/snazzy/version.rb
|
97
|
+
- lib/snazzy_ui.rb
|
98
|
+
- lib/tasks/install.rake
|
99
|
+
homepage: https://github.com/lordofthedanse/snazzy_ui
|
100
|
+
licenses:
|
101
|
+
- MIT
|
102
|
+
metadata:
|
103
|
+
homepage_uri: https://github.com/lordofthedanse/snazzy_ui
|
104
|
+
source_code_uri: https://github.com/lordofthedanse/snazzy_ui
|
105
|
+
bug_tracker_uri: https://github.com/lordofthedanse/snazzy_ui/issues
|
106
|
+
changelog_uri: https://github.com/lordofthedanse/snazzy_ui/blob/main/CHANGELOG.md
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options: []
|
109
|
+
require_paths:
|
110
|
+
- lib
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '3.2'
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
requirements: []
|
122
|
+
rubygems_version: 3.5.22
|
123
|
+
signing_key:
|
124
|
+
specification_version: 4
|
125
|
+
summary: A collection of snazzy UI components for Rails applications.
|
126
|
+
test_files: []
|