keystone_ui 0.45.0 → 0.47.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: 978bd820f9629e5a312c465218ba68c9f055edbf7d4e740b450ffc935541ef00
4
- data.tar.gz: 1d210e3d67198f807e6ebd626de17c8f39ff98182409b6b1746061f1b63830f9
3
+ metadata.gz: 20f1ebc15a9b1a15a854e634286e510b9b224167f3f56181ab756744afaa3f44
4
+ data.tar.gz: a53452a7e08710ca4659937d39bb6d7384f84daa768f6d8afda29c330677a4a3
5
5
  SHA512:
6
- metadata.gz: 57965061e3a70ccd8ee5be0dd9c9ba7af36afac30278bcb84fa1ec19a74c2b135367bb4fddc06371b5c914d5a6ad2c1b8332dca913ec823aa6030a28028a514a
7
- data.tar.gz: 2d8c7a55901017fc335b759596bf13a400ece07714e13a986029fa142cf94f287db343a8468555074d0847433ca227e9893466625b5c94da4d2edc936d371afc
6
+ metadata.gz: 96a47743d927612dbc8cbed5b0ab0f12c8c34ae1df1555377613b1b33962b5a8ab15732e72983879724f03c18d79da3aec22d9973bc3b8a8396b9306b8f671dc
7
+ data.tar.gz: 7a0a0e1d2910d253f1f424e65082cd63c3bd40e4b3dbf83cdc67186bfbff72e9116c4c8d4b548bfddf5c59a5ee8b6ceb18051f0a8489e3592a12bb95f2460db5
@@ -59,6 +59,8 @@ const LIGHT_THEMES = ["light", "custom"]
59
59
 
60
60
  export const isDarkMode = (theme, prefersDark) => theme === "dark" || (prefersDark && !LIGHT_THEMES.includes(theme))
61
61
 
62
+ export const drawnDark = (element, prefersDark) => isDarkMode(element.closest("[data-theme]")?.dataset.theme, prefersDark)
63
+
62
64
  const CHART_COLOR_COUNT = 5
63
65
 
64
66
  const INHERITED_FONTS = ["", "inherit"]
@@ -86,7 +88,7 @@ export const styleDatasets = (datasets, theme, resolve) => {
86
88
 
87
89
  export const watchTheme = ({ document, matchMedia, MutationObserver }, onChange) => {
88
90
  const observer = new MutationObserver(onChange)
89
- observer.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme", "data-look"] })
91
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme", "data-look", "style"] })
90
92
  observer.observe(document.head, { childList: true, subtree: true, characterData: true })
91
93
  const darkPreference = matchMedia("(prefers-color-scheme: dark)")
92
94
  darkPreference.addEventListener("change", onChange)
@@ -136,7 +138,7 @@ export default class extends Controller {
136
138
  return match ? read(match[1]) || color : color
137
139
  }
138
140
 
139
- const dark = isDarkMode(document.documentElement.dataset.theme, matchMedia("(prefers-color-scheme: dark)").matches)
141
+ const dark = drawnDark(this.element, matchMedia("(prefers-color-scheme: dark)").matches)
140
142
  return { theme: themeColors(read, dark, styles.fontFamily), resolve }
141
143
  }
142
144
  }
@@ -0,0 +1,13 @@
1
+ <svg class="ks-bar-chart" viewBox="0 0 <%= Keystone::Ui::BarChartComponent::WIDTH %> <%= Keystone::Ui::BarChartComponent::HEIGHT %>" role="img">
2
+ <% ticks.each do |tick| %>
3
+ <line class="ks-bar-chart-grid" x1="<%= Keystone::Ui::BarChartComponent::LEFT %>" x2="<%= Keystone::Ui::BarChartComponent::WIDTH - Keystone::Ui::BarChartComponent::RIGHT %>" y1="<%= tick[:y].round(2) %>" y2="<%= tick[:y].round(2) %>"></line>
4
+ <text class="ks-bar-chart-text" data-axis-tick x="<%= axis_x %>" y="<%= (tick[:y] + 4).round(2) %>" text-anchor="end"><%= tick[:label] %></text>
5
+ <% end %>
6
+ <% drawn_bars.each do |bar| %>
7
+ <rect class="<%= bar[:classes] %>" x="<%= bar[:x].round(2) %>" y="<%= bar[:y].round(2) %>" width="<%= bar[:width].round(2) %>" height="<%= bar[:height].round(2) %>"></rect>
8
+ <% if show_values? %>
9
+ <text class="ks-bar-chart-value" x="<%= bar[:center].round(2) %>" y="<%= (bar[:y] - 4).round(2) %>" text-anchor="middle"><%= bar[:value] %></text>
10
+ <% end %>
11
+ <text class="ks-bar-chart-text" data-bar-label x="<%= bar[:center].round(2) %>" y="<%= label_baseline %>" text-anchor="middle"><%= bar[:label] %></text>
12
+ <% end %>
13
+ </svg>
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Keystone
4
+ module Ui
5
+ class BarChartComponent < ViewComponent::Base
6
+ WIDTH = 600
7
+ HEIGHT = 240
8
+ LEFT = 40
9
+ RIGHT = 8
10
+ TOP = 18
11
+ BOTTOM = 24
12
+ BAR_SHARE = 0.7
13
+ TICK_COUNT = 4
14
+ ROUND_STEPS = [ 1, 2, 2.5, 5, 10 ].freeze
15
+
16
+ attr_reader :bars
17
+
18
+ def initialize(bars:, show_values: false)
19
+ @bars = bars
20
+ @show_values = show_values
21
+ end
22
+
23
+ def show_values?
24
+ @show_values
25
+ end
26
+
27
+ def drawn_bars
28
+ bars.each_with_index.map do |bar, index|
29
+ height = bar[:value].to_f / axis_max * plot_height
30
+ { x: LEFT + (index * band) + (band * (1 - BAR_SHARE) / 2), y: TOP + plot_height - height, width: band * BAR_SHARE, height: height,
31
+ center: LEFT + (index * band) + (band / 2), label: bar[:label], value: bar[:value],
32
+ classes: [ "ks-bar-chart-bar", ("ks-bar-chart-bar-shaded" if bar[:shaded]) ].compact.join(" ") }
33
+ end
34
+ end
35
+
36
+ def ticks
37
+ (0..TICK_COUNT).map do |position|
38
+ value = (step * position).round(6)
39
+ { y: TOP + plot_height - (value / axis_max * plot_height), label: (value % 1).zero? ? value.to_i.to_s : value.to_s }
40
+ end
41
+ end
42
+
43
+ def axis_x
44
+ LEFT - 6
45
+ end
46
+
47
+ def label_baseline
48
+ HEIGHT - 6
49
+ end
50
+
51
+ private
52
+
53
+ def axis_max
54
+ step * (largest / step).ceil
55
+ end
56
+
57
+ def step
58
+ @step ||= begin
59
+ raw = largest / TICK_COUNT
60
+ magnitude = 10.0**Math.log10(raw).floor
61
+ ROUND_STEPS.map { |round| round * magnitude }.find { |candidate| candidate >= raw }
62
+ end
63
+ end
64
+
65
+ def largest
66
+ bars.map { |bar| bar[:value].to_f }.select(&:positive?).max || 1.0
67
+ end
68
+
69
+ def band
70
+ plot_width / [ bars.size, 1 ].max
71
+ end
72
+
73
+ def plot_width
74
+ WIDTH - LEFT - RIGHT
75
+ end
76
+
77
+ def plot_height
78
+ HEIGHT - TOP - BOTTOM
79
+ end
80
+ end
81
+ end
82
+ end
@@ -21,6 +21,10 @@ module KeystoneUiHelper
21
21
  render Keystone::Ui::LineChartComponent.new(**args)
22
22
  end
23
23
 
24
+ def ui_bar_chart(**args)
25
+ render Keystone::Ui::BarChartComponent.new(**args)
26
+ end
27
+
24
28
  def ui_cta_banner(**args, &block)
25
29
  render Keystone::Ui::CtaBannerComponent.new(**args), &block
26
30
  end
@@ -21,6 +21,7 @@ module Keystone
21
21
  Keystone::Ui::ModalComponent,
22
22
  Keystone::Ui::SelectComponent,
23
23
  Keystone::Ui::BadgeComponent,
24
+ Keystone::Ui::BarChartComponent,
24
25
  Keystone::Ui::CopyButtonComponent,
25
26
  Keystone::Ui::CardComponent,
26
27
  Keystone::Ui::ButtonComponent,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KeystoneUi
4
- VERSION = "0.45.0"
4
+ VERSION = "0.47.0"
5
5
  end
@@ -82,6 +82,7 @@ not available in the app yet, that is `keystone_ui-install`'s job.
82
82
  - `ui_swipe_deck` — a stack of cards accepted or rejected by button or swipe.
83
83
  - `ui_chart_card` — a titled card holding a chart.
84
84
  - `ui_line_chart` — a line chart with one line per series, over labels or dates.
85
+ - `ui_bar_chart` — a bar chart drawn on the server, which needs no JavaScript and prints.
85
86
  - `ui_funnel` — a conversion funnel drawn as bars or one joined shape.
86
87
  - `ui_bucket` — an upright container filled toward a goal.
87
88
  - `ui_bucket_series` — a wrapping row of buckets.
@@ -455,6 +456,10 @@ two exceptions: `ui_page`'s `padding:` treats any value other than `:none` as
455
456
 
456
457
  - `ui_chart_card(title:, height: :md)` takes a block holding a chart, with
457
458
  `height:` `:sm` `:md` `:lg`.
459
+ - `ui_bar_chart(bars:, show_values: false)` — `bars:` is
460
+ `[{ label:, value:, shaded: }, ...]`, where `shaded: true` draws a bar
461
+ faded. It is drawn on the server as an SVG, so use it, not
462
+ `ui_line_chart`, for a chart that must print or show without JavaScript.
458
463
  - `ui_line_chart(series:, labels: nil, dates: nil, height: :md)` —
459
464
  `series:` is `[{ name:, data:, color:, dashed: }, ...]`, where `color:`
460
465
  is a CSS colour and `dashed: true` is optional. Pass exactly one of
@@ -11,6 +11,7 @@ develop:
11
11
  - ui_stat_card
12
12
  - ui_chart_card
13
13
  - ui_line_chart
14
+ - ui_bar_chart
14
15
  - ui_cta_banner
15
16
  - ui_feature_grid
16
17
  - ui_hero
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.45.0
4
+ version: 0.47.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.20.0
18
+ version: 0.22.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.20.0
25
+ version: 0.22.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: view_component
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -82,6 +82,8 @@ files:
82
82
  - app/components/keystone/ui/alert_component.rb
83
83
  - app/components/keystone/ui/badge_component.html.erb
84
84
  - app/components/keystone/ui/badge_component.rb
85
+ - app/components/keystone/ui/bar_chart_component.html.erb
86
+ - app/components/keystone/ui/bar_chart_component.rb
85
87
  - app/components/keystone/ui/bottom_nav_component.html.erb
86
88
  - app/components/keystone/ui/bottom_nav_component.rb
87
89
  - app/components/keystone/ui/bottom_nav_item_component.html.erb