keystone_ui 0.41.0 → 0.42.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/assets/javascripts/keystone_ui/line_chart_controller.js +98 -13
- data/app/components/keystone/ui/caption_component.html.erb +1 -0
- data/app/components/keystone/ui/caption_component.rb +13 -0
- data/app/components/keystone/ui/heading_component.html.erb +8 -0
- data/app/components/keystone/ui/heading_component.rb +21 -0
- data/app/components/keystone/ui/paragraph_component.html.erb +1 -0
- data/app/components/keystone/ui/paragraph_component.rb +13 -0
- data/app/helpers/keystone_ui_helper.rb +12 -0
- data/lib/keystone_ui/safelist.rb +3 -0
- data/lib/keystone_ui/version.rb +1 -1
- data/the_local/agents/keystone_ui-develop.md +7 -0
- data/the_local/interface.yml +3 -0
- metadata +9 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 28436277e0b304a48d5093a2220bb1df0e04f94315f085781e93cb34207ea654
|
|
4
|
+
data.tar.gz: 82bb8f8de9b4132f47da04e7ab3423ba3c73e216b2ae79659f8709b52888bfde
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3e63c3495c2b1709109cb25584e9a9e5c5b7fcaef4cfb1cc1e948641b5ae06a22acc3703f52d038d69a7371b57d39a554d75a8ac671e90662f525734979ab8fd
|
|
7
|
+
data.tar.gz: 8941b3263e4944bbfc4e6d6dbc8eb82f74908ee51b922f9c6f61b6cb9c4af869171c22b8e5cbdc4bef6e7c238e740d2f531b143cd5abaddba2a5d73b36d34b1a
|
|
@@ -11,18 +11,92 @@ const MILLISECONDS_IN_A_DAY = 86_400_000
|
|
|
11
11
|
export const dayLabel = (day) =>
|
|
12
12
|
new Date(day * MILLISECONDS_IN_A_DAY).toLocaleDateString("en-US", { timeZone: "UTC", month: "short", day: "numeric", year: "numeric" })
|
|
13
13
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
const DATED_AXIS = { type: "linear", bounds: "data" }
|
|
15
|
+
const DATED_TICKS = { precision: 0, callback: dayLabel }
|
|
16
|
+
const DATED_TOOLTIP = { callbacks: { title: ([point]) => dayLabel(point.parsed.x) } }
|
|
17
|
+
|
|
18
|
+
const tooltip = (theme) => ({
|
|
19
|
+
backgroundColor: theme.surface,
|
|
20
|
+
borderColor: theme.border,
|
|
21
|
+
borderWidth: 1,
|
|
22
|
+
titleColor: theme.text,
|
|
23
|
+
bodyColor: theme.text
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const DRAW_IN_MILLISECONDS = 600
|
|
27
|
+
|
|
28
|
+
const pointStep = (context) => DRAW_IN_MILLISECONDS / context.chart.data.datasets[context.datasetIndex].data.length
|
|
29
|
+
|
|
30
|
+
const drawsIn = (context) => context.type === "data" && context.mode === "default"
|
|
31
|
+
|
|
32
|
+
const drawIn = {
|
|
33
|
+
x: {
|
|
34
|
+
type: "number",
|
|
35
|
+
easing: "linear",
|
|
36
|
+
from: NaN,
|
|
37
|
+
duration: (context) => (drawsIn(context) ? pointStep(context) : 0),
|
|
38
|
+
delay: (context) => (drawsIn(context) ? context.index * pointStep(context) : 0)
|
|
39
|
+
},
|
|
40
|
+
y: { duration: 0 }
|
|
17
41
|
}
|
|
18
42
|
|
|
19
|
-
|
|
43
|
+
const axisText = (theme) => ({ color: theme.mutedText, font: { size: 11, family: theme.font } })
|
|
44
|
+
|
|
45
|
+
export const chartOptions = (dated, theme = {}, animated = false) => ({
|
|
20
46
|
responsive: true,
|
|
21
47
|
maintainAspectRatio: false,
|
|
22
48
|
interaction: { mode: "index", intersect: false },
|
|
23
|
-
...(
|
|
49
|
+
...(animated ? { animations: drawIn } : { animation: false }),
|
|
50
|
+
elements: { line: { borderWidth: 1.5 }, point: { radius: 0, hoverRadius: 4 } },
|
|
51
|
+
scales: {
|
|
52
|
+
x: { ...(dated ? DATED_AXIS : {}), grid: { display: false }, ticks: { ...(dated ? DATED_TICKS : {}), ...axisText(theme) } },
|
|
53
|
+
y: { border: { display: false }, grid: { color: theme.border }, ticks: axisText(theme) }
|
|
54
|
+
},
|
|
55
|
+
plugins: { tooltip: { ...(dated ? DATED_TOOLTIP : {}), ...tooltip(theme) } }
|
|
24
56
|
})
|
|
25
57
|
|
|
58
|
+
const LIGHT_THEMES = ["light", "custom"]
|
|
59
|
+
|
|
60
|
+
export const isDarkMode = (theme, prefersDark) => theme === "dark" || (prefersDark && !LIGHT_THEMES.includes(theme))
|
|
61
|
+
|
|
62
|
+
const CHART_COLOR_COUNT = 5
|
|
63
|
+
|
|
64
|
+
const INHERITED_FONTS = ["", "inherit"]
|
|
65
|
+
|
|
66
|
+
export const themeColors = (read, dark, inheritedFont) => {
|
|
67
|
+
const variable = (name) => read(dark ? `${name}-dark` : name)
|
|
68
|
+
const bodyFont = read("--ks-font-body")
|
|
69
|
+
return {
|
|
70
|
+
series: Array.from({ length: CHART_COLOR_COUNT }, (_, index) => variable(`--ks-color-chart-${index + 1}`)),
|
|
71
|
+
border: variable("--ks-color-border"),
|
|
72
|
+
surface: variable("--ks-color-surface"),
|
|
73
|
+
text: variable("--ks-color-text"),
|
|
74
|
+
mutedText: variable("--ks-color-text-muted"),
|
|
75
|
+
font: INHERITED_FONTS.includes(bodyFont) ? inheritedFont : bodyFont
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const styleDatasets = (datasets, theme, resolve) => {
|
|
80
|
+
let uncolored = 0
|
|
81
|
+
return datasets.map((dataset) => {
|
|
82
|
+
const color = dataset.borderColor ? resolve(dataset.borderColor) : theme.series[uncolored++ % theme.series.length]
|
|
83
|
+
return { ...dataset, borderColor: color, pointHoverBackgroundColor: color }
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const watchTheme = ({ document, matchMedia, MutationObserver }, onChange) => {
|
|
88
|
+
const observer = new MutationObserver(onChange)
|
|
89
|
+
observer.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme", "data-look"] })
|
|
90
|
+
observer.observe(document.head, { childList: true, subtree: true, characterData: true })
|
|
91
|
+
const darkPreference = matchMedia("(prefers-color-scheme: dark)")
|
|
92
|
+
darkPreference.addEventListener("change", onChange)
|
|
93
|
+
|
|
94
|
+
return () => {
|
|
95
|
+
observer.disconnect()
|
|
96
|
+
darkPreference.removeEventListener("change", onChange)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
26
100
|
export default class extends Controller {
|
|
27
101
|
static targets = ["canvas"]
|
|
28
102
|
static values = { data: Object, dated: Boolean }
|
|
@@ -31,27 +105,38 @@ export default class extends Controller {
|
|
|
31
105
|
const Chart = await loadChart()
|
|
32
106
|
if (!this.element.isConnected) return
|
|
33
107
|
|
|
108
|
+
const { theme, resolve } = this.readTheme()
|
|
109
|
+
this.animated = !matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
34
110
|
this.chart = new Chart(this.canvasTarget, {
|
|
35
111
|
type: "line",
|
|
36
|
-
data: this.
|
|
37
|
-
options: chartOptions(this.datedValue)
|
|
112
|
+
data: { ...this.dataValue, datasets: styleDatasets(this.dataValue.datasets, theme, resolve) },
|
|
113
|
+
options: chartOptions(this.datedValue, theme, this.animated)
|
|
38
114
|
})
|
|
115
|
+
this.stopWatchingTheme = watchTheme({ document, matchMedia: (query) => matchMedia(query), MutationObserver }, () => this.redraw())
|
|
39
116
|
}
|
|
40
117
|
|
|
41
118
|
disconnect() {
|
|
119
|
+
this.stopWatchingTheme?.()
|
|
42
120
|
this.chart?.destroy()
|
|
43
121
|
}
|
|
44
122
|
|
|
45
|
-
|
|
123
|
+
redraw() {
|
|
124
|
+
const { theme, resolve } = this.readTheme()
|
|
125
|
+
const styled = styleDatasets(this.dataValue.datasets, theme, resolve)
|
|
126
|
+
this.chart.data.datasets.forEach((dataset, index) => Object.assign(dataset, styled[index]))
|
|
127
|
+
this.chart.options = chartOptions(this.datedValue, theme, this.animated)
|
|
128
|
+
this.chart.update("none")
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
readTheme() {
|
|
46
132
|
const styles = getComputedStyle(this.element)
|
|
133
|
+
const read = (name) => styles.getPropertyValue(name).trim()
|
|
47
134
|
const resolve = (color) => {
|
|
48
135
|
const match = typeof color === "string" && color.match(/^var\((--[^)]+)\)$/)
|
|
49
|
-
return match ?
|
|
136
|
+
return match ? read(match[1]) || color : color
|
|
50
137
|
}
|
|
51
138
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
})
|
|
55
|
-
return data
|
|
139
|
+
const dark = isDarkMode(document.documentElement.dataset.theme, matchMedia("(prefers-color-scheme: dark)").matches)
|
|
140
|
+
return { theme: themeColors(read, dark, styles.fontFamily), resolve }
|
|
56
141
|
}
|
|
57
142
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<p class="ks-caption"><%= text %></p>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Keystone
|
|
4
|
+
module Ui
|
|
5
|
+
class HeadingComponent < ViewComponent::Base
|
|
6
|
+
SIZE_CLASSES = { sm: "ks-heading-sm", md: "ks-heading-md", lg: "ks-heading-lg" }.freeze
|
|
7
|
+
|
|
8
|
+
attr_reader :text, :note
|
|
9
|
+
|
|
10
|
+
def initialize(text:, size: :md, note: nil)
|
|
11
|
+
@text = text
|
|
12
|
+
@note = note
|
|
13
|
+
@size = size
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def classes
|
|
17
|
+
[ "ks-heading", SIZE_CLASSES.fetch(@size) ].join(" ")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<p class="ks-paragraph"><%= text %></p>
|
|
@@ -237,6 +237,18 @@ module KeystoneUiHelper
|
|
|
237
237
|
render Keystone::Ui::FigureComponent.new(**args)
|
|
238
238
|
end
|
|
239
239
|
|
|
240
|
+
def ui_heading(**args)
|
|
241
|
+
render Keystone::Ui::HeadingComponent.new(**args)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def ui_caption(**args)
|
|
245
|
+
render Keystone::Ui::CaptionComponent.new(**args)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def ui_paragraph(**args)
|
|
249
|
+
render Keystone::Ui::ParagraphComponent.new(**args)
|
|
250
|
+
end
|
|
251
|
+
|
|
240
252
|
def ui_info(**args, &block)
|
|
241
253
|
render(Keystone::Ui::InfoComponent.new(**args), &block)
|
|
242
254
|
end
|
data/lib/keystone_ui/safelist.rb
CHANGED
|
@@ -14,7 +14,9 @@ module Keystone
|
|
|
14
14
|
Keystone::Ui::LineChartComponent,
|
|
15
15
|
Keystone::Ui::CtaBannerComponent,
|
|
16
16
|
Keystone::Ui::FeatureGridComponent,
|
|
17
|
+
Keystone::Ui::CaptionComponent,
|
|
17
18
|
Keystone::Ui::FigureComponent,
|
|
19
|
+
Keystone::Ui::HeadingComponent,
|
|
18
20
|
Keystone::Ui::HeroComponent,
|
|
19
21
|
Keystone::Ui::ModalComponent,
|
|
20
22
|
Keystone::Ui::SelectComponent,
|
|
@@ -32,6 +34,7 @@ module Keystone
|
|
|
32
34
|
Keystone::Ui::TextareaComponent,
|
|
33
35
|
Keystone::Ui::FormFieldComponent,
|
|
34
36
|
Keystone::Ui::PageHeaderComponent,
|
|
37
|
+
Keystone::Ui::ParagraphComponent,
|
|
35
38
|
Keystone::Ui::AlertComponent,
|
|
36
39
|
Keystone::Ui::NavDropdownComponent,
|
|
37
40
|
Keystone::Ui::NavItemComponent,
|
data/lib/keystone_ui/version.rb
CHANGED
|
@@ -63,6 +63,9 @@ not available in the app yet, that is `keystone_ui-install`'s job.
|
|
|
63
63
|
- `ui_button` — a link or a button styled as a button.
|
|
64
64
|
- `ui_badge` — a coloured pill holding a short label.
|
|
65
65
|
- `ui_figure` — one amount as inline text, coloured as a gain or a loss.
|
|
66
|
+
- `ui_heading` — a heading at one of three sizes, with an optional short note on its right.
|
|
67
|
+
- `ui_caption` — small uppercase muted text placed above a title or a figure.
|
|
68
|
+
- `ui_paragraph` — a paragraph of running text.
|
|
66
69
|
- `ui_alert` — a banner message, optionally dismissible.
|
|
67
70
|
- `ui_progress` — a labelled progress bar.
|
|
68
71
|
- `ui_stat_card` — a single metric tile with optional change, link and info button.
|
|
@@ -397,6 +400,10 @@ two exceptions: `ui_page`'s `padding:` treats any value other than `:none` as
|
|
|
397
400
|
- `ui_disclosure(open: false)` takes a block yielding the component. Fill
|
|
398
401
|
its `summary` slot with the header, and the rest of the block is the body.
|
|
399
402
|
- `ui_accordion(items: [])` — `items:` is `[{ question:, answer: }, ...]`.
|
|
403
|
+
- `ui_heading(text:, size: :md, note: nil)` — `size:` is `:sm` `:md` `:lg`,
|
|
404
|
+
and `note:` puts a short muted note at the right end of the heading's row.
|
|
405
|
+
- `ui_caption(text:)` shows small uppercase muted text above a title or a figure.
|
|
406
|
+
- `ui_paragraph(text:)` shows running text.
|
|
400
407
|
|
|
401
408
|
10. Explain figures.
|
|
402
409
|
|
data/the_local/interface.yml
CHANGED
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.42.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tyler Schneider
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.19.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.
|
|
25
|
+
version: 0.19.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: view_component
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -98,6 +98,8 @@ files:
|
|
|
98
98
|
- app/components/keystone/ui/button_component.rb
|
|
99
99
|
- app/components/keystone/ui/calculation_component.html.erb
|
|
100
100
|
- app/components/keystone/ui/calculation_component.rb
|
|
101
|
+
- app/components/keystone/ui/caption_component.html.erb
|
|
102
|
+
- app/components/keystone/ui/caption_component.rb
|
|
101
103
|
- app/components/keystone/ui/card_component.html.erb
|
|
102
104
|
- app/components/keystone/ui/card_component.rb
|
|
103
105
|
- app/components/keystone/ui/card_link_component.html.erb
|
|
@@ -139,6 +141,8 @@ files:
|
|
|
139
141
|
- app/components/keystone/ui/funnel_component.rb
|
|
140
142
|
- app/components/keystone/ui/grid_component.html.erb
|
|
141
143
|
- app/components/keystone/ui/grid_component.rb
|
|
144
|
+
- app/components/keystone/ui/heading_component.html.erb
|
|
145
|
+
- app/components/keystone/ui/heading_component.rb
|
|
142
146
|
- app/components/keystone/ui/hero_component.html.erb
|
|
143
147
|
- app/components/keystone/ui/hero_component.rb
|
|
144
148
|
- app/components/keystone/ui/info_component.html.erb
|
|
@@ -171,6 +175,8 @@ files:
|
|
|
171
175
|
- app/components/keystone/ui/page_header_component.rb
|
|
172
176
|
- app/components/keystone/ui/panel_component.html.erb
|
|
173
177
|
- app/components/keystone/ui/panel_component.rb
|
|
178
|
+
- app/components/keystone/ui/paragraph_component.html.erb
|
|
179
|
+
- app/components/keystone/ui/paragraph_component.rb
|
|
174
180
|
- app/components/keystone/ui/pipeline_component.html.erb
|
|
175
181
|
- app/components/keystone/ui/pipeline_component.rb
|
|
176
182
|
- app/components/keystone/ui/progress_component.html.erb
|