keystone_ui-preferences 0.9.0 → 0.10.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 +3 -3
- data/app/javascript/keystone_ui/preferences/appearance_editor_controller.js +21 -8
- data/app/models/keystone_ui/preferences/sent_theme.rb +5 -5
- data/app/views/keystone_ui/preferences/settings/_editor_layout.html.erb +15 -9
- data/app/views/keystone_ui/preferences/settings/_theme_settings.html.erb +12 -3
- data/lib/keystone_ui/preferences/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f764e1eedad8a815d7d8a0a92ff8f889d202a851f2c4bc063d7e1389867fb0f1
|
|
4
|
+
data.tar.gz: 4fd53a54a9c8b1e5609314e7bedfd3d6b6ed9ef0cc9a723e34a3bc6dbe72c0f4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9370930c96ddf66319f31e53f9ea08697367c72e6bce7782e216f83b7c3622857dad697d8241c76ca95e924069898300eb41f9d69dce1d507c818f690e59d8a5
|
|
7
|
+
data.tar.gz: aa97e42143a98e7d5f14858c0acd93eef460036dde1585092fca0b7da982a7957b69969fd33fc6638ee93d61ffa4dc8c4aa77b4601fda29865201bb19d922f04
|
data/README.md
CHANGED
|
@@ -198,11 +198,11 @@ Shape and type settings are kept from the `:root` block only, as `{ "radius": {
|
|
|
198
198
|
|
|
199
199
|
Each refusal below saves nothing and says why. A paste over 20,000 characters is refused before any of it is read. A paste containing `url(` or `@import` anywhere is refused. A paste with no shadcn colour role in a `:root` or `.dark` block is refused. A colour that fails the check is refused with a message naming the role and whether it is the light or dark value. A shape or type value that fails its check is refused with a message naming the setting, and so is a font list left with no font the app offers. A paste is refused with the same message as other colours while the account sets the colours for everyone.
|
|
200
200
|
|
|
201
|
-
After Look and Mode, the section lists every theme setting as its own field, named by its shadcn variable
|
|
201
|
+
After Look and Mode, the section lists every theme setting as its own field, named by its shadcn variable. The fields are grouped as tweakcn's editor groups them, and each group is a keystone_ui disclosure that is closed when the section opens, so Save sits a short way below Mode. A group holding a setting that was refused on Save opens, so the refusal shows under that setting. Colors holds the colour roles in sets, each with its foreground beside it: Page (`background` and `foreground`), Primary, Secondary, Accent, Muted, Card, Popover, Destructive, Border, input and ring, Chart (`chart-1` to `chart-5`) and the eight Sidebar roles. Typography holds the sans, serif, mono and heading fonts, the four font weights and the letter spacing. Other holds the radius, the spacing, the border width and the six shadow settings. Each colour role shows its name once, such as `--primary`, with a light and a dark keystone_ui colour picker side by side under it, labelled Light and Dark, and the name labels the pair for a screen reader. The shadow colour has one picker, so a colour can be typed as hex, `rgb()`, `hsl()` or `oklch()`. Every other setting is a text field. Each font field suggests the fonts in `theme_fonts` and then the system fonts the gem offers, and still takes a typed list of several fonts. The fields show the person's saved theme, so after a paste they hold the values that were read. Changing a field and pressing Save saves the fields as the person's theme, so a theme edited by hand stays the person's own. A field left blank leaves that setting out of the theme. Each value is checked and refused exactly as a pasted one is, with spaces around it removed first. The fields are saved only when they differ from the saved theme, so picking a template or changing the Accent, Surface or Text picker saves as before, and a paste sent in the same save wins over the fields. The grouped fields and the font suggestions need no script, but the colour pickers need keystone_ui's colour picker script to take a typed colour.
|
|
202
202
|
|
|
203
203
|
Each change to a theme field is drawn on the whole page before saving, through the appearance editor script the install generator copies. The script sets the setting's `--ks-*` variables on the page's root element, taking the list of which setting sets which variables from the same theme roles list the palette style tag writes from, so the page looks as it will after Save. A colour also sets the hover colour the style tag derives from `primary`, `secondary` and `destructive`, and a dark value sets only the `-dark` variables, so it shows only where the page is drawn dark. Colours, corner radius, spacing, border width, letter spacing, fonts, font weights and the shadow are drawn this way, and the shadow is drawn only once all six shadow settings hold a value, as the style tag writes it. A value the browser cannot draw leaves the page as it was, and it is still sent and checked on Save. Leaving the section without saving removes every variable the script set, so the next page, a Turbo visit included, is drawn in the saved theme. The script saves nothing and sends no request, so Save works as before, and without the script the fields still save.
|
|
204
204
|
|
|
205
|
-
Beside the fields the section shows a preview of keystone_ui's own components: a card, a primary, a secondary and a danger button, a form with a text field and a select, a table, a line chart and a sidebar navigation list. The preview is drawn twice, once on a side marked light and once on a side marked dark, from sample data the gem holds. Each unsaved change the appearance editor script draws on the page also shows in the preview, so a dark value changes only the dark side, including while the page itself is drawn dark. A chart colour redraws the preview's line chart and a sidebar colour changes its navigation list. The preview names no font of its own and loads nothing from another site, so it draws in the fonts the theme sets.
|
|
205
|
+
Beside the fields the section shows a preview of keystone_ui's own components: a card, a primary, a secondary and a danger button, a form with a text field and a select, a table, a line chart and a sidebar navigation list. The preview is drawn twice, once on a side marked light and once on a side marked dark, from sample data the gem holds. Each unsaved change the appearance editor script draws on the page also shows in the preview, so a dark value changes only the dark side, including while the page itself is drawn dark. A chart colour redraws the preview's line chart and a sidebar colour changes its navigation list. The preview names no font of its own and loads nothing from another site, so it draws in the fonts the theme sets. The layout follows the width the section is given, not the window's, so a section in half of a two-column settings page is laid out by that half. Where the section is 56rem wide or wider the fields and the preview sit side by side. Where it is at least 32rem wide but narrower than 56rem, the preview sits under the fields and Save. Where it is narrower than 32rem, such as on a phone, the section shows an Edit tab and a Preview tab from keystone_ui's tab switcher, and picking a tab shows its panel through the appearance editor script. The widths come from a CSS container query, which needs Safari 16, Chrome 105 or Firefox 110 or later. The preview needs keystone_ui-styles 0.23.0 or later, whose dark styles leave a side marked light drawn light inside a dark page, and keystone_ui 0.47.0 or later, whose line chart takes light or dark from the side it is drawn on and redraws when the theme's variables change, and it needs keystone_ui's tab switcher and line chart scripts registered. The preview is shown only alongside the theme fields, so it is not shown while the account sets the colours for everyone.
|
|
206
206
|
|
|
207
207
|
The preset themes are option cards after the templates, each showing its light primary and secondary colours as swatches, and the person's saved preset is selected. Picking a preset card fills the theme fields with that preset's values, through the appearance editor script, which draws them on the whole page and the preview before saving, as it draws a field changed by hand. Once a preset is saved the fields show its values. Theme fields sent unchanged, holding the picked or the saved preset's values, save the picked card as before. Changing a field after picking a preset saves the fields as the person's own theme in place of the preset. Without the script, picking a card and pressing Save still saves the preset. A Reset button beside Save fills every theme field, the page and the preview from the picked preset card, or from the saved theme when no preset card is picked, and saves nothing. A setting the saved theme leaves out is emptied, and the page draws it from the saved theme again.
|
|
208
208
|
|
|
@@ -249,7 +249,7 @@ SettingsHub.section :account_appearance, area: :account, title: "Appearance",
|
|
|
249
249
|
runs: "KeystoneUi::Preferences::PickAccountAppearance"
|
|
250
250
|
```
|
|
251
251
|
|
|
252
|
-
The section looks and behaves as keystone_ui-colors' account picker does. It offers Default, the five named templates, the six preset themes and Custom with the three colour pickers and the paste box the person's section has, then the app's own look and every registered look, with the account's saved choices selected, or Default and the app's own look when it has none. Under them are two switches, one for whether members choose their own colours and one for whether members choose their own look. Both are on until an admin turns them off. The theme fields come last, grouped into Colors, Typography and Other as in the person's section. The section is the same theme editor as the person's. Each change to a field and each picked preset card is drawn on the whole page before saving. The preview
|
|
252
|
+
The section looks and behaves as keystone_ui-colors' account picker does. It offers Default, the five named templates, the six preset themes and Custom with the three colour pickers and the paste box the person's section has, then the app's own look and every registered look, with the account's saved choices selected, or Default and the app's own look when it has none. Under them are two switches, one for whether members choose their own colours and one for whether members choose their own look. Both are on until an admin turns them off. The theme fields come last, grouped into Colors, Typography and Other and folded closed as in the person's section. The section is the same theme editor as the person's. Each change to a field and each picked preset card is drawn on the whole page before saving. The preview is laid out by the section's width as in the person's section: beside the fields from 56rem, under them from 32rem, and under Edit and Preview tabs below that. A Reset button beside Save fills the fields from the picked preset card, or from the account's saved theme, and saves nothing. The Export text area and its Copy button carry the fields' current values. Leaving the section without saving draws the next page in the admin's own saved theme. While the app turns off account colours the section shows no preview, no tabs and no Reset button.
|
|
253
253
|
|
|
254
254
|
Saving keeps the account's colours under the `colours` key, as `{ "template_name": "forest" }`, `{ "preset_name": "harbor" }`, `{ "accent": "#e11d48", "surface": "#f5e6c8", "text": "#3b2f1e" }` or a pasted or hand-edited theme, and its look under the `look` key, with each switch in that row's `members_choose` column. Saving Default keeps `{ "template_name": "default" }`, and saving the app's own look keeps an empty look, so neither removes the row its switch is on. The colours, a pasted theme, the theme fields and the look pass the same checks as a person's, the theme fields are saved only when they differ from the account's saved theme or the picked or saved preset, and a template, preset, colour, theme value or look that fails one is refused with a message, and nothing is saved. A save with no account is refused too.
|
|
255
255
|
|
|
@@ -44,8 +44,7 @@ export default class extends Controller {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
disconnect() {
|
|
47
|
-
this.#
|
|
48
|
-
this.#setVariables.clear()
|
|
47
|
+
this.#removeSetVariables()
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
apply({ target: { name, value } }) {
|
|
@@ -86,6 +85,7 @@ export default class extends Controller {
|
|
|
86
85
|
this.setPickerValue(this.accentPickerTarget, accentHex)
|
|
87
86
|
this.setPickerValue(this.surfacePickerTarget, surfaceHex)
|
|
88
87
|
this.setPickerValue(this.textPickerTarget, textHex)
|
|
88
|
+
this.#restoreSavedTheme()
|
|
89
89
|
|
|
90
90
|
this.highlightTemplate(label.dataset.template)
|
|
91
91
|
}
|
|
@@ -103,12 +103,7 @@ export default class extends Controller {
|
|
|
103
103
|
|
|
104
104
|
reset() {
|
|
105
105
|
const preset = this.element.querySelector("input[name='template_name'][data-theme]:checked")
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
this.element.querySelectorAll("[name^='theme[']").forEach(({ name }) => {
|
|
109
|
-
const [, role, side] = name.match(THEME_FIELD)
|
|
110
|
-
this.setThemeField(name, theme[role]?.[side] ?? "")
|
|
111
|
-
})
|
|
106
|
+
this.#fillThemeFields(preset ? JSON.parse(preset.dataset.theme) : this.#savedTheme())
|
|
112
107
|
}
|
|
113
108
|
|
|
114
109
|
setThemeField(name, value) {
|
|
@@ -130,6 +125,7 @@ export default class extends Controller {
|
|
|
130
125
|
|
|
131
126
|
customColorChanged() {
|
|
132
127
|
if (this.hasCustomRadioTarget) this.customRadioTarget.checked = true
|
|
128
|
+
this.#restoreSavedTheme()
|
|
133
129
|
this.highlightTemplate("custom")
|
|
134
130
|
}
|
|
135
131
|
|
|
@@ -158,6 +154,23 @@ export default class extends Controller {
|
|
|
158
154
|
if (text) text.value = hex
|
|
159
155
|
}
|
|
160
156
|
|
|
157
|
+
#restoreSavedTheme() {
|
|
158
|
+
this.#fillThemeFields(this.#savedTheme())
|
|
159
|
+
this.#removeSetVariables()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
#removeSetVariables() {
|
|
163
|
+
this.#setVariables.forEach(variable => this.#rootStyle().removeProperty(variable))
|
|
164
|
+
this.#setVariables.clear()
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#fillThemeFields(theme) {
|
|
168
|
+
this.element.querySelectorAll("[name^='theme[']").forEach(({ name }) => {
|
|
169
|
+
const [, role, side] = name.match(THEME_FIELD)
|
|
170
|
+
this.setThemeField(name, theme[role]?.[side] ?? "")
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
161
174
|
#writeExport() {
|
|
162
175
|
const exported = this.element.querySelector("[data-theme-export]")
|
|
163
176
|
if (!exported) return
|
|
@@ -25,15 +25,15 @@ module KeystoneUi
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def editing?
|
|
28
|
-
typed.any? && [ saved["theme"],
|
|
28
|
+
typed.any? && [ saved["theme"], *preset_themes ].exclude?(typed)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
def
|
|
32
|
-
|
|
31
|
+
def preset_themes
|
|
32
|
+
KeystoneUi::Looks::Themes.names.map { |name| KeystoneUi::Looks::Themes[name][:theme] }
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
def
|
|
36
|
-
|
|
35
|
+
def saved
|
|
36
|
+
@saved ||= @saved_colours.call
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def typed
|
|
@@ -2,18 +2,24 @@
|
|
|
2
2
|
<% if previews %>
|
|
3
3
|
<style>
|
|
4
4
|
[data-editor-panel]:not([data-shown]) { display: none; }
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
[data-editor-
|
|
5
|
+
[data-editor-space] { container-type: inline-size; }
|
|
6
|
+
@container (min-width: 32rem) {
|
|
7
|
+
[data-editor-space] .ks-tab-bar { display: none; }
|
|
8
|
+
[data-editor-panels] { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1.5rem; }
|
|
8
9
|
[data-editor-panel]:not([data-shown]) { display: block; }
|
|
9
10
|
}
|
|
11
|
+
@container (min-width: 56rem) {
|
|
12
|
+
[data-editor-panels] { grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); align-items: start; }
|
|
13
|
+
}
|
|
10
14
|
</style>
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
<div data-editor-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
<div data-editor-space>
|
|
16
|
+
<%= ui_tab_switcher(tabs: %w[Edit Preview]) do %>
|
|
17
|
+
<div data-editor-panels>
|
|
18
|
+
<div data-editor-panel data-shown><%= editor %></div>
|
|
19
|
+
<div data-editor-panel><%= render partial: "keystone_ui/preferences/settings/theme_preview" %></div>
|
|
20
|
+
</div>
|
|
21
|
+
<% end %>
|
|
22
|
+
</div>
|
|
17
23
|
<% else %>
|
|
18
24
|
<%= editor %>
|
|
19
25
|
<% end %>
|
|
@@ -7,18 +7,25 @@
|
|
|
7
7
|
<% roles = KeystoneUi::Preferences::ThemeRoles::ROLES.to_h { |name, role| [ name, role.merge(hover: KeystoneUi::Preferences::ThemeCss::HOVERS[name]).compact ] } %>
|
|
8
8
|
<div data-action="change->keystone-ui--preferences--appearance-editor#apply input->keystone-ui--preferences--appearance-editor#apply" data-theme-roles="<%= roles.to_json %>" data-saved-theme="<%= theme.to_json %>" style="display: flex; flex-direction: column; gap: 2rem;">
|
|
9
9
|
<% KeystoneUi::Preferences::ThemeRoles::GROUPS.each do |group, sets| %>
|
|
10
|
-
<%=
|
|
10
|
+
<%= ui_disclosure(open: sets.values.flatten.include?(check.failing_setting)) do |disclosure| %>
|
|
11
|
+
<% disclosure.with_summary do %><h2 class="<%= Keystone::Ui::SectionComponent::TITLE_CLASSES %>"><%= group %></h2><% end %>
|
|
12
|
+
<div style="display: flex; flex-direction: column; gap: 1.5rem; padding-top: 1rem;">
|
|
11
13
|
<% sets.each do |set, names| %>
|
|
12
14
|
<fieldset style="border: 0; padding: 0; margin: 0; min-width: 0;">
|
|
13
15
|
<legend class="<%= Keystone::Ui::FormFieldComponent::LABEL_CLASSES %>"><%= set %></legend>
|
|
14
16
|
<div style="display: flex; flex-wrap: wrap; gap: 1rem;">
|
|
15
17
|
<% names.each do |name| %>
|
|
16
18
|
<% role = KeystoneUi::Preferences::ThemeRoles::ROLES.fetch(name) %>
|
|
17
|
-
<% if role[:kind] == :colour
|
|
19
|
+
<% if role[:kind] == :colour %>
|
|
20
|
+
<% name_id = "#{id_prefix}-theme-#{name}-name" %>
|
|
21
|
+
<div role="group" aria-labelledby="<%= name_id %>" style="display: flex; flex-direction: column; gap: 0.5rem;">
|
|
22
|
+
<span id="<%= name_id %>" class="<%= Keystone::Ui::FormFieldComponent::LABEL_CLASSES %>">--<%= name %></span>
|
|
23
|
+
<div style="display: flex; flex-wrap: wrap; gap: 1rem;">
|
|
24
|
+
<% end %>
|
|
18
25
|
<% (role[:kind] == :colour ? %w[light dark] : %w[light]).each do |side| %>
|
|
19
26
|
<% field_name = "theme[#{name}][#{side}]" %>
|
|
20
27
|
<% value = shown.dig(name, side).to_s %>
|
|
21
|
-
<% label = role[:kind] == :colour ?
|
|
28
|
+
<% label = role[:kind] == :colour ? side.capitalize : "--#{name}" %>
|
|
22
29
|
<% if %i[colour shadow_colour].include?(role[:kind]) && (value.empty? || value.match?(KeystoneUi::Preferences::CheckedTheme::COLOUR)) %>
|
|
23
30
|
<%= ui_color_picker(name: field_name, value: value, label: label) %>
|
|
24
31
|
<% else %>
|
|
@@ -29,6 +36,7 @@
|
|
|
29
36
|
</div>
|
|
30
37
|
<% end %>
|
|
31
38
|
<% end %>
|
|
39
|
+
<% if role[:kind] == :colour %></div><% end %>
|
|
32
40
|
<% if name == check.failing_setting %>
|
|
33
41
|
<p class="<%= Keystone::Ui::FormFieldComponent::ERROR_CLASSES %>"><%= check.refusal.message %></p>
|
|
34
42
|
<% end %>
|
|
@@ -37,6 +45,7 @@
|
|
|
37
45
|
</div>
|
|
38
46
|
</fieldset>
|
|
39
47
|
<% end %>
|
|
48
|
+
</div>
|
|
40
49
|
<% end %>
|
|
41
50
|
<% end %>
|
|
42
51
|
<div class="<%= Keystone::Ui::FormFieldComponent::WRAPPER_CLASSES %>">
|