keystone_ui 0.12.2 → 0.13.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/app/components/keystone/ui/bucket_component.html.erb +11 -0
- data/app/components/keystone/ui/bucket_component.rb +75 -0
- data/app/components/keystone/ui/bucket_series_component.html.erb +5 -0
- data/app/components/keystone/ui/bucket_series_component.rb +21 -0
- data/app/components/keystone/ui/funnel_component.html.erb +1 -1
- data/app/components/keystone/ui/funnel_component.rb +21 -7
- data/app/components/keystone/ui/progress_component.rb +2 -2
- data/app/helpers/keystone_ui_helper.rb +8 -0
- data/lib/keystone_ui/safelist.rb +2 -0
- data/lib/keystone_ui/version.rb +1 -1
- data/the_local/agents/keystone_ui-develop.md +72 -33
- data/the_local/agents/keystone_ui-info.md +56 -48
- data/the_local/agents/keystone_ui-install.md +86 -50
- data/the_local/interface.yml +4 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 903dfcff87cb63fce06692a39fb26b73af673f562f28f3b3ebf72b13b5280642
|
|
4
|
+
data.tar.gz: 7db5a26e387cc0c2b11a311c70e967ec977f5b1636c6d38bf8a42b28d5ffbd9a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 574997b044dfcbb23b6aad3e74065b041d549d1be5a8629f5c08f792720f1fda5d988340516caa581f80c1455fcc3a32080174b8056f06a41c4fa58423236cbb
|
|
7
|
+
data.tar.gz: 30a7877baf982039cff72b9c8ad0dc879a8fdbc8362d8ec15cec9b7b48964aa8dc1786494b25e3c131e1c28a2c000054c33998011ea41b111da749e1863518f3
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<div class="<%= container_classes %>">
|
|
2
|
+
<% if label %>
|
|
3
|
+
<span class="<%= label_classes %>"><%= label %></span>
|
|
4
|
+
<% end %>
|
|
5
|
+
<span class="<%= goal_classes %>"><%= goal %></span>
|
|
6
|
+
<div class="<%= tank_classes %>">
|
|
7
|
+
<div class="<%= fill_classes %>" style="height: <%= fill_percent %>%"></div>
|
|
8
|
+
</div>
|
|
9
|
+
<span class="<%= actual_classes %>"><%= actual %></span>
|
|
10
|
+
<span class="<%= percent_classes %>"><%= percent %>%</span>
|
|
11
|
+
</div>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Keystone
|
|
4
|
+
module Ui
|
|
5
|
+
class BucketComponent < ViewComponent::Base
|
|
6
|
+
CONTAINER_CLASSES = "flex w-24 flex-col items-center gap-1"
|
|
7
|
+
LABEL_CLASSES = "text-center text-sm font-medium text-gray-700 dark:text-gray-300"
|
|
8
|
+
GOAL_CLASSES = "text-xs tabular-nums text-gray-500 dark:text-gray-400"
|
|
9
|
+
TANK_CLASSES = "flex h-40 w-full items-end overflow-hidden rounded-t-sm rounded-b-xl border-2 border-gray-300 bg-gray-50 dark:border-zinc-600 dark:bg-zinc-800"
|
|
10
|
+
ACTUAL_CLASSES = "text-sm font-semibold tabular-nums text-gray-900 dark:text-white"
|
|
11
|
+
PERCENT_CLASSES = "text-xs tabular-nums text-gray-500 dark:text-gray-400"
|
|
12
|
+
FILL_BASE_CLASSES = "w-full transition-all"
|
|
13
|
+
WITHIN_GOAL_FILL_CLASSES = "bg-accent-500"
|
|
14
|
+
OVER_GOAL_FILL_CLASSES = {
|
|
15
|
+
success: "bg-green-500",
|
|
16
|
+
warning: "bg-amber-500"
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
attr_reader :goal, :actual, :label
|
|
20
|
+
|
|
21
|
+
def initialize(goal:, actual:, label: nil, over: :success)
|
|
22
|
+
@goal = goal
|
|
23
|
+
@actual = actual
|
|
24
|
+
@label = label
|
|
25
|
+
@over_fill_classes = OVER_GOAL_FILL_CLASSES.fetch(over)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def percent
|
|
29
|
+
return 0 if goal.zero?
|
|
30
|
+
|
|
31
|
+
(actual.to_f / goal * 100).round
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def fill_percent
|
|
35
|
+
[ percent, 100 ].min
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def fill_classes
|
|
39
|
+
"#{FILL_BASE_CLASSES} #{fill_color_classes}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def container_classes
|
|
43
|
+
CONTAINER_CLASSES
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def label_classes
|
|
47
|
+
LABEL_CLASSES
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def goal_classes
|
|
51
|
+
GOAL_CLASSES
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def tank_classes
|
|
55
|
+
TANK_CLASSES
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def actual_classes
|
|
59
|
+
ACTUAL_CLASSES
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def percent_classes
|
|
63
|
+
PERCENT_CLASSES
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def fill_color_classes
|
|
69
|
+
return @over_fill_classes if actual > goal
|
|
70
|
+
|
|
71
|
+
WITHIN_GOAL_FILL_CLASSES
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Keystone
|
|
4
|
+
module Ui
|
|
5
|
+
class BucketSeriesComponent < ViewComponent::Base
|
|
6
|
+
ROW_CLASSES = "flex flex-wrap justify-center gap-4 sm:justify-start"
|
|
7
|
+
|
|
8
|
+
def initialize(buckets:)
|
|
9
|
+
@buckets = buckets
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def bucket_components
|
|
13
|
+
@buckets.map { |bucket| BucketComponent.new(**bucket) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def row_classes
|
|
17
|
+
ROW_CLASSES
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<span class="<%= label_classes %>"><%= layer.label %></span>
|
|
9
9
|
<span class="<%= value_classes %>"><%= layer.value %></span>
|
|
10
10
|
</div>
|
|
11
|
-
<div class="<%= bar_classes %>" style="width: <%= layer.width_percent %>%"></div>
|
|
11
|
+
<div class="<%= bar_classes %> <%= layer.color_classes %>" style="width: <%= layer.width_percent %>%"></div>
|
|
12
12
|
</div>
|
|
13
13
|
<% end %>
|
|
14
14
|
</div>
|
|
@@ -3,15 +3,22 @@
|
|
|
3
3
|
module Keystone
|
|
4
4
|
module Ui
|
|
5
5
|
class FunnelComponent < ViewComponent::Base
|
|
6
|
-
Layer = Struct.new(:label, :value, :width_percent, :conversion_percent, keyword_init: true)
|
|
6
|
+
Layer = Struct.new(:label, :value, :width_percent, :conversion_percent, :color_classes, keyword_init: true)
|
|
7
7
|
|
|
8
8
|
CONTAINER_CLASSES = "space-y-2"
|
|
9
9
|
LAYER_CLASSES = "space-y-1"
|
|
10
10
|
ROW_CLASSES = "flex items-baseline justify-between gap-3"
|
|
11
|
-
LABEL_CLASSES = "text-sm font-medium text-surface-700 truncate"
|
|
12
|
-
VALUE_CLASSES = "text-sm font-semibold text-surface-900 tabular-nums"
|
|
13
|
-
BAR_CLASSES = "h-8 rounded-md
|
|
14
|
-
TRANSITION_CLASSES = "py-1 text-center text-xs text-surface-500"
|
|
11
|
+
LABEL_CLASSES = "text-sm font-medium text-surface-700 truncate dark:text-surface-300"
|
|
12
|
+
VALUE_CLASSES = "text-sm font-semibold text-surface-900 tabular-nums dark:text-white"
|
|
13
|
+
BAR_CLASSES = "h-8 rounded-md transition-all"
|
|
14
|
+
TRANSITION_CLASSES = "py-1 text-center text-xs text-surface-500 dark:text-surface-400"
|
|
15
|
+
STEP_COLOR_CLASSES = {
|
|
16
|
+
accent: "bg-accent-500",
|
|
17
|
+
sky: "bg-sky-500",
|
|
18
|
+
violet: "bg-violet-500",
|
|
19
|
+
amber: "bg-amber-500",
|
|
20
|
+
rose: "bg-rose-500"
|
|
21
|
+
}.freeze
|
|
15
22
|
|
|
16
23
|
attr_reader :steps
|
|
17
24
|
|
|
@@ -22,12 +29,13 @@ module Keystone
|
|
|
22
29
|
def layers
|
|
23
30
|
previous = nil
|
|
24
31
|
|
|
25
|
-
steps.map do |step|
|
|
32
|
+
steps.each_with_index.map do |step, index|
|
|
26
33
|
layer = Layer.new(
|
|
27
34
|
label: step[:label],
|
|
28
35
|
value: step[:value],
|
|
29
36
|
width_percent: width_percent(step[:value]),
|
|
30
|
-
conversion_percent: conversion_percent(step[:value], previous)
|
|
37
|
+
conversion_percent: conversion_percent(step[:value], previous),
|
|
38
|
+
color_classes: color_classes(step[:color], index)
|
|
31
39
|
)
|
|
32
40
|
previous = step[:value]
|
|
33
41
|
layer
|
|
@@ -64,6 +72,12 @@ module Keystone
|
|
|
64
72
|
|
|
65
73
|
private
|
|
66
74
|
|
|
75
|
+
def color_classes(color, index)
|
|
76
|
+
return STEP_COLOR_CLASSES.fetch(color) if color
|
|
77
|
+
|
|
78
|
+
STEP_COLOR_CLASSES.values[index % STEP_COLOR_CLASSES.size]
|
|
79
|
+
end
|
|
80
|
+
|
|
67
81
|
def conversion_percent(value, previous)
|
|
68
82
|
return nil if previous.nil?
|
|
69
83
|
return 0 if previous.zero?
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
module Keystone
|
|
4
4
|
module Ui
|
|
5
5
|
class ProgressComponent < ViewComponent::Base
|
|
6
|
-
TRACK_CLASSES = "w-full h-2 bg-surface-200 rounded-full overflow-hidden"
|
|
6
|
+
TRACK_CLASSES = "w-full h-2 bg-surface-200 rounded-full overflow-hidden dark:bg-surface-700"
|
|
7
7
|
BAR_CLASSES = "h-full bg-accent-500 rounded-full transition-all"
|
|
8
|
-
LABEL_CLASSES = "mb-1 text-sm font-medium text-surface-700"
|
|
8
|
+
LABEL_CLASSES = "mb-1 text-sm font-medium text-surface-700 dark:text-surface-300"
|
|
9
9
|
|
|
10
10
|
attr_reader :value, :max, :label
|
|
11
11
|
|
|
@@ -193,6 +193,14 @@ module KeystoneUiHelper
|
|
|
193
193
|
render Keystone::Ui::FunnelComponent.new(**args)
|
|
194
194
|
end
|
|
195
195
|
|
|
196
|
+
def ui_bucket(**args)
|
|
197
|
+
render Keystone::Ui::BucketComponent.new(**args)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def ui_bucket_series(**args)
|
|
201
|
+
render Keystone::Ui::BucketSeriesComponent.new(**args)
|
|
202
|
+
end
|
|
203
|
+
|
|
196
204
|
def ui_pipeline(**args)
|
|
197
205
|
render Keystone::Ui::PipelineComponent.new(**args)
|
|
198
206
|
end
|
data/lib/keystone_ui/safelist.rb
CHANGED
|
@@ -51,6 +51,8 @@ module Keystone
|
|
|
51
51
|
Keystone::Ui::FormComponent,
|
|
52
52
|
Keystone::Ui::FileUploadComponent,
|
|
53
53
|
Keystone::Ui::FunnelComponent,
|
|
54
|
+
Keystone::Ui::BucketComponent,
|
|
55
|
+
Keystone::Ui::BucketSeriesComponent,
|
|
54
56
|
Keystone::Ui::PipelineComponent,
|
|
55
57
|
Keystone::Ui::CodeComponent,
|
|
56
58
|
Keystone::Ui::DisclosureComponent,
|
data/lib/keystone_ui/version.rb
CHANGED
|
@@ -29,17 +29,23 @@ this one's.
|
|
|
29
29
|
|
|
30
30
|
Every entry point is a view helper called from ERB. Keywords with defaults are
|
|
31
31
|
optional; the rest are required. Symbol options are validated — an unrecognized
|
|
32
|
-
one raises at render time.
|
|
32
|
+
one raises at render time. Two exceptions: `ui_page`'s `padding:` treats any
|
|
33
|
+
value other than `:none` as `:standard`, and `ui_pipeline`'s box `accent:` falls
|
|
34
|
+
back to `:muted`.
|
|
35
|
+
|
|
36
|
+
Six helpers — `ui_page`, `ui_section`, `ui_page_header`, `ui_card`, `ui_alert`,
|
|
37
|
+
`ui_badge` — also accept `class:`, a string of classes appended to the helper's
|
|
38
|
+
outer element. See Conventions before using it.
|
|
33
39
|
|
|
34
40
|
### Page shells and layout
|
|
35
41
|
|
|
36
|
-
- `ui_page(max_width: :full, padding: :standard, top_offset: nil)` —
|
|
37
|
-
block. The outer wrapper for a screen. `max_width:` `:sm` `:md` `:lg`
|
|
38
|
-
`:
|
|
39
|
-
|
|
40
|
-
- `ui_section(title: nil, subtitle: nil, action: nil, spacing: :md)`
|
|
41
|
-
block. A titled block of content with an optional right-aligned
|
|
42
|
-
`action:` is `{ label:, href: }`; `spacing:` `:sm` `:md` `:lg`.
|
|
42
|
+
- `ui_page(max_width: :full, padding: :standard, top_offset: nil, class: nil)` —
|
|
43
|
+
takes a block. The outer wrapper for a screen. `max_width:` `:sm` `:md` `:lg`
|
|
44
|
+
`:xl` `:full`; `padding:` `:standard` or `:none`; `top_offset:` `:sm` `:md`
|
|
45
|
+
`:lg` `:xl` to clear a fixed navbar.
|
|
46
|
+
- `ui_section(title: nil, subtitle: nil, action: nil, spacing: :md, class: nil)`
|
|
47
|
+
— takes a block. A titled block of content with an optional right-aligned
|
|
48
|
+
link. `action:` is `{ label:, href: }`; `spacing:` `:sm` `:md` `:lg`.
|
|
43
49
|
- `ui_panel(padding: :md, radius: :lg, shadow: true)` — takes a block. A bordered
|
|
44
50
|
card surface. `padding:` `:sm` `:md` `:lg`; `radius:` `:md` `:lg` `:xl`.
|
|
45
51
|
- `ui_grid(cols: { default: 1 }, gap: :md, gap_x: nil, gap_y: nil)` — takes a
|
|
@@ -48,10 +54,10 @@ one raises at render time.
|
|
|
48
54
|
`:xl`; passing `gap_x:`/`gap_y:` replaces `gap:` entirely.
|
|
49
55
|
- `ui_card_link(href:, padding: :md, shadow: true)` — takes a block. A whole
|
|
50
56
|
panel that is one link. `padding:` `:sm` `:md` `:lg`.
|
|
51
|
-
- `ui_card(title:, summary:, link:, cta: "Read more", edge_to_edge: false)`
|
|
52
|
-
fixed title/summary/CTA card. `edge_to_edge: true` drops the side border and
|
|
57
|
+
- `ui_card(title:, summary:, link:, cta: "Read more", edge_to_edge: false, class: nil)`
|
|
58
|
+
— a fixed title/summary/CTA card. `edge_to_edge: true` drops the side border and
|
|
53
59
|
corner rounding below `sm:` so it spans the full width on mobile.
|
|
54
|
-
- `ui_page_header(title:, subtitle: nil, action_url: nil, action_label: "Add new")`
|
|
60
|
+
- `ui_page_header(title:, subtitle: nil, action_url: nil, action_label: "Add new", class: nil)`
|
|
55
61
|
— takes a block yielding the header. Desktop-only page title (hidden below
|
|
56
62
|
`sm:`). Call `header.action { ... }` in the block to place a custom control on
|
|
57
63
|
the right; only what `action` receives is rendered. Passing `action_url:`
|
|
@@ -93,10 +99,14 @@ one raises at render time.
|
|
|
93
99
|
- `ui_form_field(attribute:, label: nil, type: :text, required: false, hint: nil, placeholder: nil, min: nil, max: nil, step: nil, value: nil, options: [], errors: [])`
|
|
94
100
|
— a labeled field with hint and error text. This is the default way to render
|
|
95
101
|
an input. `type:` `:text` `:number` `:email` `:password` `:date` `:textarea`
|
|
96
|
-
`:checkbox` `:select`. `
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
102
|
+
`:checkbox` `:select`. `attribute:` is used verbatim as the input's `name`, so
|
|
103
|
+
pass the full param name the controller expects, e.g. `"quote[title]"`, and
|
|
104
|
+
pass `label:` whenever the attribute is a nested name. `label:` defaults to the
|
|
105
|
+
attribute with underscores turned to spaces and the first letter capitalized.
|
|
106
|
+
`options:` is for `:select` and takes `[[label, value], ...]`; `value:` picks
|
|
107
|
+
the selected option, and a non-required select gets a leading blank option. A
|
|
108
|
+
`:checkbox` renders its label beside the box, submits `"0"` when unchecked and
|
|
109
|
+
`"1"` when checked, and pre-checks when `value:` is `"1"`. `errors:` is an
|
|
100
110
|
array of message strings.
|
|
101
111
|
- `ui_input(name:, type: :text, value: nil, placeholder: nil, disabled: false, min: nil, max: nil, step: nil)`
|
|
102
112
|
— a bare styled input with no label. `type:` `:text` `:number` `:email`
|
|
@@ -107,9 +117,10 @@ one raises at render time.
|
|
|
107
117
|
— a bare styled select. `options:` is `[[label, value], ...]`;
|
|
108
118
|
`include_blank:` is the text of a leading empty option.
|
|
109
119
|
- `ui_multi_select(name:, label:, options:, selected: [])` — a dropdown of
|
|
110
|
-
checkboxes all posting under `name
|
|
111
|
-
|
|
112
|
-
|
|
120
|
+
checkboxes all posting under `name`, used verbatim; pass an array name such as
|
|
121
|
+
`"status[]"` so every checked value arrives. `options:` is
|
|
122
|
+
`[[label, value], ...]`; `selected:` is the values to pre-check. The trigger
|
|
123
|
+
reads "All <label>" when nothing is checked and "N selected" otherwise.
|
|
113
124
|
- `ui_file_upload(name:, label: nil, accept: nil, multiple: false, hint: nil)` —
|
|
114
125
|
a drop zone with drag-and-drop and selected-file feedback. Requires the
|
|
115
126
|
enclosing form to be multipart.
|
|
@@ -155,9 +166,10 @@ one raises at render time.
|
|
|
155
166
|
— renders an `<a>` when `href:` is given and a `<button>` otherwise. `variant:`
|
|
156
167
|
`:primary` `:secondary` `:danger`; `size:` `:sm` `:md` `:lg`; `type:` applies
|
|
157
168
|
only to the button form.
|
|
158
|
-
- `ui_badge(label:, variant: :neutral)` — a pill. `variant:`
|
|
159
|
-
`:success` `:danger` `:warning` `:info`.
|
|
160
|
-
- `ui_alert(message:, type: :info, title: nil, dismissible: false)` —
|
|
169
|
+
- `ui_badge(label:, variant: :neutral, class: nil)` — a pill. `variant:`
|
|
170
|
+
`:neutral` `:success` `:danger` `:warning` `:info`.
|
|
171
|
+
- `ui_alert(message:, type: :info, title: nil, dismissible: false, class: nil)` —
|
|
172
|
+
a banner.
|
|
161
173
|
`type:` `:info` `:success` `:warning` `:error`. `dismissible: true` adds a
|
|
162
174
|
close control.
|
|
163
175
|
- `ui_progress(value:, max:, label: nil)` — a labeled progress bar. The percent
|
|
@@ -185,14 +197,19 @@ one raises at render time.
|
|
|
185
197
|
tab dispatches a `tab-switcher:change` event carrying the clicked index —
|
|
186
198
|
showing and hiding the matching panels is the app's job.
|
|
187
199
|
- `ui_modal(title:, size: :md)` — takes a block holding the body. `size:` `:sm`
|
|
188
|
-
`:md` `:lg` `:xl`. Renders hidden
|
|
189
|
-
backdrop click
|
|
190
|
-
the
|
|
200
|
+
`:md` `:lg` `:xl`. Renders hidden, and closes on its own close button and on a
|
|
201
|
+
backdrop click. Nothing in the gem opens it: the modal's outermost element
|
|
202
|
+
carries the `hidden` class, and the app's own code must remove that class to
|
|
203
|
+
show it. A `data-action="modal#open"` placed outside the modal does nothing.
|
|
191
204
|
- `ui_swipe_deck(items:, empty_title: "All done!", empty_subtitle: nil)` — takes
|
|
192
205
|
a block yielding the deck. Call `deck.item { |item| ... }` in the block to
|
|
193
|
-
render one card's face. Accepting a card
|
|
194
|
-
|
|
195
|
-
|
|
206
|
+
render one card's face. Accepting a card (button or swipe right) dispatches a
|
|
207
|
+
bubbling `swipe-deck:complete` event and rejecting (button or swipe left)
|
|
208
|
+
dispatches `swipe-deck:skip`. Both carry `detail.itemId` — the item's `id`, or
|
|
209
|
+
its position in `items:` when it has none — and `detail.card`; `complete` also
|
|
210
|
+
carries `detail.value`, read from an input inside the card's face marked
|
|
211
|
+
`data-swipe-deck-value`, or `null`. The app must listen and persist the
|
|
212
|
+
outcome.
|
|
196
213
|
|
|
197
214
|
### Charts and analytics
|
|
198
215
|
|
|
@@ -200,10 +217,27 @@ one raises at render time.
|
|
|
200
217
|
`:sm` `:md` `:lg`.
|
|
201
218
|
- `ui_line_chart(series:, labels:, height: :md)` — a line chart. `labels:` is the
|
|
202
219
|
x-axis labels; `series:` is `[{ name:, data:, color:, dashed: }, ...]` where
|
|
203
|
-
`color:` and `dashed
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
220
|
+
`color:` (a CSS color string for the line) and `dashed: true` are optional.
|
|
221
|
+
`height:` `:sm` `:md` `:lg`.
|
|
222
|
+
- `ui_funnel(steps:)` — a conversion funnel. `steps:` is
|
|
223
|
+
`[{ label:, value:, color: }, ...]` in order, with `color:` optional. Bar
|
|
224
|
+
widths are relative to the first step; the caption between two layers is the
|
|
225
|
+
step-to-step conversion. Each bar takes the next colour in the order
|
|
226
|
+
`:accent` `:sky` `:violet` `:amber` `:rose`, starting again after `:rose`; a
|
|
227
|
+
step passing one of those symbols as `color:` uses it instead, and any other
|
|
228
|
+
symbol raises. Divide-by-zero safe, no JavaScript.
|
|
229
|
+
- `ui_bucket(goal:, actual:, label: nil, over: :success)` — an upright container
|
|
230
|
+
for one target, filled from the bottom toward `goal:`. It shows the optional
|
|
231
|
+
`label` on top, then `goal`, the container, `actual`, and the percent reached.
|
|
232
|
+
`goal` and `actual` are printed exactly as given, so pass numbers already
|
|
233
|
+
formatted as needed. The percent is `actual / goal * 100` rounded and **not**
|
|
234
|
+
clamped (so 150% shows as 150%), and it is 0 when `goal` is zero. The fill
|
|
235
|
+
stops at the top once the goal is reached. Within the goal the fill uses the
|
|
236
|
+
accent colour; over it the fill turns green with `over: :success` or amber with
|
|
237
|
+
`over: :warning`, and any other symbol raises. No JavaScript.
|
|
238
|
+
- `ui_bucket_series(buckets:)` — a row of buckets that wraps onto more rows on
|
|
239
|
+
narrow screens. `buckets:` is an array of hashes, each taking the same keywords
|
|
240
|
+
as `ui_bucket`, e.g. `[{ goal: 10, actual: 7, label: "Mon" }, ...]`.
|
|
207
241
|
- `ui_pipeline(title:, boxes:, links:, subtitle: nil)` — a staged flow diagram
|
|
208
242
|
for event flows, approval chains, or state machines. `boxes:` is
|
|
209
243
|
`[{ label:, count:, accent:, action: }, ...]` where `count:` and `accent:`
|
|
@@ -281,8 +315,13 @@ one raises at render time.
|
|
|
281
315
|
layered onto a helper's output fight the component and drift the moment the gem
|
|
282
316
|
updates. The gem owns spacing, color, borders, radius, shadow, and dark mode.
|
|
283
317
|
- **Never restyle a helper from the outside** — no wrapper div that overrides its
|
|
284
|
-
padding or width, no
|
|
285
|
-
|
|
318
|
+
padding or width, no CSS targeting its markup. Choose a different option
|
|
319
|
+
symbol instead, or say the helper does not fit.
|
|
320
|
+
- **`class:` needs the developer's approval.** The six helpers that accept it
|
|
321
|
+
append whatever it holds to their outer element, so it can override anything
|
|
322
|
+
the gem sets. Before passing it, name the class and the reason to the
|
|
323
|
+
developer and wait for a yes; if the same class keeps being needed, it is a
|
|
324
|
+
missing option in the gem.
|
|
286
325
|
- **Semantic color only.** Themed color is `accent-*` (the brand hue) and
|
|
287
326
|
`surface-*` (the neutral family). Never write a literal color into a view;
|
|
288
327
|
changing the palette is install-local territory.
|
|
@@ -1,81 +1,89 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: keystone_ui-info
|
|
3
|
-
description: Use to learn what Keystone UI offers — what the component system covers, its layout and
|
|
3
|
+
description: Use to learn what Keystone UI offers — what the component system covers, its layout, color and theme model, and the vocabulary the install and develop locals assume.
|
|
4
4
|
tools: Read
|
|
5
5
|
scope: UI — pages, forms, tables, navigation, dashboards
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
This local explains Keystone UI and
|
|
9
|
-
|
|
8
|
+
This local explains Keystone UI and sends you to the local that does the work.
|
|
9
|
+
It changes nothing and gives no steps.
|
|
10
10
|
|
|
11
11
|
## What Keystone UI is
|
|
12
12
|
|
|
13
|
-
Keystone UI is a Rails engine gem that supplies a host app's
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
and Tailwind. Every class the UI renders
|
|
18
|
-
constants, so the look is
|
|
13
|
+
Keystone UI is a Rails engine gem that supplies a host app's visual layer as a
|
|
14
|
+
library of view helpers built on ViewComponent. Screens are built from named
|
|
15
|
+
pieces — page shells, sections, panels, grids, form fields, data tables,
|
|
16
|
+
navigation bars, cards, stat tiles, charts, funnels, goal buckets, pipelines,
|
|
17
|
+
banners — instead of hand-written ERB and Tailwind. Every class the UI renders
|
|
18
|
+
lives inside the gem, in frozen constants, so the look is defined in one place.
|
|
19
19
|
|
|
20
|
-
Reach for it whenever you
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
webview.
|
|
20
|
+
Reach for it whenever you build or change a screen in an app that has it
|
|
21
|
+
installed. It exists to stop UI drift: two pages built from the same helpers
|
|
22
|
+
cannot disagree about spacing, color or dark-mode treatment, and a change to a
|
|
23
|
+
component updates every page that uses it. It is mobile-first — components ship
|
|
24
|
+
separate mobile and desktop treatments (a bottom tab bar and mobile header on
|
|
25
|
+
small screens, a full navigation bar from the `lg:` breakpoint up), because
|
|
26
|
+
these apps are often viewed in a native webview.
|
|
28
27
|
|
|
29
28
|
## Interface
|
|
30
29
|
|
|
31
30
|
This local declares no commands. The two working surfaces belong elsewhere:
|
|
32
31
|
|
|
33
|
-
- **Getting the gem into a host app** — adding it, wiring Tailwind and
|
|
34
|
-
|
|
32
|
+
- **Getting the gem into a host app** — adding it, wiring Tailwind, Stimulus and
|
|
33
|
+
the theme attributes on the layout, setting the palette → **`keystone_ui-install`**.
|
|
35
34
|
- **Building UI with it** — which helper renders what, what keywords it takes,
|
|
36
35
|
how helpers nest → **`keystone_ui-develop`**.
|
|
37
36
|
|
|
38
37
|
## How to use it
|
|
39
38
|
|
|
40
|
-
One decision: is
|
|
39
|
+
One decision: is Keystone UI already set up in the app?
|
|
41
40
|
|
|
42
|
-
- No, or
|
|
41
|
+
- No, or the setup is out of date → **`keystone_ui-install`**.
|
|
43
42
|
- Yes, and you have a screen to build or edit → **`keystone_ui-develop`**. Do not
|
|
44
43
|
hand-write ERB or Tailwind for UI it covers.
|
|
45
44
|
|
|
46
|
-
Questions about which piece fits a scenario
|
|
47
|
-
|
|
45
|
+
Questions about which piece fits a scenario also go to the develop local, which
|
|
46
|
+
holds the catalog.
|
|
48
47
|
|
|
49
48
|
## Conventions
|
|
50
49
|
|
|
51
50
|
- **Helpers, not classes.** Every entry point is a view helper prefixed `ui_`,
|
|
52
51
|
called from ERB. Components live under the `Keystone::Ui` namespace, but a
|
|
53
|
-
host app
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
host app does not name a component class directly. The one exception is the
|
|
53
|
+
table column value object, which is passed as an argument and renders nothing.
|
|
54
|
+
- **Containers take blocks, leaves take keywords.** Helpers that wrap content
|
|
55
|
+
(page shells, sections, panels, grids, forms, tables) yield a block. Helpers
|
|
56
|
+
that render one thing (a button, a badge, a field, a stat, a bucket) are
|
|
57
|
+
configured entirely by keyword arguments. The navigation bar exposes named
|
|
58
|
+
slots instead of a single block.
|
|
59
|
+
- **Options are symbols, and each component accepts its own set.** Appearance is
|
|
60
60
|
chosen by name — `variant:`, `size:`, `type:`, `padding:`, `spacing:`,
|
|
61
|
-
`max_width:`, `radius:` — on a
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
wrong guess fails loudly at render time.
|
|
61
|
+
`max_width:`, `radius:` — on a size scale (`:sm` … `:xl`) or a short list of
|
|
62
|
+
named choices. A button's `variant:` and a badge's `variant:` accept
|
|
63
|
+
different symbols, and the develop local carries the real values. Most
|
|
64
|
+
components raise on a symbol they do not know, so a wrong guess fails at
|
|
65
|
+
render time.
|
|
67
66
|
- **Semantic color, not literal color.** The themed hue is `accent-*` and the
|
|
68
|
-
themed neutral family is `surface
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
`gray
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
themed neutral family is `surface-*`, both CSS custom properties whose
|
|
68
|
+
defaults (blue and zinc) come from the keystone_ui-styles gem. Retheming an
|
|
69
|
+
app changes those values, not the components. Some components still use
|
|
70
|
+
Tailwind's stock `gray-*` and `zinc-*` neutrals, or fixed status colors such
|
|
71
|
+
as green and amber, directly. Changing the defaults belongs to the install
|
|
72
|
+
local.
|
|
73
|
+
- **Light, dark and system themes.** Every component has dark-mode styling. The
|
|
74
|
+
theme is chosen in this order: the user's choice stored in a cookie, then a
|
|
75
|
+
mode another gem supplies, then light. System leaves the page to follow the
|
|
76
|
+
operating system.
|
|
74
77
|
- **Tailwind classes are static strings.** Class names are never interpolated,
|
|
75
|
-
so Tailwind's scanner can
|
|
76
|
-
|
|
78
|
+
so Tailwind's scanner can find them. Widths and heights that depend on data,
|
|
79
|
+
such as a progress bar or a bucket's fill, are set with an inline style
|
|
80
|
+
instead. The host needs tailwindcss-rails v4+, and the engine tells Tailwind
|
|
81
|
+
at boot where the component files are.
|
|
77
82
|
- **Interactivity is Stimulus.** Dropdowns, modals, dismissible alerts, file
|
|
78
|
-
uploads,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
uploads, the color picker, the multi-select, tab switchers, accordions, the
|
|
84
|
+
swipe deck, column pickers, stat card info panels, line charts, clipboard copy
|
|
85
|
+
and the theme toggle ship with the gem as Stimulus controllers registered once
|
|
86
|
+
at install. A host writes no JavaScript to use them. Components that post
|
|
87
|
+
somewhere, such as the column picker and the pipeline, post to endpoints the
|
|
88
|
+
host app owns.
|
|
89
|
+
- Ruby >= 3.2. ViewComponent >= 2.0 and < 5.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: keystone_ui-install
|
|
3
|
-
description: Use to hook Keystone UI into a project — adding the gem, running the install generator to wire Tailwind and
|
|
3
|
+
description: Use to hook Keystone UI into a project — adding the gem, running the install generator to wire Tailwind, the Stimulus controllers and the layout's theme attributes, and configuring the palette, the theme mode supplier and extra Tailwind imports and sources.
|
|
4
4
|
tools: Bash, Read, Edit
|
|
5
5
|
scope: UI — pages, forms, tables, navigation, dashboards
|
|
6
6
|
---
|
|
@@ -15,16 +15,21 @@ built on ViewComponent; hook it in before building any screen with those helpers
|
|
|
15
15
|
|
|
16
16
|
## Interface
|
|
17
17
|
|
|
18
|
-
- `bin/rails generate keystone:install` — sets up the host's Tailwind entry
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
- `bin/rails generate keystone:install` — sets up the host's Tailwind entry
|
|
19
|
+
point, registers the gem's Stimulus controllers, and adds the theme attributes
|
|
20
|
+
to the layout's `<html>` tag. Safe to re-run.
|
|
21
|
+
- `KeystoneUi.configure` — a block yielding the configuration: `accent` and
|
|
22
|
+
`surface` (palette names, `:blue` and `:zinc` unless set),
|
|
23
|
+
`theme_mode_supplier` (a callable that supplies a light, dark or system mode),
|
|
24
|
+
and the `tailwind_imports` and `tailwind_sources` lists (extra CSS files and
|
|
25
|
+
scan paths added to the Tailwind build).
|
|
22
26
|
|
|
23
27
|
## How to use it
|
|
24
28
|
|
|
25
29
|
1. Confirm the prerequisites: Ruby >= 3.2 and **tailwindcss-rails v4+** in the
|
|
26
|
-
host app. The gem brings ViewComponent with it.
|
|
27
|
-
|
|
30
|
+
host app. The gem brings ViewComponent and keystone_ui-styles with it.
|
|
31
|
+
Tailwind does not have to be initialized first, because the generator creates
|
|
32
|
+
the stylesheet if it is missing.
|
|
28
33
|
|
|
29
34
|
2. Add the gem to the Gemfile and install it:
|
|
30
35
|
|
|
@@ -45,31 +50,45 @@ built on ViewComponent; hook it in before building any screen with those helpers
|
|
|
45
50
|
bin/rails generate keystone:install
|
|
46
51
|
```
|
|
47
52
|
|
|
48
|
-
It touches
|
|
53
|
+
It touches three host files:
|
|
49
54
|
|
|
50
|
-
- `app/assets/tailwind/application.css` —
|
|
51
|
-
`@import "tailwindcss";` and `@import "./keystone_source.css"
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
- `app/assets/tailwind/application.css` — if absent, it is created holding
|
|
56
|
+
`@import "tailwindcss";` and `@import "./keystone_source.css";`. If present,
|
|
57
|
+
the Keystone import is added on the line after `@import "tailwindcss";`, and
|
|
58
|
+
Keystone lines left by older installs are removed.
|
|
54
59
|
- `app/javascript/controllers/index.js` — appends
|
|
55
60
|
`import { registerControllers } from "keystone_ui/index"` and
|
|
56
61
|
`registerControllers(application)`.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
- `app/views/layouts/application.html.erb` — adds
|
|
63
|
+
`<%= keystone_theme_attributes %>` inside the `<html` tag, which writes the
|
|
64
|
+
light or dark mode onto the page.
|
|
65
|
+
|
|
66
|
+
Read its output for two warnings:
|
|
67
|
+
|
|
68
|
+
- `app/javascript/controllers/index.js not found` — ask the developer where
|
|
69
|
+
the Stimulus application is set up and add those two lines there. Without
|
|
70
|
+
them, dropdowns, modals, file uploads, the column picker, the theme toggle
|
|
71
|
+
and the other interactive components do nothing.
|
|
72
|
+
- `app/views/layouts/application.html.erb not found` — ask the developer which
|
|
73
|
+
layout the app renders and add `<%= keystone_theme_attributes %>` to its
|
|
74
|
+
`<html>` tag. Without it, a saved light or dark choice is not applied when
|
|
75
|
+
the page loads.
|
|
76
|
+
|
|
77
|
+
The CSS step only adds the Keystone import if `application.css` contains the
|
|
78
|
+
exact line `@import "tailwindcss";` followed by a line break. If the output
|
|
79
|
+
says nothing about the import and the line is not in the file, add
|
|
80
|
+
`@import "./keystone_source.css";` directly under the Tailwind import by hand.
|
|
62
81
|
|
|
63
82
|
4. Restart the app (or rebuild assets). On boot the engine writes
|
|
64
|
-
`app/assets/tailwind/keystone_source.css`, which
|
|
65
|
-
component
|
|
66
|
-
|
|
67
|
-
`@import "./keystone_source.css"
|
|
68
|
-
missing.
|
|
83
|
+
`app/assets/tailwind/keystone_source.css`, which imports the gem's theme and
|
|
84
|
+
component CSS and points Tailwind at the component files. It is written only
|
|
85
|
+
when `application.css` exists **and** contains
|
|
86
|
+
`@import "./keystone_source.css";`, so if the file never appears, that import
|
|
87
|
+
is missing.
|
|
69
88
|
|
|
70
89
|
5. Keep the generated file out of git. It holds absolute paths to the gem on the
|
|
71
|
-
machine that booted the app, and is rewritten on every boot
|
|
72
|
-
and `assets:precompile
|
|
90
|
+
machine that booted the app, and is rewritten on every boot, which includes
|
|
91
|
+
the dev server, CI and `assets:precompile`. Add to `.gitignore`:
|
|
73
92
|
|
|
74
93
|
```
|
|
75
94
|
app/assets/tailwind/keystone_source.css
|
|
@@ -77,10 +96,10 @@ built on ViewComponent; hook it in before building any screen with those helpers
|
|
|
77
96
|
|
|
78
97
|
Only the `@import` line in `application.css` belongs in the repo.
|
|
79
98
|
|
|
80
|
-
6. Settle the palette.
|
|
81
|
-
scale is zinc
|
|
82
|
-
|
|
83
|
-
|
|
99
|
+
6. Settle the palette. Without configuration the accent scale is blue and the
|
|
100
|
+
surface scale is zinc. To change them statically, add an `@theme` block to
|
|
101
|
+
`application.css` **after** the two imports and override only the shades the
|
|
102
|
+
app uses:
|
|
84
103
|
|
|
85
104
|
```css
|
|
86
105
|
@import "tailwindcss";
|
|
@@ -95,12 +114,14 @@ built on ViewComponent; hook it in before building any screen with those helpers
|
|
|
95
114
|
Both scales run 50 through 950. Every component picks the values up with no
|
|
96
115
|
component changes.
|
|
97
116
|
|
|
98
|
-
This is a decision
|
|
99
|
-
or per-user colors generated at runtime by a companion theming gem. Ask
|
|
100
|
-
|
|
117
|
+
This is a decision to put to the developer: fixed app-wide colors set in CSS,
|
|
118
|
+
or per-user colors generated at runtime by a companion theming gem. Ask which
|
|
119
|
+
the app wants before wiring either.
|
|
101
120
|
|
|
102
|
-
7.
|
|
103
|
-
|
|
121
|
+
7. Write `config/initializers/keystone_ui.rb` only if one of the settings below
|
|
122
|
+
is wanted. Ask the developer about each rather than adding any by default.
|
|
123
|
+
The engine reads the configuration after initializers have run, so this file
|
|
124
|
+
is where the block goes.
|
|
104
125
|
|
|
105
126
|
```ruby
|
|
106
127
|
require "keystone_ui"
|
|
@@ -108,30 +129,45 @@ built on ViewComponent; hook it in before building any screen with those helpers
|
|
|
108
129
|
KeystoneUi.configure do |config|
|
|
109
130
|
config.accent = :emerald
|
|
110
131
|
config.surface = :slate
|
|
132
|
+
config.theme_mode_supplier = ->(view) { view.current_user&.theme }
|
|
133
|
+
config.tailwind_imports << "/absolute/path/to/extra.css"
|
|
134
|
+
config.tailwind_sources << "/absolute/path/to/components/**/*.{erb,rb}"
|
|
111
135
|
end
|
|
112
136
|
```
|
|
113
137
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
138
|
+
- `accent` and `surface` — set these only when a companion gem or engine
|
|
139
|
+
reads the palette choice. Keystone UI stores the names and changes no color
|
|
140
|
+
from them, because colors come from the CSS custom properties in step 6.
|
|
141
|
+
- `theme_mode_supplier` — a callable that receives the view and returns
|
|
142
|
+
`"light"`, `"dark"` or `"system"`. It supplies the mode when the user has
|
|
143
|
+
not picked one with the theme toggle, since the `keystone_theme` cookie that
|
|
144
|
+
the toggle writes takes precedence. Any other return value, or no supplier,
|
|
145
|
+
falls back to light.
|
|
146
|
+
- `tailwind_imports` and `tailwind_sources` — lists to append to, never
|
|
147
|
+
assign. Each import becomes an `@import` line and each source becomes an
|
|
148
|
+
`@source` line in `keystone_source.css` on the next boot. They are for
|
|
149
|
+
another gem or engine whose CSS or templates must be in the same Tailwind
|
|
150
|
+
build, and that gem normally appends its own entries.
|
|
119
151
|
|
|
120
152
|
## Conventions
|
|
121
153
|
|
|
122
|
-
- **Verify the install before building anything on it.**
|
|
123
|
-
both imports
|
|
124
|
-
the Stimulus setup calls `registerControllers(application)
|
|
125
|
-
|
|
126
|
-
|
|
154
|
+
- **Verify the install before building anything on it.** Check four things:
|
|
155
|
+
`application.css` holds both imports, `app/assets/tailwind/keystone_source.css`
|
|
156
|
+
exists after a boot, the Stimulus setup calls `registerControllers(application)`,
|
|
157
|
+
and the layout's `<html>` tag contains `<%= keystone_theme_attributes %>`. Then
|
|
158
|
+
load one page that renders a `ui_*` helper and confirm it is styled and that an
|
|
159
|
+
interactive component (a dropdown, a dismissible alert) responds.
|
|
127
160
|
- **Importmap is the supported JS path.** For apps configured with importmap the
|
|
128
|
-
gem pins its own controllers
|
|
161
|
+
gem pins its own controllers, and the charting library they need, on boot, so
|
|
129
162
|
the host pins nothing. If the app bundles JavaScript instead (esbuild, bun,
|
|
130
|
-
webpack), the appended `keystone_ui/index` import has
|
|
131
|
-
|
|
132
|
-
- **Re-run the generator after upgrading the gem.** It
|
|
133
|
-
|
|
163
|
+
webpack), the appended `keystone_ui/index` import has nothing pinned behind
|
|
164
|
+
it. Tell the developer rather than guessing at a bundler configuration.
|
|
165
|
+
- **Re-run the generator after upgrading the gem.** It removes superseded install
|
|
166
|
+
lines and reports that each file is already up to date when there is nothing
|
|
167
|
+
to do.
|
|
134
168
|
- **New components need no re-run.** Tailwind rescans the gem on each build, so
|
|
135
169
|
components added by a later version are styled on the next boot.
|
|
136
|
-
-
|
|
137
|
-
|
|
170
|
+
- On boot the engine deletes `app/assets/builds/tailwind/keystone_ui_engine.css`
|
|
171
|
+
if an older install left it there. Do not recreate it.
|
|
172
|
+
- Building UI with the helpers, including which helper to use and what keywords
|
|
173
|
+
it takes, is out of scope here and belongs to `keystone_ui-develop`.
|
data/the_local/interface.yml
CHANGED
|
@@ -51,6 +51,8 @@ develop:
|
|
|
51
51
|
- ui_form
|
|
52
52
|
- ui_file_upload
|
|
53
53
|
- ui_funnel
|
|
54
|
+
- ui_bucket
|
|
55
|
+
- ui_bucket_series
|
|
54
56
|
- ui_pipeline
|
|
55
57
|
- ui_code
|
|
56
58
|
- ui_disclosure
|
|
@@ -85,6 +87,8 @@ sources:
|
|
|
85
87
|
- app/components/keystone/ui/form_field_component.rb
|
|
86
88
|
- app/components/keystone/ui/form_page_component.rb
|
|
87
89
|
- app/components/keystone/ui/funnel_component.rb
|
|
90
|
+
- app/components/keystone/ui/bucket_component.rb
|
|
91
|
+
- app/components/keystone/ui/bucket_series_component.rb
|
|
88
92
|
- app/components/keystone/ui/grid_component.rb
|
|
89
93
|
- app/components/keystone/ui/hero_component.rb
|
|
90
94
|
- app/components/keystone/ui/input_component.rb
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: keystone_ui
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.13.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tyler Schneider
|
|
@@ -81,6 +81,10 @@ files:
|
|
|
81
81
|
- app/components/keystone/ui/bottom_nav_component.rb
|
|
82
82
|
- app/components/keystone/ui/bottom_nav_item_component.html.erb
|
|
83
83
|
- app/components/keystone/ui/bottom_nav_item_component.rb
|
|
84
|
+
- app/components/keystone/ui/bucket_component.html.erb
|
|
85
|
+
- app/components/keystone/ui/bucket_component.rb
|
|
86
|
+
- app/components/keystone/ui/bucket_series_component.html.erb
|
|
87
|
+
- app/components/keystone/ui/bucket_series_component.rb
|
|
84
88
|
- app/components/keystone/ui/button_component.html.erb
|
|
85
89
|
- app/components/keystone/ui/button_component.rb
|
|
86
90
|
- app/components/keystone/ui/card_component.html.erb
|