basefn 1.6.1 → 1.8.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.
- package/package.json +1 -1
- package/src/Basefn.res +8 -0
- package/src/Basefn.res.mjs +4 -0
- package/src/Basefn__Responsive.res +291 -0
- package/src/Basefn__Responsive.res.mjs +407 -0
- package/src/components/Basefn__Dropdown.css +6 -0
- package/src/components/Basefn__Dropdown.res +5 -0
- package/src/components/Basefn__Dropdown.res.mjs +9 -2
- package/src/components/Basefn__Responsive.css +118 -0
package/package.json
CHANGED
package/src/Basefn.res
CHANGED
|
@@ -71,6 +71,9 @@ type gridJustifyContent = Basefn__Grid.justifyContent
|
|
|
71
71
|
type gridAlignContent = Basefn__Grid.alignContent
|
|
72
72
|
type gridItemColumnSpan = Basefn__Grid.Item.columnSpan
|
|
73
73
|
type gridItemRowSpan = Basefn__Grid.Item.rowSpan
|
|
74
|
+
type breakpoint = Basefn__Responsive.breakpoint
|
|
75
|
+
type currentBreakpoint = Basefn__Responsive.currentBreakpoint
|
|
76
|
+
type responsiveValue<'a> = Basefn__Responsive.responsiveValue<'a>
|
|
74
77
|
|
|
75
78
|
// Form Components
|
|
76
79
|
module Button = {
|
|
@@ -224,3 +227,8 @@ module AlertDialog = {
|
|
|
224
227
|
module ContextMenu = {
|
|
225
228
|
include Basefn__ContextMenu
|
|
226
229
|
}
|
|
230
|
+
|
|
231
|
+
// Responsive Utilities
|
|
232
|
+
module Responsive = {
|
|
233
|
+
include Basefn__Responsive
|
|
234
|
+
}
|
package/src/Basefn.res.mjs
CHANGED
|
@@ -37,6 +37,7 @@ import * as Basefn__AppLayout from "./components/Basefn__AppLayout.res.mjs";
|
|
|
37
37
|
import * as Basefn__HoverCard from "./components/Basefn__HoverCard.res.mjs";
|
|
38
38
|
import * as Basefn__Separator from "./components/Basefn__Separator.res.mjs";
|
|
39
39
|
import * as Basefn__Breadcrumb from "./components/Basefn__Breadcrumb.res.mjs";
|
|
40
|
+
import * as Basefn__Responsive from "./Basefn__Responsive.res.mjs";
|
|
40
41
|
import * as Basefn__ScrollArea from "./components/Basefn__ScrollArea.res.mjs";
|
|
41
42
|
import * as Basefn__Typography from "./components/Basefn__Typography.res.mjs";
|
|
42
43
|
import * as Basefn__AlertDialog from "./components/Basefn__AlertDialog.res.mjs";
|
|
@@ -139,6 +140,8 @@ let AlertDialog = Basefn__AlertDialog;
|
|
|
139
140
|
|
|
140
141
|
let ContextMenu = Basefn__ContextMenu;
|
|
141
142
|
|
|
143
|
+
let Responsive = Basefn__Responsive;
|
|
144
|
+
|
|
142
145
|
export {
|
|
143
146
|
Button,
|
|
144
147
|
Input,
|
|
@@ -185,5 +188,6 @@ export {
|
|
|
185
188
|
HoverCard,
|
|
186
189
|
AlertDialog,
|
|
187
190
|
ContextMenu,
|
|
191
|
+
Responsive,
|
|
188
192
|
}
|
|
189
193
|
/* Not a pure module */
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
%%raw(`import './components/Basefn__Responsive.css'`)
|
|
2
|
+
open Xote
|
|
3
|
+
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Breakpoints
|
|
6
|
+
// ============================================================================
|
|
7
|
+
|
|
8
|
+
type breakpoint = Xs | Sm | Md | Lg | Xl | Xxl
|
|
9
|
+
|
|
10
|
+
let breakpointToPixels = (bp: breakpoint): int => {
|
|
11
|
+
switch bp {
|
|
12
|
+
| Xs => 480
|
|
13
|
+
| Sm => 640
|
|
14
|
+
| Md => 768
|
|
15
|
+
| Lg => 1024
|
|
16
|
+
| Xl => 1280
|
|
17
|
+
| Xxl => 1536
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let breakpointToString = (bp: breakpoint): string => {
|
|
22
|
+
switch bp {
|
|
23
|
+
| Xs => "xs"
|
|
24
|
+
| Sm => "sm"
|
|
25
|
+
| Md => "md"
|
|
26
|
+
| Lg => "lg"
|
|
27
|
+
| Xl => "xl"
|
|
28
|
+
| Xxl => "xxl"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// Media query strings
|
|
34
|
+
// ============================================================================
|
|
35
|
+
|
|
36
|
+
let minWidth = (bp: breakpoint): string => {
|
|
37
|
+
let px = breakpointToPixels(bp)
|
|
38
|
+
`(min-width: ${Int.toString(px)}px)`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let maxWidth = (bp: breakpoint): string => {
|
|
42
|
+
let px = breakpointToPixels(bp) - 1
|
|
43
|
+
`(max-width: ${Int.toString(px)}px)`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let between = (lower: breakpoint, upper: breakpoint): string => {
|
|
47
|
+
let minPx = breakpointToPixels(lower)
|
|
48
|
+
let maxPx = breakpointToPixels(upper) - 1
|
|
49
|
+
`(min-width: ${Int.toString(minPx)}px) and (max-width: ${Int.toString(maxPx)}px)`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Predefined media query strings
|
|
53
|
+
module Query = {
|
|
54
|
+
let xsUp = minWidth(Xs)
|
|
55
|
+
let smUp = minWidth(Sm)
|
|
56
|
+
let mdUp = minWidth(Md)
|
|
57
|
+
let lgUp = minWidth(Lg)
|
|
58
|
+
let xlUp = minWidth(Xl)
|
|
59
|
+
let xxlUp = minWidth(Xxl)
|
|
60
|
+
|
|
61
|
+
let xsDown = maxWidth(Xs)
|
|
62
|
+
let smDown = maxWidth(Sm)
|
|
63
|
+
let mdDown = maxWidth(Md)
|
|
64
|
+
let lgDown = maxWidth(Lg)
|
|
65
|
+
let xlDown = maxWidth(Xl)
|
|
66
|
+
let xxlDown = maxWidth(Xxl)
|
|
67
|
+
|
|
68
|
+
let xsOnly = maxWidth(Sm)
|
|
69
|
+
let smOnly = between(Sm, Md)
|
|
70
|
+
let mdOnly = between(Md, Lg)
|
|
71
|
+
let lgOnly = between(Lg, Xl)
|
|
72
|
+
let xlOnly = between(Xl, Xxl)
|
|
73
|
+
let xxlOnly = minWidth(Xxl)
|
|
74
|
+
|
|
75
|
+
let portrait = "(orientation: portrait)"
|
|
76
|
+
let landscape = "(orientation: landscape)"
|
|
77
|
+
let prefersReducedMotion = "(prefers-reduced-motion: reduce)"
|
|
78
|
+
let prefersDark = "(prefers-color-scheme: dark)"
|
|
79
|
+
let prefersLight = "(prefers-color-scheme: light)"
|
|
80
|
+
let highContrast = "(prefers-contrast: more)"
|
|
81
|
+
let touchDevice = "(hover: none) and (pointer: coarse)"
|
|
82
|
+
let finePointer = "(hover: hover) and (pointer: fine)"
|
|
83
|
+
let retina = "(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ============================================================================
|
|
87
|
+
// matchMedia binding
|
|
88
|
+
// ============================================================================
|
|
89
|
+
|
|
90
|
+
type mediaQueryList
|
|
91
|
+
|
|
92
|
+
@val external matchMedia: string => mediaQueryList = "window.matchMedia"
|
|
93
|
+
@get external matches: mediaQueryList => bool = "matches"
|
|
94
|
+
@send external addListener: (mediaQueryList, mediaQueryList => unit) => unit = "addEventListener"
|
|
95
|
+
@send external removeListener: (mediaQueryList, mediaQueryList => unit) => unit = "removeEventListener"
|
|
96
|
+
|
|
97
|
+
let addChangeListener: (mediaQueryList, mediaQueryList => unit) => unit = %raw(`
|
|
98
|
+
function(mql, cb) { mql.addEventListener("change", cb) }
|
|
99
|
+
`)
|
|
100
|
+
|
|
101
|
+
let removeChangeListener: (mediaQueryList, mediaQueryList => unit) => unit = %raw(`
|
|
102
|
+
function(mql, cb) { mql.removeEventListener("change", cb) }
|
|
103
|
+
`)
|
|
104
|
+
|
|
105
|
+
// ============================================================================
|
|
106
|
+
// Media query matching utilities
|
|
107
|
+
// ============================================================================
|
|
108
|
+
|
|
109
|
+
let matchesQuery = (query: string): bool => {
|
|
110
|
+
matches(matchMedia(query))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let matchesBreakpointUp = (bp: breakpoint): bool => matchesQuery(minWidth(bp))
|
|
114
|
+
let matchesBreakpointDown = (bp: breakpoint): bool => matchesQuery(maxWidth(bp))
|
|
115
|
+
|
|
116
|
+
// ============================================================================
|
|
117
|
+
// Signal-based media query tracking
|
|
118
|
+
// ============================================================================
|
|
119
|
+
|
|
120
|
+
let makeMediaSignal = (query: string): Signal.t<bool> => {
|
|
121
|
+
let mql = matchMedia(query)
|
|
122
|
+
let signal = Signal.make(matches(mql))
|
|
123
|
+
let handler = (evt: mediaQueryList) => {
|
|
124
|
+
Signal.set(signal, matches(evt))
|
|
125
|
+
}
|
|
126
|
+
addChangeListener(mql, handler)
|
|
127
|
+
signal
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let makeBreakpointSignal = (bp: breakpoint): Signal.t<bool> => {
|
|
131
|
+
makeMediaSignal(minWidth(bp))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ============================================================================
|
|
135
|
+
// Predefined screen size signals (memoized singletons)
|
|
136
|
+
// ============================================================================
|
|
137
|
+
|
|
138
|
+
// Helper: create a memoized signal that initializes on first access
|
|
139
|
+
let _memo = (make: unit => Signal.t<bool>): (unit => Signal.t<bool>) => {
|
|
140
|
+
let cached: ref<option<Signal.t<bool>>> = ref(None)
|
|
141
|
+
() => {
|
|
142
|
+
switch cached.contents {
|
|
143
|
+
| Some(s) => s
|
|
144
|
+
| None => {
|
|
145
|
+
let s = make()
|
|
146
|
+
cached := Some(s)
|
|
147
|
+
s
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Exact breakpoint range signals
|
|
154
|
+
let isXsScreen = _memo(() => makeMediaSignal(Query.xsDown))
|
|
155
|
+
let isSmScreen = _memo(() => makeMediaSignal(Query.smOnly))
|
|
156
|
+
let isMdScreen = _memo(() => makeMediaSignal(Query.mdOnly))
|
|
157
|
+
let isLgScreen = _memo(() => makeMediaSignal(Query.lgOnly))
|
|
158
|
+
let isXlScreen = _memo(() => makeMediaSignal(Query.xlOnly))
|
|
159
|
+
let isXxlScreen = _memo(() => makeMediaSignal(Query.xxlOnly))
|
|
160
|
+
|
|
161
|
+
// "And up" signals
|
|
162
|
+
let isSmallAndUp = _memo(() => makeMediaSignal(Query.smUp))
|
|
163
|
+
let isMediumAndUp = _memo(() => makeMediaSignal(Query.mdUp))
|
|
164
|
+
let isLargeAndUp = _memo(() => makeMediaSignal(Query.lgUp))
|
|
165
|
+
let isExtraLargeAndUp = _memo(() => makeMediaSignal(Query.xlUp))
|
|
166
|
+
|
|
167
|
+
// Semantic device signals
|
|
168
|
+
let isMobile = _memo(() => makeMediaSignal(Query.smDown))
|
|
169
|
+
let isTablet = _memo(() => makeMediaSignal(Query.mdOnly))
|
|
170
|
+
let isDesktop = _memo(() => makeMediaSignal(Query.lgUp))
|
|
171
|
+
|
|
172
|
+
// Preference / capability signals
|
|
173
|
+
let isPortrait = _memo(() => makeMediaSignal(Query.portrait))
|
|
174
|
+
let isLandscape = _memo(() => makeMediaSignal(Query.landscape))
|
|
175
|
+
let prefersReducedMotion = _memo(() => makeMediaSignal(Query.prefersReducedMotion))
|
|
176
|
+
let prefersDarkMode = _memo(() => makeMediaSignal(Query.prefersDark))
|
|
177
|
+
let isTouchDevice = _memo(() => makeMediaSignal(Query.touchDevice))
|
|
178
|
+
let isRetina = _memo(() => makeMediaSignal(Query.retina))
|
|
179
|
+
|
|
180
|
+
// ============================================================================
|
|
181
|
+
// Current breakpoint tracking
|
|
182
|
+
// ============================================================================
|
|
183
|
+
|
|
184
|
+
type currentBreakpoint = ExtraSmall | Small | Medium | Large | ExtraLarge | ExtraExtraLarge
|
|
185
|
+
|
|
186
|
+
let currentBreakpointToString = (bp: currentBreakpoint): string => {
|
|
187
|
+
switch bp {
|
|
188
|
+
| ExtraSmall => "xs"
|
|
189
|
+
| Small => "sm"
|
|
190
|
+
| Medium => "md"
|
|
191
|
+
| Large => "lg"
|
|
192
|
+
| ExtraLarge => "xl"
|
|
193
|
+
| ExtraExtraLarge => "xxl"
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let getCurrentBreakpoint = (): currentBreakpoint => {
|
|
198
|
+
if matchesQuery(Query.xxlUp) {
|
|
199
|
+
ExtraExtraLarge
|
|
200
|
+
} else if matchesQuery(Query.xlUp) {
|
|
201
|
+
ExtraLarge
|
|
202
|
+
} else if matchesQuery(Query.lgUp) {
|
|
203
|
+
Large
|
|
204
|
+
} else if matchesQuery(Query.mdUp) {
|
|
205
|
+
Medium
|
|
206
|
+
} else if matchesQuery(Query.smUp) {
|
|
207
|
+
Small
|
|
208
|
+
} else {
|
|
209
|
+
ExtraSmall
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let makeCurrentBreakpointSignal = (): Signal.t<currentBreakpoint> => {
|
|
214
|
+
let signal = Signal.make(getCurrentBreakpoint())
|
|
215
|
+
|
|
216
|
+
let breakpoints = [Sm, Md, Lg, Xl, Xxl]
|
|
217
|
+
breakpoints->Array.forEach(bp => {
|
|
218
|
+
let mql = matchMedia(minWidth(bp))
|
|
219
|
+
let _handler = (_evt: mediaQueryList) => {
|
|
220
|
+
Signal.set(signal, getCurrentBreakpoint())
|
|
221
|
+
}
|
|
222
|
+
addChangeListener(mql, _handler)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
signal
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
let currentBreakpoint: ref<option<Signal.t<currentBreakpoint>>> = ref(None)
|
|
229
|
+
|
|
230
|
+
let getCurrentBreakpointSignal = (): Signal.t<currentBreakpoint> => {
|
|
231
|
+
switch currentBreakpoint.contents {
|
|
232
|
+
| Some(s) => s
|
|
233
|
+
| None => {
|
|
234
|
+
let s = makeCurrentBreakpointSignal()
|
|
235
|
+
currentBreakpoint := Some(s)
|
|
236
|
+
s
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ============================================================================
|
|
242
|
+
// Responsive value helpers
|
|
243
|
+
// ============================================================================
|
|
244
|
+
|
|
245
|
+
type responsiveValue<'a> = {
|
|
246
|
+
xs?: 'a,
|
|
247
|
+
sm?: 'a,
|
|
248
|
+
md?: 'a,
|
|
249
|
+
lg?: 'a,
|
|
250
|
+
xl?: 'a,
|
|
251
|
+
xxl?: 'a,
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
let resolveResponsiveValue = (value: responsiveValue<'a>, fallback: 'a): 'a => {
|
|
255
|
+
let bp = getCurrentBreakpoint()
|
|
256
|
+
// Build ordered list from current breakpoint down, cascade to find first defined value
|
|
257
|
+
let ordered = switch bp {
|
|
258
|
+
| ExtraExtraLarge => [value.xxl, value.xl, value.lg, value.md, value.sm, value.xs]
|
|
259
|
+
| ExtraLarge => [value.xl, value.lg, value.md, value.sm, value.xs]
|
|
260
|
+
| Large => [value.lg, value.md, value.sm, value.xs]
|
|
261
|
+
| Medium => [value.md, value.sm, value.xs]
|
|
262
|
+
| Small => [value.sm, value.xs]
|
|
263
|
+
| ExtraSmall => [value.xs]
|
|
264
|
+
}
|
|
265
|
+
let rec find = (items: array<option<'a>>, idx: int): 'a => {
|
|
266
|
+
if idx >= Array.length(items) {
|
|
267
|
+
fallback
|
|
268
|
+
} else {
|
|
269
|
+
switch items->Array.getUnsafe(idx) {
|
|
270
|
+
| Some(v) => v
|
|
271
|
+
| None => find(items, idx + 1)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
find(ordered, 0)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ============================================================================
|
|
279
|
+
// Visibility helpers (CSS class-based)
|
|
280
|
+
// ============================================================================
|
|
281
|
+
|
|
282
|
+
module Visibility = {
|
|
283
|
+
let hiddenBelow = (bp: breakpoint): string =>
|
|
284
|
+
`basefn-hidden-below-${breakpointToString(bp)}`
|
|
285
|
+
|
|
286
|
+
let hiddenAbove = (bp: breakpoint): string =>
|
|
287
|
+
`basefn-hidden-above-${breakpointToString(bp)}`
|
|
288
|
+
|
|
289
|
+
let visibleOnly = (bp: breakpoint): string =>
|
|
290
|
+
`basefn-visible-${breakpointToString(bp)}-only`
|
|
291
|
+
}
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Xote from "xote/src/Xote.res.mjs";
|
|
4
|
+
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
5
|
+
|
|
6
|
+
import './components/Basefn__Responsive.css'
|
|
7
|
+
;
|
|
8
|
+
|
|
9
|
+
function breakpointToPixels(bp) {
|
|
10
|
+
switch (bp) {
|
|
11
|
+
case "Xs" :
|
|
12
|
+
return 480;
|
|
13
|
+
case "Sm" :
|
|
14
|
+
return 640;
|
|
15
|
+
case "Md" :
|
|
16
|
+
return 768;
|
|
17
|
+
case "Lg" :
|
|
18
|
+
return 1024;
|
|
19
|
+
case "Xl" :
|
|
20
|
+
return 1280;
|
|
21
|
+
case "Xxl" :
|
|
22
|
+
return 1536;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function breakpointToString(bp) {
|
|
27
|
+
switch (bp) {
|
|
28
|
+
case "Xs" :
|
|
29
|
+
return "xs";
|
|
30
|
+
case "Sm" :
|
|
31
|
+
return "sm";
|
|
32
|
+
case "Md" :
|
|
33
|
+
return "md";
|
|
34
|
+
case "Lg" :
|
|
35
|
+
return "lg";
|
|
36
|
+
case "Xl" :
|
|
37
|
+
return "xl";
|
|
38
|
+
case "Xxl" :
|
|
39
|
+
return "xxl";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function minWidth(bp) {
|
|
44
|
+
let px = breakpointToPixels(bp);
|
|
45
|
+
return `(min-width: ` + px.toString() + `px)`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function maxWidth(bp) {
|
|
49
|
+
let px = breakpointToPixels(bp) - 1 | 0;
|
|
50
|
+
return `(max-width: ` + px.toString() + `px)`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function between(lower, upper) {
|
|
54
|
+
let minPx = breakpointToPixels(lower);
|
|
55
|
+
let maxPx = breakpointToPixels(upper) - 1 | 0;
|
|
56
|
+
return `(min-width: ` + minPx.toString() + `px) and (max-width: ` + maxPx.toString() + `px)`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let xsUp = minWidth("Xs");
|
|
60
|
+
|
|
61
|
+
let smUp = minWidth("Sm");
|
|
62
|
+
|
|
63
|
+
let mdUp = minWidth("Md");
|
|
64
|
+
|
|
65
|
+
let lgUp = minWidth("Lg");
|
|
66
|
+
|
|
67
|
+
let xlUp = minWidth("Xl");
|
|
68
|
+
|
|
69
|
+
let xxlUp = minWidth("Xxl");
|
|
70
|
+
|
|
71
|
+
let xsDown = maxWidth("Xs");
|
|
72
|
+
|
|
73
|
+
let smDown = maxWidth("Sm");
|
|
74
|
+
|
|
75
|
+
let mdDown = maxWidth("Md");
|
|
76
|
+
|
|
77
|
+
let lgDown = maxWidth("Lg");
|
|
78
|
+
|
|
79
|
+
let xlDown = maxWidth("Xl");
|
|
80
|
+
|
|
81
|
+
let xxlDown = maxWidth("Xxl");
|
|
82
|
+
|
|
83
|
+
let xsOnly = maxWidth("Sm");
|
|
84
|
+
|
|
85
|
+
let smOnly = between("Sm", "Md");
|
|
86
|
+
|
|
87
|
+
let mdOnly = between("Md", "Lg");
|
|
88
|
+
|
|
89
|
+
let lgOnly = between("Lg", "Xl");
|
|
90
|
+
|
|
91
|
+
let xlOnly = between("Xl", "Xxl");
|
|
92
|
+
|
|
93
|
+
let xxlOnly = minWidth("Xxl");
|
|
94
|
+
|
|
95
|
+
let portrait = "(orientation: portrait)";
|
|
96
|
+
|
|
97
|
+
let landscape = "(orientation: landscape)";
|
|
98
|
+
|
|
99
|
+
let prefersReducedMotion = "(prefers-reduced-motion: reduce)";
|
|
100
|
+
|
|
101
|
+
let prefersDark = "(prefers-color-scheme: dark)";
|
|
102
|
+
|
|
103
|
+
let touchDevice = "(hover: none) and (pointer: coarse)";
|
|
104
|
+
|
|
105
|
+
let retina = "(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)";
|
|
106
|
+
|
|
107
|
+
let Query = {
|
|
108
|
+
xsUp: xsUp,
|
|
109
|
+
smUp: smUp,
|
|
110
|
+
mdUp: mdUp,
|
|
111
|
+
lgUp: lgUp,
|
|
112
|
+
xlUp: xlUp,
|
|
113
|
+
xxlUp: xxlUp,
|
|
114
|
+
xsDown: xsDown,
|
|
115
|
+
smDown: smDown,
|
|
116
|
+
mdDown: mdDown,
|
|
117
|
+
lgDown: lgDown,
|
|
118
|
+
xlDown: xlDown,
|
|
119
|
+
xxlDown: xxlDown,
|
|
120
|
+
xsOnly: xsOnly,
|
|
121
|
+
smOnly: smOnly,
|
|
122
|
+
mdOnly: mdOnly,
|
|
123
|
+
lgOnly: lgOnly,
|
|
124
|
+
xlOnly: xlOnly,
|
|
125
|
+
xxlOnly: xxlOnly,
|
|
126
|
+
portrait: portrait,
|
|
127
|
+
landscape: landscape,
|
|
128
|
+
prefersReducedMotion: prefersReducedMotion,
|
|
129
|
+
prefersDark: prefersDark,
|
|
130
|
+
prefersLight: "(prefers-color-scheme: light)",
|
|
131
|
+
highContrast: "(prefers-contrast: more)",
|
|
132
|
+
touchDevice: touchDevice,
|
|
133
|
+
finePointer: "(hover: hover) and (pointer: fine)",
|
|
134
|
+
retina: retina
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
let addChangeListener = (function(mql, cb) { mql.addEventListener("change", cb) });
|
|
138
|
+
|
|
139
|
+
let removeChangeListener = (function(mql, cb) { mql.removeEventListener("change", cb) });
|
|
140
|
+
|
|
141
|
+
function matchesQuery(query) {
|
|
142
|
+
return window.matchMedia(query).matches;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function matchesBreakpointUp(bp) {
|
|
146
|
+
let query = minWidth(bp);
|
|
147
|
+
return window.matchMedia(query).matches;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function matchesBreakpointDown(bp) {
|
|
151
|
+
let query = maxWidth(bp);
|
|
152
|
+
return window.matchMedia(query).matches;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function makeMediaSignal(query) {
|
|
156
|
+
let mql = window.matchMedia(query);
|
|
157
|
+
let signal = Xote.Signal.make(mql.matches, undefined, undefined);
|
|
158
|
+
let handler = evt => Xote.Signal.set(signal, evt.matches);
|
|
159
|
+
addChangeListener(mql, handler);
|
|
160
|
+
return signal;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function makeBreakpointSignal(bp) {
|
|
164
|
+
return makeMediaSignal(minWidth(bp));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function _memo(make) {
|
|
168
|
+
let cached = {
|
|
169
|
+
contents: undefined
|
|
170
|
+
};
|
|
171
|
+
return () => {
|
|
172
|
+
let s = cached.contents;
|
|
173
|
+
if (s !== undefined) {
|
|
174
|
+
return s;
|
|
175
|
+
}
|
|
176
|
+
let s$1 = make();
|
|
177
|
+
cached.contents = s$1;
|
|
178
|
+
return s$1;
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let isXsScreen = _memo(() => makeMediaSignal(xsDown));
|
|
183
|
+
|
|
184
|
+
let isSmScreen = _memo(() => makeMediaSignal(smOnly));
|
|
185
|
+
|
|
186
|
+
let isMdScreen = _memo(() => makeMediaSignal(mdOnly));
|
|
187
|
+
|
|
188
|
+
let isLgScreen = _memo(() => makeMediaSignal(lgOnly));
|
|
189
|
+
|
|
190
|
+
let isXlScreen = _memo(() => makeMediaSignal(xlOnly));
|
|
191
|
+
|
|
192
|
+
let isXxlScreen = _memo(() => makeMediaSignal(xxlOnly));
|
|
193
|
+
|
|
194
|
+
let isSmallAndUp = _memo(() => makeMediaSignal(smUp));
|
|
195
|
+
|
|
196
|
+
let isMediumAndUp = _memo(() => makeMediaSignal(mdUp));
|
|
197
|
+
|
|
198
|
+
let isLargeAndUp = _memo(() => makeMediaSignal(lgUp));
|
|
199
|
+
|
|
200
|
+
let isExtraLargeAndUp = _memo(() => makeMediaSignal(xlUp));
|
|
201
|
+
|
|
202
|
+
let isMobile = _memo(() => makeMediaSignal(smDown));
|
|
203
|
+
|
|
204
|
+
let isTablet = _memo(() => makeMediaSignal(mdOnly));
|
|
205
|
+
|
|
206
|
+
let isDesktop = _memo(() => makeMediaSignal(lgUp));
|
|
207
|
+
|
|
208
|
+
let isPortrait = _memo(() => makeMediaSignal(portrait));
|
|
209
|
+
|
|
210
|
+
let isLandscape = _memo(() => makeMediaSignal(landscape));
|
|
211
|
+
|
|
212
|
+
let prefersReducedMotion$1 = _memo(() => makeMediaSignal(prefersReducedMotion));
|
|
213
|
+
|
|
214
|
+
let prefersDarkMode = _memo(() => makeMediaSignal(prefersDark));
|
|
215
|
+
|
|
216
|
+
let isTouchDevice = _memo(() => makeMediaSignal(touchDevice));
|
|
217
|
+
|
|
218
|
+
let isRetina = _memo(() => makeMediaSignal(retina));
|
|
219
|
+
|
|
220
|
+
function currentBreakpointToString(bp) {
|
|
221
|
+
switch (bp) {
|
|
222
|
+
case "ExtraSmall" :
|
|
223
|
+
return "xs";
|
|
224
|
+
case "Small" :
|
|
225
|
+
return "sm";
|
|
226
|
+
case "Medium" :
|
|
227
|
+
return "md";
|
|
228
|
+
case "Large" :
|
|
229
|
+
return "lg";
|
|
230
|
+
case "ExtraLarge" :
|
|
231
|
+
return "xl";
|
|
232
|
+
case "ExtraExtraLarge" :
|
|
233
|
+
return "xxl";
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function getCurrentBreakpoint() {
|
|
238
|
+
if (window.matchMedia(xxlUp).matches) {
|
|
239
|
+
return "ExtraExtraLarge";
|
|
240
|
+
} else if (window.matchMedia(xlUp).matches) {
|
|
241
|
+
return "ExtraLarge";
|
|
242
|
+
} else if (window.matchMedia(lgUp).matches) {
|
|
243
|
+
return "Large";
|
|
244
|
+
} else if (window.matchMedia(mdUp).matches) {
|
|
245
|
+
return "Medium";
|
|
246
|
+
} else if (window.matchMedia(smUp).matches) {
|
|
247
|
+
return "Small";
|
|
248
|
+
} else {
|
|
249
|
+
return "ExtraSmall";
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function makeCurrentBreakpointSignal() {
|
|
254
|
+
let signal = Xote.Signal.make(getCurrentBreakpoint(), undefined, undefined);
|
|
255
|
+
let breakpoints = [
|
|
256
|
+
"Sm",
|
|
257
|
+
"Md",
|
|
258
|
+
"Lg",
|
|
259
|
+
"Xl",
|
|
260
|
+
"Xxl"
|
|
261
|
+
];
|
|
262
|
+
breakpoints.forEach(bp => {
|
|
263
|
+
let mql = window.matchMedia(minWidth(bp));
|
|
264
|
+
let _handler = _evt => Xote.Signal.set(signal, getCurrentBreakpoint());
|
|
265
|
+
addChangeListener(mql, _handler);
|
|
266
|
+
});
|
|
267
|
+
return signal;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let currentBreakpoint = {
|
|
271
|
+
contents: undefined
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
function getCurrentBreakpointSignal() {
|
|
275
|
+
let s = currentBreakpoint.contents;
|
|
276
|
+
if (s !== undefined) {
|
|
277
|
+
return s;
|
|
278
|
+
}
|
|
279
|
+
let s$1 = makeCurrentBreakpointSignal();
|
|
280
|
+
currentBreakpoint.contents = s$1;
|
|
281
|
+
return s$1;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function resolveResponsiveValue(value, fallback) {
|
|
285
|
+
let bp = getCurrentBreakpoint();
|
|
286
|
+
let ordered;
|
|
287
|
+
switch (bp) {
|
|
288
|
+
case "ExtraSmall" :
|
|
289
|
+
ordered = [value.xs];
|
|
290
|
+
break;
|
|
291
|
+
case "Small" :
|
|
292
|
+
ordered = [
|
|
293
|
+
value.sm,
|
|
294
|
+
value.xs
|
|
295
|
+
];
|
|
296
|
+
break;
|
|
297
|
+
case "Medium" :
|
|
298
|
+
ordered = [
|
|
299
|
+
value.md,
|
|
300
|
+
value.sm,
|
|
301
|
+
value.xs
|
|
302
|
+
];
|
|
303
|
+
break;
|
|
304
|
+
case "Large" :
|
|
305
|
+
ordered = [
|
|
306
|
+
value.lg,
|
|
307
|
+
value.md,
|
|
308
|
+
value.sm,
|
|
309
|
+
value.xs
|
|
310
|
+
];
|
|
311
|
+
break;
|
|
312
|
+
case "ExtraLarge" :
|
|
313
|
+
ordered = [
|
|
314
|
+
value.xl,
|
|
315
|
+
value.lg,
|
|
316
|
+
value.md,
|
|
317
|
+
value.sm,
|
|
318
|
+
value.xs
|
|
319
|
+
];
|
|
320
|
+
break;
|
|
321
|
+
case "ExtraExtraLarge" :
|
|
322
|
+
ordered = [
|
|
323
|
+
value.xxl,
|
|
324
|
+
value.xl,
|
|
325
|
+
value.lg,
|
|
326
|
+
value.md,
|
|
327
|
+
value.sm,
|
|
328
|
+
value.xs
|
|
329
|
+
];
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
let _idx = 0;
|
|
333
|
+
while (true) {
|
|
334
|
+
let idx = _idx;
|
|
335
|
+
if (idx >= ordered.length) {
|
|
336
|
+
return fallback;
|
|
337
|
+
}
|
|
338
|
+
let v = ordered[idx];
|
|
339
|
+
if (v !== undefined) {
|
|
340
|
+
return Primitive_option.valFromOption(v);
|
|
341
|
+
}
|
|
342
|
+
_idx = idx + 1 | 0;
|
|
343
|
+
continue;
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function hiddenBelow(bp) {
|
|
348
|
+
return `basefn-hidden-below-` + breakpointToString(bp);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function hiddenAbove(bp) {
|
|
352
|
+
return `basefn-hidden-above-` + breakpointToString(bp);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function visibleOnly(bp) {
|
|
356
|
+
return `basefn-visible-` + breakpointToString(bp) + `-only`;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
let Visibility = {
|
|
360
|
+
hiddenBelow: hiddenBelow,
|
|
361
|
+
hiddenAbove: hiddenAbove,
|
|
362
|
+
visibleOnly: visibleOnly
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
export {
|
|
366
|
+
breakpointToPixels,
|
|
367
|
+
breakpointToString,
|
|
368
|
+
minWidth,
|
|
369
|
+
maxWidth,
|
|
370
|
+
between,
|
|
371
|
+
Query,
|
|
372
|
+
addChangeListener,
|
|
373
|
+
removeChangeListener,
|
|
374
|
+
matchesQuery,
|
|
375
|
+
matchesBreakpointUp,
|
|
376
|
+
matchesBreakpointDown,
|
|
377
|
+
makeMediaSignal,
|
|
378
|
+
makeBreakpointSignal,
|
|
379
|
+
_memo,
|
|
380
|
+
isXsScreen,
|
|
381
|
+
isSmScreen,
|
|
382
|
+
isMdScreen,
|
|
383
|
+
isLgScreen,
|
|
384
|
+
isXlScreen,
|
|
385
|
+
isXxlScreen,
|
|
386
|
+
isSmallAndUp,
|
|
387
|
+
isMediumAndUp,
|
|
388
|
+
isLargeAndUp,
|
|
389
|
+
isExtraLargeAndUp,
|
|
390
|
+
isMobile,
|
|
391
|
+
isTablet,
|
|
392
|
+
isDesktop,
|
|
393
|
+
isPortrait,
|
|
394
|
+
isLandscape,
|
|
395
|
+
prefersReducedMotion$1 as prefersReducedMotion,
|
|
396
|
+
prefersDarkMode,
|
|
397
|
+
isTouchDevice,
|
|
398
|
+
isRetina,
|
|
399
|
+
currentBreakpointToString,
|
|
400
|
+
getCurrentBreakpoint,
|
|
401
|
+
makeCurrentBreakpointSignal,
|
|
402
|
+
currentBreakpoint,
|
|
403
|
+
getCurrentBreakpointSignal,
|
|
404
|
+
resolveResponsiveValue,
|
|
405
|
+
Visibility,
|
|
406
|
+
}
|
|
407
|
+
/* Not a pure module */
|
|
@@ -44,9 +44,14 @@ let make = (
|
|
|
44
44
|
baseClass ++ alignClass
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
let handleBackdropClick = _ => {
|
|
48
|
+
Signal.set(isOpen, false)
|
|
49
|
+
}
|
|
50
|
+
|
|
47
51
|
let menuContent = Computed.make(() => {
|
|
48
52
|
if Signal.get(isOpen) {
|
|
49
53
|
[
|
|
54
|
+
<div class="basefn-dropdown__backdrop" onClick={handleBackdropClick} />,
|
|
50
55
|
<div class={getMenuClass()}>
|
|
51
56
|
{items
|
|
52
57
|
->Array.mapWithIndex((item, index) => {
|
|
@@ -17,9 +17,15 @@ function Basefn__Dropdown(props) {
|
|
|
17
17
|
let alignClass = align === "right" ? " basefn-dropdown__menu--right" : "";
|
|
18
18
|
return "basefn-dropdown__menu" + alignClass;
|
|
19
19
|
};
|
|
20
|
+
let handleBackdropClick = param => Xote.Signal.set(isOpen, false);
|
|
20
21
|
let menuContent = Xote.Computed.make(() => {
|
|
21
22
|
if (Xote.Signal.get(isOpen)) {
|
|
22
|
-
return [
|
|
23
|
+
return [
|
|
24
|
+
Xote__JSX.Elements.jsx("div", {
|
|
25
|
+
class: "basefn-dropdown__backdrop",
|
|
26
|
+
onClick: handleBackdropClick
|
|
27
|
+
}),
|
|
28
|
+
Xote__JSX.Elements.jsx("div", {
|
|
23
29
|
class: getMenuClass(),
|
|
24
30
|
children: Xote.Component.fragment(items.map((item, index) => {
|
|
25
31
|
if (typeof item !== "object") {
|
|
@@ -50,7 +56,8 @@ function Basefn__Dropdown(props) {
|
|
|
50
56
|
children: Xote.Component.text(match.label)
|
|
51
57
|
}, index.toString(), undefined);
|
|
52
58
|
}))
|
|
53
|
-
})
|
|
59
|
+
})
|
|
60
|
+
];
|
|
54
61
|
} else {
|
|
55
62
|
return [];
|
|
56
63
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
Responsive Visibility Utilities
|
|
3
|
+
============================================================================ */
|
|
4
|
+
|
|
5
|
+
/* Hidden below breakpoint (hidden on screens smaller than) */
|
|
6
|
+
@media (max-width: 479px) {
|
|
7
|
+
.basefn-hidden-below-xs { display: none !important; }
|
|
8
|
+
}
|
|
9
|
+
@media (max-width: 639px) {
|
|
10
|
+
.basefn-hidden-below-sm { display: none !important; }
|
|
11
|
+
}
|
|
12
|
+
@media (max-width: 767px) {
|
|
13
|
+
.basefn-hidden-below-md { display: none !important; }
|
|
14
|
+
}
|
|
15
|
+
@media (max-width: 1023px) {
|
|
16
|
+
.basefn-hidden-below-lg { display: none !important; }
|
|
17
|
+
}
|
|
18
|
+
@media (max-width: 1279px) {
|
|
19
|
+
.basefn-hidden-below-xl { display: none !important; }
|
|
20
|
+
}
|
|
21
|
+
@media (max-width: 1535px) {
|
|
22
|
+
.basefn-hidden-below-xxl { display: none !important; }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Hidden above breakpoint (hidden on screens larger than) */
|
|
26
|
+
@media (min-width: 480px) {
|
|
27
|
+
.basefn-hidden-above-xs { display: none !important; }
|
|
28
|
+
}
|
|
29
|
+
@media (min-width: 640px) {
|
|
30
|
+
.basefn-hidden-above-sm { display: none !important; }
|
|
31
|
+
}
|
|
32
|
+
@media (min-width: 768px) {
|
|
33
|
+
.basefn-hidden-above-md { display: none !important; }
|
|
34
|
+
}
|
|
35
|
+
@media (min-width: 1024px) {
|
|
36
|
+
.basefn-hidden-above-lg { display: none !important; }
|
|
37
|
+
}
|
|
38
|
+
@media (min-width: 1280px) {
|
|
39
|
+
.basefn-hidden-above-xl { display: none !important; }
|
|
40
|
+
}
|
|
41
|
+
@media (min-width: 1536px) {
|
|
42
|
+
.basefn-hidden-above-xxl { display: none !important; }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Visible only at specific breakpoint */
|
|
46
|
+
.basefn-visible-xs-only {
|
|
47
|
+
display: none !important;
|
|
48
|
+
}
|
|
49
|
+
@media (max-width: 479px) {
|
|
50
|
+
.basefn-visible-xs-only { display: revert !important; }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.basefn-visible-sm-only {
|
|
54
|
+
display: none !important;
|
|
55
|
+
}
|
|
56
|
+
@media (min-width: 640px) and (max-width: 767px) {
|
|
57
|
+
.basefn-visible-sm-only { display: revert !important; }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.basefn-visible-md-only {
|
|
61
|
+
display: none !important;
|
|
62
|
+
}
|
|
63
|
+
@media (min-width: 768px) and (max-width: 1023px) {
|
|
64
|
+
.basefn-visible-md-only { display: revert !important; }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.basefn-visible-lg-only {
|
|
68
|
+
display: none !important;
|
|
69
|
+
}
|
|
70
|
+
@media (min-width: 1024px) and (max-width: 1279px) {
|
|
71
|
+
.basefn-visible-lg-only { display: revert !important; }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.basefn-visible-xl-only {
|
|
75
|
+
display: none !important;
|
|
76
|
+
}
|
|
77
|
+
@media (min-width: 1280px) and (max-width: 1535px) {
|
|
78
|
+
.basefn-visible-xl-only { display: revert !important; }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.basefn-visible-xxl-only {
|
|
82
|
+
display: none !important;
|
|
83
|
+
}
|
|
84
|
+
@media (min-width: 1536px) {
|
|
85
|
+
.basefn-visible-xxl-only { display: revert !important; }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Orientation-based visibility */
|
|
89
|
+
.basefn-hidden-portrait {
|
|
90
|
+
display: revert !important;
|
|
91
|
+
}
|
|
92
|
+
@media (orientation: portrait) {
|
|
93
|
+
.basefn-hidden-portrait { display: none !important; }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.basefn-hidden-landscape {
|
|
97
|
+
display: revert !important;
|
|
98
|
+
}
|
|
99
|
+
@media (orientation: landscape) {
|
|
100
|
+
.basefn-hidden-landscape { display: none !important; }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* Device-based visibility */
|
|
104
|
+
@media (hover: none) and (pointer: coarse) {
|
|
105
|
+
.basefn-hidden-touch { display: none !important; }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@media (hover: hover) and (pointer: fine) {
|
|
109
|
+
.basefn-hidden-desktop { display: none !important; }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* Print visibility */
|
|
113
|
+
@media print {
|
|
114
|
+
.basefn-hidden-print { display: none !important; }
|
|
115
|
+
}
|
|
116
|
+
@media not print {
|
|
117
|
+
.basefn-visible-print-only { display: none !important; }
|
|
118
|
+
}
|