@flourish/ui-styles 6.0.2 → 7.0.1

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.
package/RELEASE_NOTES.md CHANGED
@@ -1,3 +1,9 @@
1
+ # 7.0.1
2
+ * Fix vertical alignment of text in SVG downloads
3
+
4
+ # 7.0.0
5
+ * Add shared searchbox styling support
6
+
1
7
  # 6.0.2
2
8
  * Bump @flourish/pocket-knife to v2.3.1
3
9
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flourish/ui-styles",
3
- "version": "6.0.2",
3
+ "version": "7.0.1",
4
4
  "private": false,
5
5
  "description": "Flourish module for styling UI elements",
6
6
  "main": "ui-styles.js",
@@ -9,7 +9,8 @@
9
9
  "license": "LicenseRef-LICENSE",
10
10
  "dependencies": {
11
11
  "d3-selection": "^1.4.0",
12
- "@flourish/pocket-knife": "^2.3.1"
12
+ "@flourish/utils-color": "^2.0.0",
13
+ "@flourish/pocket-knife": "^2.3.2"
13
14
  },
14
15
  "devDependencies": {
15
16
  "@babel/core": "^7.23.6",
@@ -18,10 +19,10 @@
18
19
  "@rollup/plugin-babel": "^6.0.4",
19
20
  "@rollup/plugin-node-resolve": "^15.2.3",
20
21
  "eslint": "^8.35.0",
22
+ "mocha": "^10.2.0",
21
23
  "only-allow": "^1.2.1",
22
24
  "rollup": "^4.6.1",
23
25
  "uglify-js": "^3.17.4",
24
- "mocha": "^10.2.0",
25
26
  "@flourish/mocha-env": "^1.0.0"
26
27
  },
27
28
  "exports": "./src/index.js",
@@ -0,0 +1,82 @@
1
+ - property: searchbox_input_expanded
2
+ name: Always expand searchbox
3
+ type: boolean
4
+ width: full
5
+ description: If enabled, the search input will be expanded
6
+
7
+ - property: searchbox_styles_display
8
+ name: Customize searchbox styles
9
+ type: boolean
10
+ width: full
11
+
12
+ - property: searchbox_size
13
+ show_if:
14
+ searchbox_styles_display: true
15
+ name: Height
16
+ type: number
17
+ min: 0
18
+ max: 5
19
+ step: 0.1
20
+ width: half
21
+ description: Width and height of the search icon (in rem units)
22
+
23
+ - property: searchbox_width
24
+ show_if:
25
+ searchbox_styles_display: true
26
+ name: Width
27
+ type: number
28
+ min: 1
29
+ step: 1
30
+ width: half
31
+ description: Width of the search input when expanded (in rem units)
32
+
33
+ - property: search_bg_color
34
+ show_if:
35
+ searchbox_styles_display: true
36
+ name: Background
37
+ type: color
38
+ width: half
39
+ optional: true
40
+ description: Background color of the search input and dropdown
41
+
42
+ - property: search_bg_opacity
43
+ show_if:
44
+ searchbox_styles_display: true
45
+ name: Background opacity
46
+ type: number
47
+ step: 0.1
48
+ min: 0
49
+ max: 1
50
+ width: half
51
+ description: A number from 0 to 1 representing the background opacity of the search input and dropdown
52
+
53
+ - property: searchbox_stroke
54
+ show_if:
55
+ searchbox_styles_display: true
56
+ name: Stroke opacity
57
+ type: number
58
+ step: 0.1
59
+ min: 0
60
+ max: 1
61
+ width: half
62
+ description: A number from 0 to 1 representing the opacity of the searchbox stroke
63
+
64
+ - property: searchbox_color
65
+ show_if:
66
+ searchbox_styles_display: true
67
+ name: Color
68
+ type: color
69
+ width: half
70
+ optional: true
71
+ description: Color of the search icon, searchbox stroke, placeholder text and dropdown text
72
+
73
+ - property: searchbox_radius
74
+ show_if:
75
+ searchbox_styles_display: true
76
+ name: Roundness
77
+ type: number
78
+ step: 1
79
+ min: 0
80
+ max: 50
81
+ width: quarter
82
+ description: Roundness of the searchbox (in px)
package/src/index.js CHANGED
@@ -2,6 +2,11 @@ import GeneralControlsStyle from "./general-controls-style/index.js";
2
2
  import ButtonStyle from "./button-style/index.js";
3
3
  import DropdownStyle from "./dropdown-style/index.js";
4
4
  import SliderStyle from "./slider-style/index.js";
5
+ import SearchboxStyle from "./searchbox-style/index.js";
6
+
7
+ /**
8
+ * @typedef {import('./searchbox-style/index.js').SearchboxStyleState} SearchboxStyleState
9
+ */
5
10
 
6
11
  function createGeneralControlsStyle(state, selector, layout) {
7
12
  return new GeneralControlsStyle(state, selector, layout);
@@ -19,9 +24,14 @@ function createSliderStyle(state, selector, layout) {
19
24
  return new SliderStyle(state, selector, layout);
20
25
  }
21
26
 
27
+ function createSearchboxStyle(state, selector, layout) {
28
+ return new SearchboxStyle(state, selector, layout);
29
+ }
30
+
22
31
  export {
23
32
  createGeneralControlsStyle,
24
33
  createButtonStyle,
25
34
  createDropdownStyle,
26
35
  createSliderStyle,
36
+ createSearchboxStyle,
27
37
  };
@@ -0,0 +1,256 @@
1
+ import { select } from "d3-selection";
2
+ import { isPaleBackground } from "@flourish/utils-color";
3
+ import { hexToRgba } from "@flourish/pocket-knife";
4
+ import Stylesheet from "../lib/js2css.js";
5
+
6
+ const DEFAULTS = Object.freeze({
7
+ searchbox_input_expanded: false,
8
+ searchbox_size: 2,
9
+ searchbox_width: 12,
10
+ search_bg_color: null,
11
+ search_bg_opacity: 0.9,
12
+ searchbox_stroke: 0.1,
13
+ searchbox_color: null,
14
+ searchbox_radius: 4,
15
+ });
16
+
17
+ function SearchboxStyle(state_, selector_, layout_) {
18
+ this._state = state_;
19
+ for (const key in DEFAULTS) {
20
+ if (this._state[key] === undefined) this._state[key] = DEFAULTS[key];
21
+ }
22
+
23
+ this._layout = layout_ || {};
24
+
25
+ this._styles = new Stylesheet();
26
+ this._selector = selector_;
27
+ this._createStylesheet();
28
+
29
+ return this;
30
+ }
31
+
32
+ SearchboxStyle.prototype._createStylesheet = function () {
33
+ this._stylesheet = document.createElement("style");
34
+ this._stylesheet.className = "fl-ui-styles-searchbox";
35
+ document.head.appendChild(this._stylesheet);
36
+ };
37
+
38
+ SearchboxStyle.prototype.update = function () {
39
+ this._styles.clear();
40
+ const prefers_reduced_motion =
41
+ window.matchMedia &&
42
+ window.matchMedia("(prefers-reduced-motion: reduce)").matches;
43
+
44
+ const bg_color = this._state.search_bg_color || this._layout.background_color;
45
+ const background_color = bg_color
46
+ ? hexToRgba(bg_color, this._state.search_bg_opacity)
47
+ : "transparent";
48
+
49
+ const user_defined_searchbox_color =
50
+ this._state.searchbox_color || this._layout.font_color;
51
+ const searchbox_color = user_defined_searchbox_color || "inherit";
52
+ const searchbox_color_paler = user_defined_searchbox_color
53
+ ? hexToRgba(
54
+ user_defined_searchbox_color,
55
+ (this._state.searchbox_stroke + 1) / 2,
56
+ )
57
+ : "currentColor";
58
+ const searchbox_color_palest = user_defined_searchbox_color
59
+ ? hexToRgba(user_defined_searchbox_color, this._state.searchbox_stroke)
60
+ : "currentColor";
61
+
62
+ const TEXT_SIZE = 0.55; // Relative to the search box height
63
+ const ICON_SIZE = 0.45; // Relative to the search box height
64
+
65
+ // Main container
66
+ this._styles
67
+ .select(this._selector)
68
+ .style("display", "inline-block")
69
+ .style(
70
+ "width",
71
+ this._state.searchbox_size + this._state.searchbox_width + "rem",
72
+ )
73
+ .style("max-width", "calc(100% - 2px)")
74
+ .style("box-sizing", "border-box");
75
+
76
+ select(this._selector).attr(
77
+ "data-pale-background",
78
+ isPaleBackground(background_color),
79
+ );
80
+
81
+ // Input field
82
+ this._styles
83
+ .select(this._selector + " input")
84
+ .style("border", "2px solid " + searchbox_color_palest)
85
+ .style("border-radius", this._state.searchbox_radius + "px")
86
+ .style("background-color", background_color)
87
+ .style("color", searchbox_color)
88
+ .style("font-family", "inherit")
89
+ .style("cursor", this._state.searchbox_input_expanded ? "text" : "pointer")
90
+ .style("position", "relative")
91
+ .style("opacity", this._state.search_bg_opacity)
92
+ .style("transition", prefers_reduced_motion ? "none" : "200ms width")
93
+ .style("display", "block")
94
+ .style("box-sizing", "border-box")
95
+ .style("padding", "0 0 0 calc(" + this._state.searchbox_size + "rem - 4px)")
96
+ .style("font-size", this._state.searchbox_size * TEXT_SIZE + "rem");
97
+
98
+ this._styles
99
+ .select(this._selector + ".expanded input")
100
+ .style("cursor", "text");
101
+
102
+ if (this._state.searchbox_input_expanded) {
103
+ this._styles
104
+ .select(this._selector + " input")
105
+ .style("width", "100%")
106
+ .style("height", this._state.searchbox_size + "rem");
107
+ } else {
108
+ this._styles
109
+ .select(this._selector + " input")
110
+ .style("width", this._state.searchbox_size + "rem")
111
+ .style("height", this._state.searchbox_size + "rem");
112
+ }
113
+
114
+ // Search icon
115
+ this._styles
116
+ .select(this._selector + " .search-icon")
117
+ .style("position", "absolute")
118
+ .style("pointer-events", "none")
119
+ .style("width", this._state.searchbox_size * ICON_SIZE + "rem")
120
+ .style("height", this._state.searchbox_size * ICON_SIZE + "rem")
121
+ .style(
122
+ "left",
123
+ this._state.searchbox_size * Math.pow(1 - ICON_SIZE, 2) + "rem",
124
+ )
125
+ .style(
126
+ "top",
127
+ this._state.searchbox_size * Math.pow(1 - ICON_SIZE, 2) + "rem",
128
+ );
129
+
130
+ // Search icon SVG
131
+ this._styles
132
+ .select(this._selector + " .search-icon svg")
133
+ .style("width", "100%")
134
+ .style("height", "100%")
135
+ .select("path")
136
+ .style("fill", searchbox_color);
137
+
138
+ // Search list (dropdown)
139
+ const list_bg_color =
140
+ this._state.search_bg_color || this._layout.background_color;
141
+ this._styles
142
+ .select(this._selector + " .search-list")
143
+ .style("display", "none")
144
+ .style("opacity", "0")
145
+ .style("margin-top", "1px")
146
+ .style("min-width", "100%")
147
+ .style("max-height", "50vh")
148
+ .style("overflow", "auto")
149
+ .style("position", "absolute")
150
+ .style("box-shadow", "0 1px 3px rgba(0,0,0,0.2)")
151
+ .style("color", searchbox_color)
152
+ .style("border-radius", "3px")
153
+ .style("font-size", this._state.searchbox_size * TEXT_SIZE + "rem")
154
+ .style("z-index", "1000");
155
+
156
+ this._styles
157
+ .select(this._selector + " .search-list.visible")
158
+ .style("opacity", 1)
159
+ .style("visibility", "visible")
160
+ .style("pointer-events", "auto");
161
+
162
+ // Apply data-pale-background to the search-list for accessibility styles
163
+ select(this._selector + " .search-list").attr(
164
+ "data-pale-background",
165
+ isPaleBackground(list_bg_color || background_color),
166
+ );
167
+
168
+ // Search list items
169
+ this._styles
170
+ .select(this._selector + " .search-list .search-item")
171
+ .style("height", this._state.searchbox_size * TEXT_SIZE + 1 + "rem")
172
+ .style("background-color", list_bg_color || "transparent")
173
+ .style("opacity", this._state.search_bg_opacity)
174
+ .style("padding", "0.5rem")
175
+ .style("cursor", "pointer")
176
+ .style("position", "relative")
177
+ .select("p")
178
+ .style("margin", "0")
179
+ .style("line-height", "1.2em")
180
+ .style("display", "block")
181
+ .style("white-space", "nowrap")
182
+ .style("font-weight", "normal")
183
+ .style("overflow", "hidden")
184
+ .style("text-overflow", "ellipsis")
185
+ .style("pointer-events", "none");
186
+
187
+ // Search list item hover and selected states for pale backgrounds
188
+ this._styles
189
+ .select(
190
+ this._selector +
191
+ " .search-list[data-pale-background='true'] .search-item:hover",
192
+ )
193
+ .style("outline", "2px solid black")
194
+ .style("outline-offset", "-2px");
195
+
196
+ this._styles
197
+ .select(
198
+ this._selector +
199
+ " .search-list[data-pale-background='true'] .search-item.selected",
200
+ )
201
+ .style("outline", "2px solid black")
202
+ .style("outline-offset", "-2px")
203
+ .style("cursor", "default");
204
+
205
+ this._styles
206
+ .select(
207
+ this._selector +
208
+ " .search-list[data-pale-background='true'] .search-item:focus-visible",
209
+ )
210
+ .style("outline", "2px solid black")
211
+ .style("outline-offset", "-2px");
212
+
213
+ // Search list item hover and selected states for dark backgrounds
214
+ this._styles
215
+ .select(
216
+ this._selector +
217
+ " .search-list[data-pale-background='false'] .search-item:hover",
218
+ )
219
+ .style("outline", "2px solid white")
220
+ .style("outline-offset", "-2px");
221
+
222
+ this._styles
223
+ .select(
224
+ this._selector +
225
+ " .search-list[data-pale-background='false'] .search-item.selected",
226
+ )
227
+ .style("outline", "2px solid white")
228
+ .style("outline-offset", "-2px")
229
+ .style("cursor", "default");
230
+
231
+ this._styles
232
+ .select(
233
+ this._selector +
234
+ " .search-list[data-pale-background='false'] .search-item:focus-visible",
235
+ )
236
+ .style("outline", "2px solid white")
237
+ .style("outline-offset", "-2px");
238
+
239
+ // Active state (when search is focused)
240
+ this._styles
241
+ .select(this._selector + ".active .search-list")
242
+ .style("display", "block");
243
+
244
+ // Expanded state
245
+ this._styles
246
+ .select(this._selector + ".expanded input")
247
+ .style("width", "100% !important");
248
+
249
+ this._styles
250
+ .select(this._selector + " input::placeholder")
251
+ .style("color", searchbox_color_paler);
252
+
253
+ this._stylesheet.innerHTML = this._styles.print();
254
+ };
255
+
256
+ export default SearchboxStyle;