keystone_ui 0.17.0 → 0.19.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e0dc7211df561f52f213d69e2733292b883fd7bb31cb6bc203ba581e90c5022
4
- data.tar.gz: e95919dc9707abaae6d12837d193ace1bed9a16b8f5d00c0758f0f2a6c82cf23
3
+ metadata.gz: 19d3d7682e2b19ea8bdeadd90f50b0668db2587e21c891c220fdbb5af56a3780
4
+ data.tar.gz: dafd5905c87e5723538ffd9f581b3e6edafe6d8a248a5364580191350778fe82
5
5
  SHA512:
6
- metadata.gz: 6f0ccf38449bfa1c327f0abfc4e4bf818f80859a90ff19a6a5f680b43ba6790de67502d218ba7cfba32a019ff1ee96c4ab0653ffaca5bfaa97b6beb226110d18
7
- data.tar.gz: 6019b1917c3736e6323cf5c924be7e0a7e5a2d3686aa3d371bf520f548046e7a7bdf42dccc6afcc5664693d558c77482cf4f7061c05fc383e81e6effd48ecb7e
6
+ metadata.gz: 0ee13705c4e25ef9c471d11005dfc081069acfb204482055198e35e1ddf131e836755769e42b26a3ec88e7f452e8ed47068b660430ad2d812eaaf72aeb734fb9
7
+ data.tar.gz: d48180384c5199e133b23792a0b8fdd569347f9800abe0edf49ecf18dcccad7aee3b3419133e6fb4b9ced574280e0eb7659bdf60fd920b9596008cbb0870de6b
@@ -0,0 +1,19 @@
1
+ <%= render Keystone::Ui::DisclosureComponent.new do |disclosure| %>
2
+ <% disclosure.with_summary_content(summary) %>
3
+ <div class="<%= GROUPS_CLASSES %>">
4
+ <% groups.each do |group| %>
5
+ <div>
6
+ <% if group.title %>
7
+ <p class="<%= TITLE_CLASSES %>"><%= group.title %></p>
8
+ <% end %>
9
+ <div class="<%= LINES_CLASSES %>">
10
+ <% group.lines.each do |line| %>
11
+ <span class="<%= LINE_CLASSES %>"><%= line.label %></span>
12
+ <span class="<%= LINE_CLASSES %>"><%= line.working %></span>
13
+ <span class="<%= RESULT_CLASSES %>"><%= line.result %></span>
14
+ <% end %>
15
+ </div>
16
+ </div>
17
+ <% end %>
18
+ </div>
19
+ <% end %>
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Keystone
4
+ module Ui
5
+ class CalculationComponent < ViewComponent::Base
6
+ Group = Struct.new(:title, :lines, keyword_init: true)
7
+ Line = Struct.new(:label, :working, :result, keyword_init: true)
8
+
9
+ TITLE_CLASSES = "ks-label text-sm"
10
+ LINE_CLASSES = "ks-hint text-sm"
11
+ LINES_CLASSES = "grid grid-cols-[auto_1fr_auto] ks-grid-gap-sm"
12
+ RESULT_CLASSES = "ks-hint text-sm text-right"
13
+ GROUPS_CLASSES = "grid ks-grid-gap-md"
14
+
15
+ attr_reader :groups, :summary
16
+
17
+ def initialize(groups:, summary: "How this is worked out")
18
+ @groups = groups.map { |group| Group.new(title: group[:title], lines: group.fetch(:lines).map { |line| Line.new(**line) }) }
19
+ @summary = summary
20
+ end
21
+ end
22
+ end
23
+ end
@@ -46,7 +46,7 @@ module KeystoneUiHelper
46
46
  end
47
47
 
48
48
  def keystone_theme_attributes
49
- keystone_theme_choice.html_attributes_markup.html_safe
49
+ keystone_theme_choice.html_attributes.merge(keystone_look_choice.html_attributes).map { |name, value| %(#{name}="#{value}") }.join(" ").html_safe
50
50
  end
51
51
 
52
52
  def ui_copy_button(**args)
@@ -213,9 +213,18 @@ module KeystoneUiHelper
213
213
  render Keystone::Ui::DisclosureComponent.new(**args), &block
214
214
  end
215
215
 
216
+ def ui_calculation(**args)
217
+ render Keystone::Ui::CalculationComponent.new(**args)
218
+ end
219
+
216
220
  private
217
221
 
218
222
  def keystone_theme_choice
219
223
  KeystoneUi::ThemeChoice.new(cookies[KeystoneUi::ThemeChoice::COOKIE], supplied: KeystoneUi.configuration.supplied_theme_mode(self))
220
224
  end
225
+
226
+ def keystone_look_choice
227
+ config = KeystoneUi.configuration
228
+ KeystoneUi::LookChoice.new(looks: config.looks.keys, default: config.default_look, supplied: config.supplied_look(self))
229
+ end
221
230
  end
@@ -2,14 +2,28 @@
2
2
 
3
3
  module KeystoneUi
4
4
  class Configuration
5
- attr_accessor :accent, :surface, :theme_mode_supplier
6
- attr_reader :tailwind_imports, :tailwind_sources
5
+ attr_accessor :accent, :surface, :theme_mode_supplier, :look_supplier
6
+ attr_reader :tailwind_imports, :tailwind_sources, :looks, :default_look
7
7
 
8
8
  def initialize
9
9
  @accent = :blue
10
10
  @surface = :zinc
11
11
  @tailwind_imports = []
12
12
  @tailwind_sources = []
13
+ @looks = {}
14
+ end
15
+
16
+ def default_look=(name)
17
+ @default_look = name&.to_s
18
+ end
19
+
20
+ def register_look(name, path)
21
+ @looks[name.to_s] = path.to_s
22
+ @tailwind_imports << path.to_s
23
+ end
24
+
25
+ def supplied_look(view)
26
+ look_supplier&.call(view)&.to_s
13
27
  end
14
28
 
15
29
  def supplied_theme_mode(view)
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KeystoneUi
4
+ class LookChoice
5
+ def initialize(looks:, default:, supplied: nil)
6
+ @looks = looks
7
+ @default = default
8
+ @supplied = supplied
9
+ end
10
+
11
+ def name
12
+ [ @supplied, @default ].find { |candidate| @looks.include?(candidate) }
13
+ end
14
+
15
+ def html_attributes
16
+ name ? { "data-look" => name } : {}
17
+ end
18
+ end
19
+ end
@@ -56,7 +56,8 @@ module Keystone
56
56
  Keystone::Ui::PipelineComponent,
57
57
  Keystone::Ui::CodeComponent,
58
58
  Keystone::Ui::DisclosureComponent,
59
- Keystone::Ui::ThemeToggleComponent
59
+ Keystone::Ui::ThemeToggleComponent,
60
+ Keystone::Ui::CalculationComponent
60
61
  ].freeze
61
62
 
62
63
  # Constants that hold non-CSS values (e.g. HTML input type maps)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KeystoneUi
4
- VERSION = "0.17.0"
4
+ VERSION = "0.19.0"
5
5
  end
data/lib/keystone_ui.rb CHANGED
@@ -4,6 +4,7 @@ require "view_component"
4
4
  require "keystone_ui/styles"
5
5
  require "keystone_ui/configuration"
6
6
  require "keystone_ui/theme_choice"
7
+ require "keystone_ui/look_choice"
7
8
  require "keystone_ui/engine"
8
9
  require "keystone_ui/version"
9
10
 
@@ -198,6 +198,15 @@ outer element. See Conventions before using it.
198
198
  - `ui_disclosure(open: false)` — takes a block yielding the component. Fill its
199
199
  `summary` slot with the clickable header; the rest of the block is the body.
200
200
  Native `<details>` — no JavaScript.
201
+ - `ui_calculation(groups:, summary: "How this is worked out")` — no block. Shows
202
+ how a figure was reached, closed by default under a row reading `summary:`.
203
+ `groups:` is `[{ title:, lines: [{ label:, working:, result: }, ...] }, ...]`;
204
+ `title:` is optional, `lines:` is required and a group without it raises
205
+ `KeyError`, and a line with any key other than those three raises
206
+ `ArgumentError`. Each line renders as three columns — label, working, result
207
+ — with the result right-aligned. Every value is printed as given, so format
208
+ numbers and currency before passing them. Place it directly under the figure
209
+ it explains, such as a `ui_stat_card`.
201
210
  - `ui_accordion(items: [])` — a stack of independently expandable rows. `items:`
202
211
  is `[{ question:, answer: }, ...]`.
203
212
  - `ui_tab_switcher(tabs:)` — takes a block. `tabs:` is an array of label strings;
@@ -298,6 +307,9 @@ outer element. See Conventions before using it.
298
307
  `ui_data_table` over a hand-built `<table>`, `ui_stat_card` over a panel with
299
308
  text in it. To make a stat card clickable, pass `href:` — never wrap it in
300
309
  `ui_card_link`, because a tap on the info button would then follow the link.
310
+ To show the arithmetic behind a figure, put `ui_calculation` under it rather
311
+ than a hand-built list. Which lines and groups to show is the app's own
312
+ calculation, so ask the developer which steps a reader needs to see.
301
313
 
302
314
  6. For a table, decide how columns are declared. Use `{ key: "Label" }` hashes
303
315
  when every column is plain. Switch the whole set to `Keystone::Ui::Column`
@@ -14,8 +14,9 @@ Keystone UI is a Rails engine gem that supplies a host app's visual layer as a
14
14
  library of view helpers built on ViewComponent. Screens are built from named
15
15
  pieces — page shells, sections, panels, grids, form fields, data tables,
16
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.
17
+ banners, the calculation behind a figure — instead of hand-written ERB and
18
+ Tailwind. Every class the UI renders lives inside the gem, in frozen constants,
19
+ so the look is defined in one place.
19
20
 
20
21
  Reach for it whenever you build or change a screen in an app that has it
21
22
  installed. It exists to stop UI drift: two pages built from the same helpers
@@ -49,9 +50,10 @@ holds the catalog.
49
50
 
50
51
  - **Helpers, not classes.** Every piece of UI is a view helper prefixed `ui_`,
51
52
  called from ERB. The one layout helper that is not prefixed `ui_` writes the
52
- theme onto the page's `html` tag, and it belongs to the install local. Components live under the `Keystone::Ui` namespace, but a
53
- host app does not name a component class directly. The one exception is the
54
- table column value object, which is passed as an argument and renders nothing.
53
+ theme onto the page's `html` tag, and it belongs to the install local.
54
+ Components live under the `Keystone::Ui` namespace, but a host app does not
55
+ name a component class directly. The one exception is the table column value
56
+ object, which is passed as an argument and renders nothing.
55
57
  - **Containers take blocks, leaves take keywords.** Helpers that wrap content
56
58
  (page shells, sections, panels, grids, forms, tables) yield a block. Helpers
57
59
  that render one thing (a button, a badge, a field, a stat, a bucket) are
@@ -67,6 +69,11 @@ holds the catalog.
67
69
  - **Figures are numbers.** Components that measure or compare values, such as
68
70
  progress bars, funnels and buckets, do arithmetic on them. Pass a number, not
69
71
  a formatted string such as `"9,000"`. A bucket raises on one.
72
+ - **A calculation is text.** The calculation behind a figure is the opposite
73
+ case. It is a list of groups, each with an optional title and its lines, and
74
+ each line is a label, the working and the result. They are passed as
75
+ already-formatted text and shown as written. The component does no
76
+ arithmetic, and it is closed by default under a quiet summary row.
70
77
  - **Semantic color, not literal color.** The themed hue is `accent-*` and the
71
78
  themed neutral family is `surface-*`, both CSS custom properties whose
72
79
  defaults (blue and zinc) come from the keystone_ui-styles gem. Retheming an
@@ -82,10 +89,18 @@ holds the catalog.
82
89
  theme toggle, checkbox row, radio card, option card, file upload and colour
83
90
  picker read these variables. So do the data display components: stat card,
84
91
  chart card, card link, CTA banner, feature grid, hero, data table, code,
85
- accordion, disclosure, tab switcher, progress, funnel, bucket, pipeline and
86
- swipe deck. So do the navigation components: navbar, nav item, nav dropdown,
87
- bottom nav, mobile header and settings link. A host imports its look after `keystone_source.css`, and a gem ships one through
92
+ accordion, disclosure, calculation, tab switcher, progress, funnel, bucket,
93
+ pipeline and swipe deck. So do the navigation components: navbar, nav item,
94
+ nav dropdown, bottom nav, mobile header and settings link. A host imports its
95
+ look after `keystone_source.css`, and a gem ships one through
88
96
  `tailwind_imports`, both set up through the install local.
97
+ - **Registered looks.** Looks can also be registered by name, and each page
98
+ gets one: the name another gem supplies for the request, then the configured
99
+ default, each used only when registered. The layout's `<html>` tag carries
100
+ `data-look="<name>"`, and a registered look file scopes its variables to
101
+ `:root[data-look="<name>"]`. A host with no registered looks gets no
102
+ `data-look`. Registering and choosing looks is set up through the install
103
+ local.
89
104
  - **Light, dark, system and custom themes.** Every component has dark-mode
90
105
  styling. The theme is chosen in this order: the user's choice stored in a
91
106
  cookie, then a mode another gem supplies, then light. System leaves the page
@@ -93,9 +108,8 @@ holds the catalog.
93
108
  gem defines. The theme toggle does not offer it, so a page reaches it when
94
109
  another gem supplies it. On a Turbo visit, the page being shown sets the
95
110
  theme, so a change to the mode on the server shows up without a full reload.
96
- Marking the layout with the theme is set up through
97
- the install local, and placing the toggle on a screen goes through the develop
98
- local.
111
+ Marking the layout with the theme is set up through the install local, and
112
+ placing the toggle on a screen goes through the develop local.
99
113
  - **Tailwind classes are static strings.** Class names are never interpolated,
100
114
  so Tailwind's scanner can find them. Widths and heights that depend on data,
101
115
  such as a progress bar or a bucket's fill, are set with an inline style
@@ -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, the Stimulus controllers and the layout's theme attributes, and configuring the palette, the theme mode supplier and extra Tailwind imports and sources.
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, registered looks and extra Tailwind imports and sources.
4
4
  tools: Bash, Read, Edit
5
5
  scope: UI — pages, forms, tables, navigation, dashboards
6
6
  ---
@@ -22,13 +22,18 @@ built on ViewComponent; hook it in before building any screen with those helpers
22
22
  `surface` (palette names, `:blue` and `:zinc` unless set),
23
23
  `theme_mode_supplier` (a callable that supplies a light, dark, system or
24
24
  custom mode),
25
+ `register_look` (a look's CSS file by name), `default_look` and
26
+ `look_supplier` (which registered look a page gets),
25
27
  and the `tailwind_imports` and `tailwind_sources` lists (extra CSS files and
26
28
  scan paths added to the Tailwind build).
27
29
  - `keystone_theme_attributes` — a layout helper placed inside the `<html>` tag.
28
30
  It writes `data-theme="light"`, `data-theme="dark"` or `data-theme="custom"`
29
31
  for the page's mode, and writes nothing for system so the page follows the
30
32
  operating system. The mode is the `keystone_theme` cookie's choice, then the
31
- configured `theme_mode_supplier`, then light.
33
+ configured `theme_mode_supplier`, then light. It also writes
34
+ `data-look="<name>"` for the page's look: the `look_supplier`'s name, then
35
+ `default_look`, each used only when registered. With no look registered it
36
+ writes no `data-look`.
32
37
 
33
38
  ## How to use it
34
39
 
@@ -146,11 +151,15 @@ built on ViewComponent; hook it in before building any screen with those helpers
146
151
  checkbox row, radio card, option card, file upload and colour picker read
147
152
  these variables. So do the data display components: stat card, chart card,
148
153
  card link, CTA banner, feature grid, hero, data table, code, accordion,
149
- disclosure, tab switcher, progress, funnel, bucket, pipeline and swipe deck.
154
+ disclosure, calculation, tab switcher, progress, funnel, bucket, pipeline and
155
+ swipe deck.
150
156
  So do the navigation components: navbar, nav item, nav dropdown, bottom nav,
151
157
  mobile header and settings link. The keystone_ui-styles README lists every
152
158
  variable and its default.
153
159
 
160
+ To offer several looks and choose one per page, register them by name in
161
+ step 7 rather than importing them here.
162
+
154
163
  7. Write `config/initializers/keystone_ui.rb` only if one of the settings below
155
164
  is wanted. Ask the developer about each rather than adding any by default.
156
165
  The engine reads the configuration after initializers have run, so this file
@@ -163,6 +172,9 @@ built on ViewComponent; hook it in before building any screen with those helpers
163
172
  config.accent = :emerald
164
173
  config.surface = :slate
165
174
  config.theme_mode_supplier = ->(view) { view.current_user&.theme }
175
+ config.register_look :compact, "/absolute/path/to/compact.css"
176
+ config.default_look = :compact
177
+ config.look_supplier = ->(view) { view.current_user&.look }
166
178
  config.tailwind_imports << "/absolute/path/to/extra.css"
167
179
  config.tailwind_sources << "/absolute/path/to/components/**/*.{erb,rb}"
168
180
  end
@@ -180,6 +192,14 @@ built on ViewComponent; hook it in before building any screen with those helpers
180
192
  `data-theme="custom"`. The theme toggle does not offer custom, so only a
181
193
  supplier sets it. Ask the developer which gem or code supplies custom and
182
194
  its colors before returning it.
195
+ - `register_look :name, path` — registers a look file by name and appends it
196
+ to `tailwind_imports`, so `keystone_source.css` imports it. A look offered
197
+ this way scopes its variables to `:root[data-look="<name>"]` instead of
198
+ `:root`, so several can be imported at once.
199
+ - `default_look = :name` — the look a page gets when nothing else chooses one.
200
+ - `look_supplier` — a callable that receives the view and returns a look
201
+ name for the request. A name that is not registered, or `nil`, leaves the
202
+ page on the default look.
183
203
  - `tailwind_imports` and `tailwind_sources` — lists to append to, never
184
204
  assign. Each import becomes an `@import` line and each source becomes an
185
205
  `@source` line in `keystone_source.css` on the next boot. They are for
@@ -57,6 +57,7 @@ develop:
57
57
  - ui_pipeline
58
58
  - ui_code
59
59
  - ui_disclosure
60
+ - ui_calculation
60
61
  - ui_theme_toggle
61
62
  - Keystone::Ui::Column
62
63
 
@@ -64,6 +65,7 @@ sources:
64
65
  - lib/generators/keystone/install_generator.rb
65
66
  - lib/keystone_ui/configuration.rb
66
67
  - lib/keystone_ui/theme_choice.rb
68
+ - lib/keystone_ui/look_choice.rb
67
69
  - app/helpers/keystone_ui_helper.rb
68
70
  - app/components/keystone/ui/accordion_component.rb
69
71
  - app/components/keystone/ui/alert_component.rb
@@ -84,6 +86,7 @@ sources:
84
86
  - app/components/keystone/ui/cta_banner_component.rb
85
87
  - app/components/keystone/ui/data_table_component.rb
86
88
  - app/components/keystone/ui/disclosure_component.rb
89
+ - app/components/keystone/ui/calculation_component.rb
87
90
  - app/components/keystone/ui/feature_grid_component.rb
88
91
  - app/components/keystone/ui/file_upload_component.rb
89
92
  - app/components/keystone/ui/form_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.17.0
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tyler Schneider
@@ -87,6 +87,8 @@ files:
87
87
  - app/components/keystone/ui/bucket_series_component.rb
88
88
  - app/components/keystone/ui/button_component.html.erb
89
89
  - app/components/keystone/ui/button_component.rb
90
+ - app/components/keystone/ui/calculation_component.html.erb
91
+ - app/components/keystone/ui/calculation_component.rb
90
92
  - app/components/keystone/ui/card_component.html.erb
91
93
  - app/components/keystone/ui/card_component.rb
92
94
  - app/components/keystone/ui/card_link_component.html.erb
@@ -184,6 +186,7 @@ files:
184
186
  - lib/keystone_ui/configuration.rb
185
187
  - lib/keystone_ui/engine.rb
186
188
  - lib/keystone_ui/leftover_stylesheet.rb
189
+ - lib/keystone_ui/look_choice.rb
187
190
  - lib/keystone_ui/safelist.rb
188
191
  - lib/keystone_ui/source_css.rb
189
192
  - lib/keystone_ui/theme_choice.rb