rails-active-ui 0.3.8 → 0.4.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/README.md +195 -6
- data/Rakefile +1 -1
- data/app/components/accordion_component.rb +41 -24
- data/app/components/accordion_item_component.rb +40 -0
- data/app/components/ad_component.rb +1 -1
- data/app/components/api_component.rb +1 -1
- data/app/components/breadcrumb_component.rb +1 -1
- data/app/components/button_group_component.rb +36 -0
- data/app/components/button_to_component.rb +1 -1
- data/app/components/calendar_component.rb +24 -20
- data/app/components/card_component.rb +5 -3
- data/app/components/comment_component.rb +5 -3
- data/app/components/comment_group_component.rb +27 -0
- data/app/components/comment_reply_component.rb +47 -0
- data/app/components/comment_reply_group_component.rb +25 -0
- data/app/components/dimmer_component.rb +1 -1
- data/app/components/divider_component.rb +1 -1
- data/app/components/dropdown_component.rb +6 -2
- data/app/components/embed_component.rb +1 -1
- data/app/components/emoji_component.rb +1 -1
- data/app/components/feed_component.rb +16 -4
- data/app/components/feed_item_component.rb +85 -0
- data/app/components/field_component.rb +42 -0
- data/app/components/flag_component.rb +1 -1
- data/app/components/flyout_component.rb +3 -2
- data/app/components/h_stack_component.rb +1 -1
- data/app/components/header_component.rb +2 -2
- data/app/components/image_component.rb +1 -1
- data/app/components/input_component.rb +22 -41
- data/app/components/item_component.rb +3 -2
- data/app/components/item_group_component.rb +1 -1
- data/app/components/list_component.rb +2 -2
- data/app/components/list_content_component.rb +27 -0
- data/app/components/list_description_component.rb +17 -0
- data/app/components/list_header_component.rb +28 -0
- data/app/components/list_item_component.rb +37 -0
- data/app/components/loader_component.rb +1 -1
- data/app/components/menu_component.rb +1 -1
- data/app/components/modal_component.rb +2 -1
- data/app/components/nag_component.rb +1 -1
- data/app/components/overlay_component.rb +1 -1
- data/app/components/placeholder_component.rb +37 -13
- data/app/components/popup_component.rb +1 -1
- data/app/components/progress_component.rb +1 -1
- data/app/components/pusher_component.rb +1 -1
- data/app/components/rail_component.rb +1 -1
- data/app/components/rating_component.rb +1 -1
- data/app/components/reveal_component.rb +3 -2
- data/app/components/row_component.rb +4 -0
- data/app/components/search_component.rb +1 -1
- data/app/components/segment_group_component.rb +1 -1
- data/app/components/shape_component.rb +1 -1
- data/app/components/sidebar_component.rb +1 -1
- data/app/components/site_component.rb +1 -1
- data/app/components/slider_component.rb +1 -1
- data/app/components/state_component.rb +1 -1
- data/app/components/statistic_component.rb +3 -2
- data/app/components/step_component.rb +2 -2
- data/app/components/step_group_component.rb +1 -1
- data/app/components/sticky_component.rb +1 -1
- data/app/components/style_component.rb +1 -1
- data/app/components/sub_accordion_component.rb +25 -0
- data/app/components/sub_header_component.rb +1 -1
- data/app/components/sub_menu_component.rb +1 -1
- data/app/components/table_cell_component.rb +2 -2
- data/app/components/table_component.rb +2 -2
- data/app/components/tag_component.rb +57 -0
- data/app/components/tag_group_component.rb +33 -0
- data/app/components/text_component.rb +1 -1
- data/app/components/toast_component.rb +1 -1
- data/app/components/transition_component.rb +2 -2
- data/app/components/v_stack_component.rb +1 -1
- data/app/components/visibility_component.rb +1 -1
- data/app/helpers/component_helper.rb +23 -6
- data/app/helpers/ui/fomantic_form_builder.rb +155 -201
- data/app/javascript/ui/controllers/fui_calendar_controller.js +18 -8
- data/lib/ui/version.rb +1 -1
- metadata +15 -3
- data/app/components/checkbox_component.rb +0 -41
- data/app/components/label_component.rb +0 -49
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 847077ab92e13f582035970b65c03270e249b0c07d231aaa24ec9b8bd67b3747
|
|
4
|
+
data.tar.gz: 19289c0f34216d56bb1445ddba8a2458e53907f4609c7cd0daf9f725384e12f6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 11ecf07a55ce294c3551c1a736d588aab2f02faf7e8fa53a20e44e11e1b4200d5996c867eb5bd77c6e6d99a1078269b73f2c735f5e34a59e14799b39bceca93f
|
|
7
|
+
data.tar.gz: c63697323a25b300048da0744a79fcc81b2d952bebf36983bf31ecb6810c90ff754b1cb887f419ad5baf5e5009e39e744588cabc53cd9a8eb45ffccecab1bf11
|
data/README.md
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
# rails-active-ui
|
|
2
2
|
|
|
3
|
-
A Fomantic-UI component system for Rails. Views use `.html.ruby` files with PascalCase component calls
|
|
3
|
+
A Fomantic-UI component system for Rails. Views use `.html.ruby` files with PascalCase component calls built on `ActiveModel::Attributes`.
|
|
4
4
|
|
|
5
5
|
## Setup
|
|
6
6
|
|
|
7
7
|
Add to your Gemfile:
|
|
8
8
|
|
|
9
9
|
```ruby
|
|
10
|
-
gem "rails-active-ui"
|
|
10
|
+
gem "rails-active-ui", path: "engines/rails-active-ui"
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
Requires Ruby >= 3.2 and Rails ~> 8.1.
|
|
14
|
+
|
|
13
15
|
### Engine initializers
|
|
14
16
|
|
|
15
17
|
The gem's engine (`Ui::Engine`) registers the following automatically:
|
|
@@ -48,7 +50,7 @@ const application = Application.start()
|
|
|
48
50
|
registerFuiControllers(application)
|
|
49
51
|
```
|
|
50
52
|
|
|
51
|
-
These are thin jQuery bridge controllers that initialize Fomantic-UI widgets in `connect()` and tear them down in `disconnect()`, making them Turbo-compatible.
|
|
53
|
+
These are thin jQuery bridge controllers that initialize Fomantic-UI widgets in `connect()` and tear them down in `disconnect()`, making them Turbo-compatible. There are 28 Fomantic bridge controllers covering accordion, api, calendar, checkbox, datatable, dimmer, dropdown, embed, emoji-picker, flyout, form, item-list, modal, nag, popup, progress, rating, search, shape, sidebar, site, slider, state, sticky, tab, toast, transition, and visibility.
|
|
52
54
|
|
|
53
55
|
### Rails engine usage
|
|
54
56
|
|
|
@@ -64,15 +66,157 @@ class Engine < ::Rails::Engine
|
|
|
64
66
|
end
|
|
65
67
|
```
|
|
66
68
|
|
|
69
|
+
## View DSL
|
|
70
|
+
|
|
71
|
+
Views use `.html.ruby` files with a pure-Ruby DSL. Every PascalCase call renders a component:
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
Header(size: :h2) { "Hello World" }
|
|
75
|
+
Segment(inverted: true) {
|
|
76
|
+
Paragraph { "Some content here" }
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Text content
|
|
81
|
+
|
|
82
|
+
There are two ways to output text inside a block:
|
|
83
|
+
|
|
84
|
+
- **Block return value** -- when the block contains only a string, just return it directly:
|
|
85
|
+
```ruby
|
|
86
|
+
Header { "Page Title" }
|
|
87
|
+
Button(color: "green") { "Save" }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
- **`Text` component** -- when you need to mix text with other components in the same block, use `Text { "..." }` for each piece of text and `NbSpace()` for whitespace between components:
|
|
91
|
+
```ruby
|
|
92
|
+
Button(color: "blue") {
|
|
93
|
+
Icon(name: "edit")
|
|
94
|
+
NbSpace()
|
|
95
|
+
Text { "Add Reply" }
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Prefer `{ "string" }` over `{ Text { "string" } }` when the block contains only text.
|
|
100
|
+
|
|
101
|
+
Never use lowercase `text` to emit inline content alongside components -- use `Text { "..." }` instead. Never embed leading/trailing spaces into text strings -- use `NbSpace()`. Every HTML element must be rendered through a component call.
|
|
102
|
+
|
|
103
|
+
### Inline elements
|
|
104
|
+
|
|
105
|
+
Common HTML elements that appear inside components:
|
|
106
|
+
|
|
107
|
+
| HTML | Ruby DSL |
|
|
108
|
+
|------|----------|
|
|
109
|
+
| `<a>Text</a>` | `LinkTo { "Text" }` |
|
|
110
|
+
| `<a class="user">Name</a>` | `LinkTo(class: "user") { "Name" }` |
|
|
111
|
+
| `<a href="/path">Link</a>` | `LinkTo(href: "/path") { "Link" }` |
|
|
112
|
+
| `<span class="ui red text">` | `Text(color: "red") { "..." }` |
|
|
113
|
+
| `<div>` (generic) | `Wrapper(class: "...") { ... }` |
|
|
114
|
+
| ` ` | `NbSpace()` |
|
|
115
|
+
|
|
116
|
+
### Utility helpers
|
|
117
|
+
|
|
118
|
+
| Helper | Output |
|
|
119
|
+
|--------|--------|
|
|
120
|
+
| `text "string"` | Appends plain text to the output buffer |
|
|
121
|
+
| `NbSpace()` | Appends ` ` |
|
|
122
|
+
| `Partial("path/to/partial")` | Delegates to `render` |
|
|
123
|
+
| `DocType()` | Outputs `<!DOCTYPE html>` |
|
|
124
|
+
| `StylesheetLink("file.css")` | Wraps `stylesheet_link_tag` |
|
|
125
|
+
| `JavascriptImportmap()` | Wraps `javascript_importmap_tags` |
|
|
126
|
+
| `CsrfMetaTags()` | Wraps `csrf_meta_tags` |
|
|
127
|
+
| `CspMetaTag()` | Wraps `csp_meta_tag` |
|
|
128
|
+
| `ContentFor(:name) { ... }` | Wraps `content_for` |
|
|
129
|
+
| `Style("css string")` | Renders an inline `<style>` tag |
|
|
130
|
+
|
|
131
|
+
## Components
|
|
132
|
+
|
|
133
|
+
All components are registered in `ComponentHelper::COMPONENT_MAP` (`app/helpers/component_helper.rb`). Every PascalCase call in a `.html.ruby` view resolves through this map.
|
|
134
|
+
|
|
135
|
+
### Base class
|
|
136
|
+
|
|
137
|
+
All components inherit from `Component` (`app/lib/component.rb`), which provides:
|
|
138
|
+
|
|
139
|
+
- **`ActiveModel::Attributes`** for declarative attribute definitions with types and defaults
|
|
140
|
+
- **Named slots** via the `slot` class macro -- yields `self` so callers fill regions:
|
|
141
|
+
```ruby
|
|
142
|
+
Card { |c|
|
|
143
|
+
c.header { "Title" }
|
|
144
|
+
c.description { "Body text" }
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
- **HTML pass-through** -- `id`, `class`, `data`, `style`, `role`, `tabindex`, `title`, `aria`, `target`, `rel` bypass attributes and merge into the rendered tag
|
|
148
|
+
- **`merge_html_options`** -- merges component-computed options with user-provided HTML options (classes concatenate, data hashes deep-merge)
|
|
149
|
+
- **Dev annotations** -- in development, output is wrapped in `<!-- BEGIN ClassName -->` / `<!-- END ClassName -->` comments
|
|
150
|
+
|
|
151
|
+
### Component inventory
|
|
152
|
+
|
|
153
|
+
**Layout Primitives:** VStack, HStack, Column, Row, Pusher, Overlay, LinkTo, SubHeader
|
|
154
|
+
|
|
155
|
+
**Globals:** Reset, Site, Wrapper, Template, BackButton
|
|
156
|
+
|
|
157
|
+
**Elements:** Button, ButtonGroup, ButtonTo, Paragraph, Container, Divider, Emoji, Flag, Header, Icon, Image, Input, Tag, TagGroup, List, ListItem, ListContent, ListHeader, ListDescription, Loader, Placeholder, Rail, Reveal, Segment, SegmentGroup, Step, StepGroup, Text
|
|
158
|
+
|
|
159
|
+
**Collections:** Breadcrumb, Form, Grid, Menu, MenuItem, SubMenu, Message, Table, TableRow, TableCell
|
|
160
|
+
|
|
161
|
+
**Views:** Ad, ItemGroup, Card, Comment, CommentGroup, CommentReplyGroup, CommentReply, Feed, FeedItem, Item, Statistic
|
|
162
|
+
|
|
163
|
+
**Modules:** Accordion, AccordionItem, SubAccordion, Calendar, Dimmer, Dropdown, Embed, Flyout, Modal, Nag, Popup, Progress, Slider, Rating, Search, Shape, Sidebar, Sticky, Tab, TabGroup, Toast, Transition
|
|
164
|
+
|
|
165
|
+
**Behaviors:** Api, State, Visibility
|
|
166
|
+
|
|
167
|
+
**Blocks:** ResourceListBlock
|
|
168
|
+
|
|
169
|
+
### Component patterns
|
|
170
|
+
|
|
171
|
+
**Simple component** -- attributes map to CSS class modifiers:
|
|
172
|
+
```ruby
|
|
173
|
+
Button(color: "red", size: "large") { "Delete" }
|
|
174
|
+
# => <button class="ui red large button">Delete</button>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Component with slots** -- child regions are filled via yielded self:
|
|
178
|
+
```ruby
|
|
179
|
+
FeedItem { |e|
|
|
180
|
+
e.label { Image(src: "/images/avatar/small/elliot.jpg") }
|
|
181
|
+
e.summary {
|
|
182
|
+
LinkTo(class: "user") { "Elliot Fu" }
|
|
183
|
+
NbSpace()
|
|
184
|
+
Text { "added you as a friend" }
|
|
185
|
+
}
|
|
186
|
+
e.date_inline { "1 Hour Ago" }
|
|
187
|
+
e.meta {
|
|
188
|
+
LinkTo(class: "like") {
|
|
189
|
+
Icon(name: "like")
|
|
190
|
+
NbSpace()
|
|
191
|
+
Text { "4 Likes" }
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**CSS class passthrough** -- for one-off Fomantic classes not covered by component attributes:
|
|
198
|
+
```ruby
|
|
199
|
+
Menu(class: "equal width") { ... }
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### method_missing behavior
|
|
203
|
+
|
|
204
|
+
PascalCase calls not in `COMPONENT_MAP` are handled by `method_missing`:
|
|
205
|
+
|
|
206
|
+
1. If inside a `Form()` block, the call is tried as an underscored form builder method (e.g. `TextField` -> `f.text_field`)
|
|
207
|
+
2. Otherwise, it falls through to `tag.public_send(tag_name)`, generating an arbitrary HTML element
|
|
208
|
+
|
|
209
|
+
This means typos in component names will not raise errors -- they silently generate unexpected HTML tags.
|
|
210
|
+
|
|
67
211
|
## Form Builder
|
|
68
212
|
|
|
69
|
-
rails-active-ui ships with `FomanticFormBuilder`, a drop-in `ActionView::Helpers::FormBuilder` subclass that wraps every field helper in Fomantic-UI markup.
|
|
213
|
+
rails-active-ui ships with `Ui::FomanticFormBuilder`, a drop-in `ActionView::Helpers::FormBuilder` subclass that wraps every field helper in Fomantic-UI markup.
|
|
70
214
|
|
|
71
215
|
Set it as the default in your `ApplicationController`:
|
|
72
216
|
|
|
73
217
|
```ruby
|
|
74
218
|
class ApplicationController < ActionController::Base
|
|
75
|
-
|
|
219
|
+
default_form_builder Ui::FomanticFormBuilder
|
|
76
220
|
end
|
|
77
221
|
```
|
|
78
222
|
|
|
@@ -97,7 +241,18 @@ Form(url: users_path, method: :post) {
|
|
|
97
241
|
| `EmailField(:email)` | `f.email_field :email` | Email input |
|
|
98
242
|
| `PasswordField(:password)` | `f.password_field :password` | Password input |
|
|
99
243
|
| `NumberField(:age)` | `f.number_field :age` | Number input |
|
|
244
|
+
| `UrlField(:website)` | `f.url_field :website` | URL input |
|
|
245
|
+
| `TelephoneField(:phone)` | `f.telephone_field :phone` | Telephone input |
|
|
246
|
+
| `SearchField(:q)` | `f.search_field :q` | Search input |
|
|
247
|
+
| `ColorField(:color)` | `f.color_field :color` | Color picker |
|
|
248
|
+
| `RangeField(:volume)` | `f.range_field :volume` | Range slider |
|
|
249
|
+
| `DateField(:birthday)` | `f.date_field :birthday` | Date picker (Fomantic calendar) |
|
|
250
|
+
| `DatetimeLocalField(:starts_at)` | `f.datetime_local_field :starts_at` | Datetime picker |
|
|
251
|
+
| `TimeField(:alarm)` | `f.time_field :alarm` | Time picker |
|
|
252
|
+
| `MonthField(:month)` | `f.month_field :month` | Month picker |
|
|
253
|
+
| `WeekField(:week)` | `f.week_field :week` | Week picker |
|
|
100
254
|
| `TextArea(:bio)` | `f.text_area :bio` | Textarea |
|
|
255
|
+
| `EmojiField(:icon)` | `f.emoji_field :icon` | Emoji picker (Stimulus-powered) |
|
|
101
256
|
| `Select(:role, choices)` | `f.select :role, choices` | Select dropdown |
|
|
102
257
|
| `CheckBox(:terms)` | `f.check_box :terms` | Checkbox with Fomantic styling |
|
|
103
258
|
| `RadioButton(:plan, "pro")` | `f.radio_button :plan, "pro"` | Radio button |
|
|
@@ -112,9 +267,11 @@ All field helpers accept these options:
|
|
|
112
267
|
- `label:` -- override label text (`nil` to suppress)
|
|
113
268
|
- `required:` -- adds "required" class and asterisk
|
|
114
269
|
- `disabled:` -- adds "disabled" class
|
|
270
|
+
- `readonly:` -- adds "read-only" class
|
|
115
271
|
- `inline:` -- label sits beside the input
|
|
116
272
|
- `width:` -- Fomantic grid column word (e.g. `"six"`, `"three"`)
|
|
117
|
-
- `error:` -- error message string
|
|
273
|
+
- `error:` -- error message string, adds "error" class
|
|
274
|
+
- `warning:` -- warning message string, adds "warning" class
|
|
118
275
|
- `hint:` -- grey note beneath the input
|
|
119
276
|
- `field_class:` -- extra classes on the wrapping `.field` div
|
|
120
277
|
- `input_class:` -- extra classes on the input element
|
|
@@ -125,6 +282,15 @@ All field helpers accept these options:
|
|
|
125
282
|
- `size:` -- Fomantic size (e.g. `"tiny"`, `"large"`)
|
|
126
283
|
- `basic:` -- basic button style
|
|
127
284
|
- `icon:` -- icon name (e.g. `"checkmark"`)
|
|
285
|
+
- `inverted:` -- inverted style
|
|
286
|
+
|
|
287
|
+
### Checkbox / Radio options
|
|
288
|
+
|
|
289
|
+
- `kind:` -- `:checkbox` (default), `:slider`, or `:toggle`
|
|
290
|
+
- `size:` -- Fomantic size
|
|
291
|
+
- `inverted:` -- inverted style
|
|
292
|
+
- `fitted:` -- removes label padding
|
|
293
|
+
- `right_aligned:` -- label appears on the left
|
|
128
294
|
|
|
129
295
|
### Field groups
|
|
130
296
|
|
|
@@ -144,5 +310,28 @@ Form(url: users_path) {
|
|
|
144
310
|
ErrorMessage("Something went wrong", ["Email is taken"])
|
|
145
311
|
SuccessMessage("All done!", "Profile updated.")
|
|
146
312
|
WarningMessage("Heads up", ["Verify your email"])
|
|
313
|
+
InfoMessage("Note", ["This is informational"])
|
|
147
314
|
}
|
|
148
315
|
```
|
|
316
|
+
|
|
317
|
+
## FuiHelper
|
|
318
|
+
|
|
319
|
+
`FuiHelper` is auto-included into `ActionView::Base` and provides:
|
|
320
|
+
|
|
321
|
+
- **`fui_javascript_tags`** -- emits `<script>` tags for jQuery and all 23 Fomantic-UI component scripts in the correct load order (site and transition first)
|
|
322
|
+
- **`datatable(columns:, options:, &block)`** -- renders a DataTables-powered table wrapped in a `fui-datatable` Stimulus controller div
|
|
323
|
+
|
|
324
|
+
## Example Browser
|
|
325
|
+
|
|
326
|
+
The repository includes a full Rails application that serves as a live example browser for all components. Example views live under `app/views/examples/` organized by Fomantic-UI category:
|
|
327
|
+
|
|
328
|
+
```
|
|
329
|
+
app/views/examples/
|
|
330
|
+
collections/ # breadcrumb, form, grid, menu, message, table
|
|
331
|
+
elements/ # button, container, divider, emoji, flag, header, icon, image, input, list, etc.
|
|
332
|
+
examples/ # attached, bootstrap, dashboard, fixed, grid, homepage, login, etc.
|
|
333
|
+
modules/ # accordion, calendar, checkbox, dimmer, dropdown, tab, etc.
|
|
334
|
+
views/ # advertisement, card, comment, feed, item, statistic
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Run `bin/rails server` and visit `http://localhost:3000` to browse all examples. See `CONVERTING_EXAMPLES.md` for the guide on converting Fomantic-UI HTML docs into `.html.ruby` example views.
|
data/Rakefile
CHANGED
|
@@ -1,43 +1,60 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Accordion — collapsible content
|
|
3
|
+
# Accordion — collapsible content panels.
|
|
4
|
+
#
|
|
5
|
+
# Renders a Fomantic-UI accordion container wired to the fui-accordion
|
|
6
|
+
# Stimulus controller. Block content should contain raw `.title` and
|
|
7
|
+
# `.content` div pairs (built with `tag.div`).
|
|
4
8
|
#
|
|
5
9
|
# Usage:
|
|
6
|
-
# Accordion(
|
|
7
|
-
#
|
|
8
|
-
#
|
|
10
|
+
# Accordion(styled: true) {
|
|
11
|
+
# tag.div(class: "active title") {
|
|
12
|
+
# safe_join([tag.i(class: "dropdown icon"), "What is a dog?"])
|
|
13
|
+
# }
|
|
14
|
+
# tag.div(class: "active content") {
|
|
15
|
+
# tag.p { "A dog is a type of domesticated animal." }
|
|
16
|
+
# }
|
|
9
17
|
# }
|
|
10
18
|
|
|
11
19
|
class AccordionComponent < Component
|
|
12
20
|
include Attachable
|
|
13
21
|
|
|
14
|
-
attribute :
|
|
15
|
-
attribute :
|
|
16
|
-
attribute :
|
|
17
|
-
attribute :
|
|
18
|
-
attribute :
|
|
19
|
-
attribute :
|
|
20
|
-
attribute :
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
attribute :styled, :boolean, default: false
|
|
23
|
+
attribute :fluid, :boolean, default: false
|
|
24
|
+
attribute :inverted, :boolean, default: false
|
|
25
|
+
attribute :basic, :boolean, default: false
|
|
26
|
+
attribute :compact, :boolean, default: false
|
|
27
|
+
attribute :very_compact, :boolean, default: false
|
|
28
|
+
attribute :tree, :boolean, default: false
|
|
29
|
+
attribute :color, :string, default: nil
|
|
30
|
+
attribute :exclusive, :boolean, default: true
|
|
31
|
+
attribute :collapsible, :boolean, default: true
|
|
32
|
+
attribute :duration, :integer, default: 350
|
|
23
33
|
|
|
24
34
|
def to_s
|
|
25
35
|
classes = class_names(
|
|
26
36
|
"ui",
|
|
27
37
|
color,
|
|
28
|
-
{
|
|
29
|
-
"
|
|
30
|
-
|
|
38
|
+
{
|
|
39
|
+
"attached" => attached,
|
|
40
|
+
"styled" => styled,
|
|
41
|
+
"fluid" => fluid,
|
|
42
|
+
"inverted" => inverted,
|
|
43
|
+
"basic" => basic && styled,
|
|
44
|
+
"compact" => compact,
|
|
45
|
+
"very compact" => very_compact,
|
|
46
|
+
"tree" => tree
|
|
47
|
+
},
|
|
48
|
+
"accordion"
|
|
31
49
|
)
|
|
32
50
|
|
|
33
|
-
|
|
34
|
-
|
|
51
|
+
data = { controller: "fui-accordion" }
|
|
52
|
+
data[:fui_accordion_exclusive_value] = "false" unless exclusive
|
|
53
|
+
data[:fui_accordion_collapsible_value] = "false" unless collapsible
|
|
54
|
+
data[:fui_accordion_duration_value] = duration.to_s if duration != 350
|
|
55
|
+
|
|
56
|
+
opts = merge_html_options(class: classes, data: data)
|
|
35
57
|
|
|
36
|
-
tag.
|
|
37
|
-
safe_join([
|
|
38
|
-
tag.summary { @slots[:title] || "" },
|
|
39
|
-
tag.div { @content }
|
|
40
|
-
])
|
|
41
|
-
}
|
|
58
|
+
tag.div(**opts) { @content }
|
|
42
59
|
end
|
|
43
60
|
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# AccordionItem — a single panel inside an Accordion.
|
|
4
|
+
#
|
|
5
|
+
# Renders two sibling divs (.title + .content) with no wrapper,
|
|
6
|
+
# exactly the structure Fomantic-UI expects as direct children of
|
|
7
|
+
# .ui.accordion or a nested .accordion.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# Accordion(styled: true) {
|
|
11
|
+
# AccordionItem(active: true) { |i|
|
|
12
|
+
# i.title { "What is a dog?" }
|
|
13
|
+
# i.content {
|
|
14
|
+
# Paragraph { "A dog is a type of domesticated animal." }
|
|
15
|
+
# }
|
|
16
|
+
# }
|
|
17
|
+
# }
|
|
18
|
+
|
|
19
|
+
class AccordionItemComponent < Component
|
|
20
|
+
attribute :active, :boolean, default: false
|
|
21
|
+
attribute :icon, :string, default: "dropdown"
|
|
22
|
+
|
|
23
|
+
slot :title
|
|
24
|
+
slot :content
|
|
25
|
+
|
|
26
|
+
def to_s
|
|
27
|
+
title_classes = class_names({ "active" => active }, "title")
|
|
28
|
+
content_classes = class_names({ "active" => active }, "content")
|
|
29
|
+
|
|
30
|
+
title_el = @slots[:title] ? tag.div(class: title_classes) {
|
|
31
|
+
safe_join([ tag.i(class: "#{icon} icon"), @slots[:title] ])
|
|
32
|
+
} : nil
|
|
33
|
+
|
|
34
|
+
content_el = @slots[:content] ? tag.div(class: content_classes) {
|
|
35
|
+
@slots[:content]
|
|
36
|
+
} : nil
|
|
37
|
+
|
|
38
|
+
safe_join([ title_el, content_el ])
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -19,6 +19,6 @@ class ApiComponent < Component
|
|
|
19
19
|
data[:fui_api_action_value] = action_val if action_val
|
|
20
20
|
data[:fui_api_state_context_value] = state_context if state_context
|
|
21
21
|
|
|
22
|
-
tag.div(data: data) { @content }
|
|
22
|
+
tag.div(**merge_html_options(data: data)) { @content }
|
|
23
23
|
end
|
|
24
24
|
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# ButtonGroup — a group of buttons displayed together.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# ButtonGroup(color: "teal") {
|
|
7
|
+
# Button { "Save" }
|
|
8
|
+
# Dropdown(floating: true, button: true) { ... }
|
|
9
|
+
# }
|
|
10
|
+
# ButtonGroup(icon: true) {
|
|
11
|
+
# Button { Icon(name: "bold") }
|
|
12
|
+
# Button { Icon(name: "italic") }
|
|
13
|
+
# }
|
|
14
|
+
|
|
15
|
+
class ButtonGroupComponent < Component
|
|
16
|
+
include Sizeable
|
|
17
|
+
|
|
18
|
+
attribute :color, :string, default: nil
|
|
19
|
+
attribute :icon, :boolean, default: false
|
|
20
|
+
attribute :vertical, :boolean, default: false
|
|
21
|
+
attribute :basic, :boolean, default: false
|
|
22
|
+
attribute :labeled, :boolean, default: false
|
|
23
|
+
|
|
24
|
+
def to_s
|
|
25
|
+
classes = class_names(
|
|
26
|
+
"ui",
|
|
27
|
+
color,
|
|
28
|
+
size,
|
|
29
|
+
{ "icon" => icon, "vertical" => vertical, "basic" => basic,
|
|
30
|
+
"labeled" => labeled },
|
|
31
|
+
"buttons"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
tag.div(**merge_html_options(class: classes)) { @content }
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -1,34 +1,38 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Calendar — date/time picker.
|
|
3
|
+
# Calendar — Fomantic-UI date/time picker container.
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
# Calendar(type: :
|
|
7
|
-
# Calendar(type:
|
|
5
|
+
# Standalone usage (wraps any content — Input, Button, or nothing for inline):
|
|
6
|
+
# Calendar(type: "date") { Input(icon: "calendar", icon_position: "left", placeholder: "Date") }
|
|
7
|
+
# Calendar(type: "datetime") { Button { "Select date" } }
|
|
8
|
+
# Calendar(type: "datetime") # inline calendar (no input)
|
|
9
|
+
#
|
|
10
|
+
# Inside a Form, date/time fields render the calendar automatically:
|
|
11
|
+
# Form(url: "#") { DateField(:start_date) }
|
|
8
12
|
|
|
9
13
|
class CalendarComponent < Component
|
|
10
|
-
attribute :type, :string,
|
|
11
|
-
attribute :
|
|
12
|
-
attribute :
|
|
13
|
-
attribute :
|
|
14
|
-
attribute :
|
|
14
|
+
attribute :type, :string, default: "date"
|
|
15
|
+
attribute :format, :string, default: nil
|
|
16
|
+
attribute :min_date, :string, default: nil
|
|
17
|
+
attribute :max_date, :string, default: nil
|
|
18
|
+
attribute :inverted, :boolean, default: false
|
|
19
|
+
attribute :disabled, :boolean, default: false
|
|
20
|
+
attribute :size, :string, default: nil
|
|
15
21
|
|
|
16
22
|
def to_s
|
|
17
23
|
data = { controller: "fui-calendar", fui_calendar_type_value: type }
|
|
18
|
-
data[:fui_calendar_format_value]
|
|
24
|
+
data[:fui_calendar_format_value] = format if format
|
|
19
25
|
data[:fui_calendar_min_date_value] = min_date if min_date
|
|
20
26
|
data[:fui_calendar_max_date_value] = max_date if max_date
|
|
21
27
|
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
classes = class_names(
|
|
29
|
+
"ui",
|
|
30
|
+
size,
|
|
31
|
+
("inverted" if inverted),
|
|
32
|
+
("disabled" if disabled),
|
|
33
|
+
"calendar"
|
|
34
|
+
)
|
|
24
35
|
|
|
25
|
-
tag.div(class:
|
|
26
|
-
tag.div(class: "ui input left icon") {
|
|
27
|
-
safe_join([
|
|
28
|
-
tag.i(class: "calendar icon"),
|
|
29
|
-
tag.input(**input_opts)
|
|
30
|
-
])
|
|
31
|
-
}
|
|
32
|
-
}
|
|
36
|
+
tag.div(**merge_html_options(class: classes, data: data)) { @content }
|
|
33
37
|
end
|
|
34
38
|
end
|
|
@@ -45,12 +45,14 @@ class CardComponent < Component
|
|
|
45
45
|
content_el = content_parts.any? ? tag.div(class: "content") { safe_join(content_parts) } : nil
|
|
46
46
|
extra_el = @slots[:extra] ? tag.div(class: "extra content") { @slots[:extra] } : nil
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
# Only include @content when no slots are used (block-only mode)
|
|
49
|
+
loose_content = @slots.values.any? ? nil : @content.presence
|
|
50
|
+
inner = safe_join([ image_el, content_el, loose_content, extra_el ])
|
|
49
51
|
|
|
50
52
|
if href
|
|
51
|
-
tag.a(class: classes, href: href) { inner }
|
|
53
|
+
tag.a(**merge_html_options(class: classes, href: href)) { inner }
|
|
52
54
|
else
|
|
53
|
-
tag.div(class: classes) { inner }
|
|
55
|
+
tag.div(**merge_html_options(class: classes)) { inner }
|
|
54
56
|
end
|
|
55
57
|
end
|
|
56
58
|
end
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
class CommentComponent < Component
|
|
15
15
|
attribute :collapsed, :boolean, default: false
|
|
16
16
|
attribute :threaded, :boolean, default: false
|
|
17
|
+
attribute :disabled, :boolean, default: false
|
|
17
18
|
|
|
18
19
|
slot :avatar
|
|
19
20
|
slot :author
|
|
@@ -23,7 +24,7 @@ class CommentComponent < Component
|
|
|
23
24
|
|
|
24
25
|
def to_s
|
|
25
26
|
classes = class_names(
|
|
26
|
-
{ "collapsed" => collapsed },
|
|
27
|
+
{ "collapsed" => collapsed, "disabled" => disabled },
|
|
27
28
|
"comment"
|
|
28
29
|
)
|
|
29
30
|
|
|
@@ -38,8 +39,9 @@ class CommentComponent < Component
|
|
|
38
39
|
|
|
39
40
|
content_el = content_parts.any? ? tag.div(class: "content") { safe_join(content_parts) } : nil
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
loose_content = @slots.values.any? ? nil : @content.presence
|
|
43
|
+
tag.div(**merge_html_options(class: classes)) {
|
|
44
|
+
safe_join([ avatar_el, content_el, loose_content ])
|
|
43
45
|
}
|
|
44
46
|
end
|
|
45
47
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# CommentGroup — wrapper for a group of comments.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# CommentGroup(threaded: true) {
|
|
7
|
+
# Comment { |c| ... }
|
|
8
|
+
# Comment { |c| ... }
|
|
9
|
+
# }
|
|
10
|
+
|
|
11
|
+
class CommentGroupComponent < Component
|
|
12
|
+
attribute :threaded, :boolean, default: false
|
|
13
|
+
attribute :minimal, :boolean, default: false
|
|
14
|
+
attribute :inverted, :boolean, default: false
|
|
15
|
+
attribute :size, :string, default: nil
|
|
16
|
+
|
|
17
|
+
def to_s
|
|
18
|
+
classes = class_names(
|
|
19
|
+
"ui",
|
|
20
|
+
size,
|
|
21
|
+
{ "threaded" => threaded, "minimal" => minimal, "inverted" => inverted },
|
|
22
|
+
"comments"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
tag.div(**merge_html_options(class: classes)) { @content }
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# CommentReply — a reply comment nested inside a CommentReplyGroup.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# CommentReplyGroup {
|
|
7
|
+
# CommentReply { |c|
|
|
8
|
+
# c.avatar { Image(src: "avatar.png") }
|
|
9
|
+
# c.author { text "Jenny" }
|
|
10
|
+
# c.metadata { text "Just now" }
|
|
11
|
+
# c.text_slot { text "Great reply!" }
|
|
12
|
+
# c.actions { text "Reply" }
|
|
13
|
+
# }
|
|
14
|
+
# }
|
|
15
|
+
|
|
16
|
+
class CommentReplyComponent < Component
|
|
17
|
+
attribute :disabled, :boolean, default: false
|
|
18
|
+
|
|
19
|
+
slot :avatar
|
|
20
|
+
slot :author
|
|
21
|
+
slot :metadata
|
|
22
|
+
slot :text_slot
|
|
23
|
+
slot :actions
|
|
24
|
+
|
|
25
|
+
def to_s
|
|
26
|
+
classes = class_names(
|
|
27
|
+
{ "disabled" => disabled },
|
|
28
|
+
"comment"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
avatar_el = @slots[:avatar] ? tag.a(class: "avatar") { @slots[:avatar] } : nil
|
|
32
|
+
|
|
33
|
+
content_parts = [
|
|
34
|
+
@slots[:author] ? tag.a(class: "author") { @slots[:author] } : nil,
|
|
35
|
+
@slots[:metadata] ? tag.div(class: "metadata") { @slots[:metadata] } : nil,
|
|
36
|
+
@slots[:text_slot] ? tag.div(class: "text") { @slots[:text_slot] } : nil,
|
|
37
|
+
@slots[:actions] ? tag.div(class: "actions") { @slots[:actions] } : nil
|
|
38
|
+
].compact
|
|
39
|
+
|
|
40
|
+
content_el = content_parts.any? ? tag.div(class: "content") { safe_join(content_parts) } : nil
|
|
41
|
+
|
|
42
|
+
loose_content = @slots.values.any? ? nil : @content.presence
|
|
43
|
+
tag.div(**merge_html_options(class: classes)) {
|
|
44
|
+
safe_join([ avatar_el, content_el, loose_content ])
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
end
|