@olenbetong/synergi-react 1.0.6 → 2.1.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/dist/esm/ob.react.css +221 -174
- package/dist/esm/ob.react.js +223 -104
- package/dist/esm/ob.react.min.css +1 -1
- package/dist/esm/ob.react.min.css.map +3 -3
- package/dist/esm/ob.react.min.js +1 -1
- package/dist/esm/ob.react.min.js.map +4 -4
- package/dist/iife/ob.react.css +221 -174
- package/dist/iife/ob.react.js +223 -104
- package/dist/iife/ob.react.min.css +1 -1
- package/dist/iife/ob.react.min.css.map +3 -3
- package/dist/iife/ob.react.min.js +1 -1
- package/dist/iife/ob.react.min.js.map +4 -4
- package/dist/styles/styles.css +1 -1
- package/dist/styles/styles.css.map +1 -1
- package/es/Checkbox/index.css +1 -1
- package/es/Checkbox/index.d.ts +11 -0
- package/es/Checkbox/index.js +7 -5
- package/es/ColorCard/index.d.ts +5 -2
- package/es/ColorCard/index.js +6 -0
- package/es/DateNavigator/index.d.ts +8 -3
- package/es/DateNavigator/index.js +99 -27
- package/es/LinkedCardList/index.css +1 -1
- package/es/LinkedCardList/index.d.ts +2 -2
- package/es/LinkedCardList/index.js +2 -2
- package/es/ProgressBar/index.css +1 -1
- package/es/SplitContainer/index.d.ts +3 -1
- package/es/SplitContainer/index.js +45 -14
- package/es/ValueToggleGroup/index.d.ts +18 -0
- package/es/ValueToggleGroup/index.js +12 -0
- package/es/index.css +1 -1
- package/es/index.d.ts +8 -8
- package/es/index.js +3 -3
- package/package.json +88 -90
- package/src/Checkbox/index.scss +12 -5
- package/src/Checkbox/index.tsx +23 -6
- package/src/ColorCard/{index.scss → index.css} +50 -59
- package/src/ColorCard/index.tsx +21 -9
- package/src/DateNavigator/index.css +96 -0
- package/src/DateNavigator/index.tsx +128 -36
- package/src/LinkedCardList/index.scss +8 -1
- package/src/LinkedCardList/index.tsx +2 -2
- package/src/ProgressBar/index.scss +2 -2
- package/src/Spinner/{index.scss → index.css} +13 -10
- package/src/Spinner/index.tsx +1 -1
- package/src/SplitContainer/index.tsx +71 -15
- package/src/ValueToggleGroup/index.css +79 -0
- package/src/ValueToggleGroup/index.tsx +52 -0
- package/src/index.scss +4 -3
- package/src/index.ts +12 -12
- package/LICENSE +0 -21
- package/es/ColorCard/index.css +0 -1
- package/es/DateNavigator/index.css +0 -1
- package/es/Spinner/index.css +0 -1
- package/es/ValueToggle/index.css +0 -1
- package/es/ValueToggle/index.d.ts +0 -18
- package/es/ValueToggle/index.js +0 -38
- package/src/DateNavigator/index.scss +0 -60
- package/src/ValueToggle/index.scss +0 -50
- package/src/ValueToggle/index.tsx +0 -75
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--ob-datenavigator-height: 3rem;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.ObDateNavigator-icon {
|
|
6
|
+
display: inline-block;
|
|
7
|
+
|
|
8
|
+
svg {
|
|
9
|
+
width: 0.625em;
|
|
10
|
+
position: relative;
|
|
11
|
+
top: -1px;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.ObDateNavigator-root {
|
|
16
|
+
background-color: hsl(220, 20%, 96%);
|
|
17
|
+
background-color: var(--block-header-color-bg);
|
|
18
|
+
display: flex;
|
|
19
|
+
font-size: 1rem;
|
|
20
|
+
outline: none;
|
|
21
|
+
|
|
22
|
+
&:focus-visible {
|
|
23
|
+
outline: 2px solid var(--brand-dark, #336699);
|
|
24
|
+
outline-offset: 2px;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.ObDateNavigator-arrow {
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
flex: 0 0 var(--ob-datenavigator-height);
|
|
32
|
+
justify-content: center;
|
|
33
|
+
|
|
34
|
+
height: var(--ob-datenavigator-height);
|
|
35
|
+
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
text-align: center;
|
|
38
|
+
|
|
39
|
+
&:hover:not(:disabled) {
|
|
40
|
+
background-color: #e3e3e3;
|
|
41
|
+
background-color: var(--block-header-button-hover);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
&:disabled {
|
|
45
|
+
cursor: not-allowed;
|
|
46
|
+
color: rgba(0 0 0 / 0.2);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.ObDateNavigator-arrowLeft {
|
|
51
|
+
border-right: 1px solid #dddddd;
|
|
52
|
+
order: 1;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.ObDateNavigator-arrowRight {
|
|
56
|
+
border-left: 1px solid #dddddd;
|
|
57
|
+
order: 3;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.ObDateNavigator-date {
|
|
61
|
+
display: flex;
|
|
62
|
+
flex: 1 1 100%;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
justify-content: center;
|
|
65
|
+
order: 2;
|
|
66
|
+
|
|
67
|
+
height: var(--ob-datenavigator-height);
|
|
68
|
+
|
|
69
|
+
text-align: center;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.ObDateNavigator-today {
|
|
73
|
+
order: 4;
|
|
74
|
+
padding: 0 1rem;
|
|
75
|
+
height: var(--ob-datenavigator-height);
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
justify-content: center;
|
|
79
|
+
font-size: 0.875rem;
|
|
80
|
+
color: var(--brand-dark, #336699);
|
|
81
|
+
text-transform: uppercase;
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
|
|
84
|
+
text-wrap: nowrap;
|
|
85
|
+
|
|
86
|
+
border-left: 1px solid #dddddd;
|
|
87
|
+
|
|
88
|
+
&:hover:not(:disabled) {
|
|
89
|
+
background-color: var(--block-header-button-hover);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
&:focus-visible {
|
|
93
|
+
outline: 2px solid var(--brand-dark, #336699);
|
|
94
|
+
outline-offset: 2px;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -1,35 +1,36 @@
|
|
|
1
|
-
import "./index.
|
|
1
|
+
import "./index.css";
|
|
2
2
|
|
|
3
3
|
import { getLocalizedString } from "@olenbetong/appframe-core";
|
|
4
|
-
|
|
5
4
|
import clsx from "clsx";
|
|
6
|
-
import { useState } from "react";
|
|
5
|
+
import { useEffect, useRef, useState } from "react";
|
|
7
6
|
|
|
8
7
|
function getUiCulture() {
|
|
9
8
|
return af?.userSession?.culture ?? "default";
|
|
10
9
|
}
|
|
11
10
|
|
|
12
|
-
function Arrow(
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
function Arrow({
|
|
12
|
+
direction,
|
|
13
|
+
...rest
|
|
14
|
+
}: { direction: "left" | "right" } & React.ButtonHTMLAttributes<HTMLButtonElement>) {
|
|
15
15
|
return (
|
|
16
16
|
<button
|
|
17
17
|
className={clsx(
|
|
18
|
-
"ObDateNavigator-arrow",
|
|
18
|
+
"ObDateNavigator-arrow btn-reset",
|
|
19
19
|
direction === "left" ? "ObDateNavigator-arrowLeft" : "ObDateNavigator-arrowRight",
|
|
20
20
|
)}
|
|
21
|
+
aria-label={direction === "left" ? getLocalizedString("Previous day") : getLocalizedString("Next day")}
|
|
21
22
|
{...rest}
|
|
22
23
|
>
|
|
23
24
|
<span className="ObDateNavigator-icon">
|
|
24
25
|
{direction === "left" ? (
|
|
25
|
-
<svg aria-hidden="true"
|
|
26
|
+
<svg aria-hidden="true" viewBox="0 0 320 512">
|
|
26
27
|
<path
|
|
27
28
|
fill="currentColor"
|
|
28
29
|
d="M34.52 239.03L228.87 44.69c9.37-9.37 24.57-9.37 33.94 0l22.67 22.67c9.36 9.36 9.37 24.52.04 33.9L131.49 256l154.02 154.75c9.34 9.38 9.32 24.54-.04 33.9l-22.67 22.67c-9.37 9.37-24.57 9.37-33.94 0L34.52 272.97c-9.37-9.37-9.37-24.57 0-33.94z"
|
|
29
30
|
/>
|
|
30
31
|
</svg>
|
|
31
32
|
) : (
|
|
32
|
-
<svg aria-hidden="true"
|
|
33
|
+
<svg aria-hidden="true" viewBox="0 0 320 512">
|
|
33
34
|
<path
|
|
34
35
|
fill="currentColor"
|
|
35
36
|
d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"
|
|
@@ -48,45 +49,136 @@ const formatter = new Intl.DateTimeFormat(getUiCulture(), {
|
|
|
48
49
|
year: "numeric",
|
|
49
50
|
timeZone: "utc",
|
|
50
51
|
});
|
|
51
|
-
const today = new Date();
|
|
52
|
-
const todayUtc = Date.UTC(today.getFullYear(), today.getMonth(), today.getDate());
|
|
53
52
|
|
|
54
53
|
const oneDay = 24 * 60 * 60 * 1000;
|
|
55
54
|
|
|
56
|
-
export
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
55
|
+
export type DateNavigatorProps = {
|
|
56
|
+
date?: Date | string | number;
|
|
57
|
+
defaultDate?: Date | string | number; // For default uncontrolled value
|
|
58
|
+
disableTodayButton?: boolean;
|
|
59
|
+
onChange?: (date: Date) => void;
|
|
60
|
+
minDate?: Date | string | number;
|
|
61
|
+
maxDate?: Date | string | number;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
function sanitizeDate(date: Date | string | number): Date {
|
|
65
|
+
if (typeof date === "string" || typeof date === "number") {
|
|
66
|
+
let parsedDate = new Date(date);
|
|
67
|
+
if (Number.isNaN(parsedDate.getTime())) {
|
|
68
|
+
throw new Error(`Invalid date value: ${date}`);
|
|
69
|
+
}
|
|
70
|
+
return parsedDate;
|
|
71
|
+
} else if (date instanceof Date) {
|
|
72
|
+
if (Number.isNaN(date.getTime())) {
|
|
73
|
+
throw new Error(`Invalid date object: ${date}`);
|
|
74
|
+
}
|
|
75
|
+
return new Date(date.getTime()); // Return a copy of the date
|
|
76
|
+
} else {
|
|
77
|
+
throw new Error(`Unsupported date type: ${typeof date}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function DateNavigator({
|
|
82
|
+
date: dateProp,
|
|
83
|
+
defaultDate,
|
|
84
|
+
disableTodayButton,
|
|
85
|
+
onChange,
|
|
86
|
+
minDate: minDateProp,
|
|
87
|
+
maxDate: maxDateProp,
|
|
88
|
+
}: DateNavigatorProps) {
|
|
89
|
+
let [internalDate, setInternalDate] = useState(() => (defaultDate ? sanitizeDate(defaultDate) : new Date()));
|
|
90
|
+
let date = dateProp ? sanitizeDate(dateProp) : internalDate;
|
|
91
|
+
let dateUtc = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate());
|
|
92
|
+
let today = new Date();
|
|
93
|
+
let todayUtc = Date.UTC(today.getFullYear(), today.getMonth(), today.getDate());
|
|
94
|
+
let liveRef = useRef<HTMLDivElement>(null);
|
|
95
|
+
|
|
96
|
+
let text = (() => {
|
|
97
|
+
let base = formatter.format(new Date(dateUtc));
|
|
98
|
+
if (todayUtc === dateUtc) return `${getLocalizedString("Today")}, ${base}`;
|
|
99
|
+
if (todayUtc === dateUtc + oneDay) return `${getLocalizedString("Yesterday")}, ${base}`;
|
|
100
|
+
if (todayUtc === dateUtc - oneDay) return `${getLocalizedString("Tomorrow")}, ${base}`;
|
|
101
|
+
return base;
|
|
102
|
+
})();
|
|
103
|
+
|
|
104
|
+
let minDate = minDateProp && sanitizeDate(minDateProp);
|
|
105
|
+
let maxDate = maxDateProp && sanitizeDate(maxDateProp);
|
|
106
|
+
let disablePrev = !!minDate && dateUtc <= Date.UTC(minDate.getFullYear(), minDate.getMonth(), minDate.getDate());
|
|
107
|
+
let disableNext = !!maxDate && dateUtc >= Date.UTC(maxDate.getFullYear(), maxDate.getMonth(), maxDate.getDate());
|
|
108
|
+
|
|
109
|
+
function updateDate(newDate: Date) {
|
|
110
|
+
if (minDate && newDate < minDate) {
|
|
111
|
+
newDate = new Date(minDate); // Ensure we don't go below minDate
|
|
112
|
+
} else if (maxDate && newDate > maxDate) {
|
|
113
|
+
newDate = new Date(maxDate); // Ensure we don't go above maxDate
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
onChange?.(newDate);
|
|
117
|
+
setInternalDate(newDate);
|
|
71
118
|
}
|
|
72
119
|
|
|
73
|
-
|
|
74
|
-
|
|
120
|
+
function jumpToToday() {
|
|
121
|
+
updateDate(new Date());
|
|
75
122
|
}
|
|
76
123
|
|
|
77
|
-
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (liveRef.current) {
|
|
126
|
+
liveRef.current.textContent = text;
|
|
127
|
+
}
|
|
128
|
+
}, [text]);
|
|
129
|
+
|
|
130
|
+
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
|
|
131
|
+
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
|
|
132
|
+
if (event.defaultPrevented) return;
|
|
133
|
+
if (event.altKey || event.metaKey) return; // Ignore if alt or meta key is pressed
|
|
134
|
+
|
|
135
|
+
if (disablePrev && event.key === "ArrowLeft") return;
|
|
136
|
+
if (disableNext && event.key === "ArrowRight") return;
|
|
137
|
+
|
|
138
|
+
if (event.ctrlKey && event.shiftKey) {
|
|
139
|
+
// If both ctrl and shift are pressed, navigate by 1 year
|
|
140
|
+
let newDate = new Date(dateUtc);
|
|
141
|
+
if (event.key === "ArrowLeft") {
|
|
142
|
+
newDate.setFullYear(newDate.getFullYear() - 1);
|
|
143
|
+
} else if (event.key === "ArrowRight") {
|
|
144
|
+
newDate.setFullYear(newDate.getFullYear() + 1);
|
|
145
|
+
}
|
|
146
|
+
updateDate(newDate);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (event.ctrlKey) {
|
|
151
|
+
// Navigate by month
|
|
152
|
+
let newDate = new Date(dateUtc);
|
|
153
|
+
if (event.key === "ArrowLeft") {
|
|
154
|
+
newDate.setMonth(newDate.getMonth() - 1);
|
|
155
|
+
} else if (event.key === "ArrowRight") {
|
|
156
|
+
newDate.setMonth(newDate.getMonth() + 1);
|
|
157
|
+
}
|
|
158
|
+
updateDate(newDate);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let modifier = event.shiftKey ? 7 : 1;
|
|
163
|
+
if (event.key === "ArrowLeft") {
|
|
164
|
+
updateDate(new Date(dateUtc - oneDay * modifier));
|
|
165
|
+
} else if (event.key === "ArrowRight") {
|
|
166
|
+
updateDate(new Date(dateUtc + oneDay * modifier));
|
|
82
167
|
}
|
|
83
168
|
}
|
|
84
169
|
|
|
85
170
|
return (
|
|
86
|
-
|
|
87
|
-
|
|
171
|
+
// biome-ignore lint/a11y/noNoninteractiveTabindex: I hate this component
|
|
172
|
+
<div className="ObDateNavigator-root" onKeyDown={handleKeyDown} tabIndex={0} aria-label="Date navigator">
|
|
173
|
+
<Arrow direction="left" onClick={() => updateDate(new Date(dateUtc - oneDay))} disabled={disablePrev} />
|
|
88
174
|
<div className="ObDateNavigator-date">{text}</div>
|
|
89
|
-
<Arrow direction="right" onClick={() =>
|
|
175
|
+
<Arrow direction="right" onClick={() => updateDate(new Date(dateUtc + oneDay))} disabled={disableNext} />
|
|
176
|
+
{!disableTodayButton && (
|
|
177
|
+
<button type="button" className="ObDateNavigator-today btn-reset" onClick={jumpToToday}>
|
|
178
|
+
{getLocalizedString("Today")}
|
|
179
|
+
</button>
|
|
180
|
+
)}
|
|
181
|
+
<div aria-live="polite" ref={liveRef} style={{ position: "absolute", left: "-9999px" }} />
|
|
90
182
|
</div>
|
|
91
183
|
);
|
|
92
184
|
}
|
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
$bullet-size: 0.7em;
|
|
4
4
|
$line-width: 0.2em;
|
|
5
5
|
|
|
6
|
+
:root {
|
|
7
|
+
--ob-linked-list-bullet-size: 0.7em;
|
|
8
|
+
--ob-linked-list-line-width: 0.2em;
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
.ObLinkedList-root {
|
|
12
|
+
|
|
7
13
|
list-style-type: none;
|
|
8
14
|
margin-left: 0;
|
|
9
15
|
padding-left: 0;
|
|
@@ -47,10 +53,11 @@ $line-width: 0.2em;
|
|
|
47
53
|
left: 0;
|
|
48
54
|
top: 50%;
|
|
49
55
|
|
|
50
|
-
transform: translate(calc(-1 * (
|
|
56
|
+
transform: translate(calc(-1 * (var(--ob-linked-list-bullet-size) / 2) + (var(--ob-linked-list-line-width) / 2)), -50%);
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
.ObLinkedList-card {
|
|
60
|
+
display: block;
|
|
54
61
|
background: white;
|
|
55
62
|
border: 1px solid;
|
|
56
63
|
border-color: variables.$color-border-top variables.$color-border-side variables.$color-border-bottom;
|
|
@@ -2,7 +2,7 @@ import "./index.scss";
|
|
|
2
2
|
|
|
3
3
|
import clsx from "clsx";
|
|
4
4
|
|
|
5
|
-
export function
|
|
5
|
+
export function LinkedList({ children, className, ...other }: React.HTMLProps<HTMLUListElement>) {
|
|
6
6
|
return (
|
|
7
7
|
<ul className={clsx("ObLinkedList-root", className)} {...other}>
|
|
8
8
|
{children}
|
|
@@ -10,7 +10,7 @@ export function List({ children, className, ...other }: React.HTMLProps<HTMLULis
|
|
|
10
10
|
);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export function
|
|
13
|
+
export function LinkedListItem({
|
|
14
14
|
children,
|
|
15
15
|
className,
|
|
16
16
|
label,
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
transparent 66%
|
|
29
29
|
),
|
|
30
30
|
linear-gradient(top, rgba(255, 255, 255, 0.25), rgba(0, 0, 0, 0.2)),
|
|
31
|
-
linear-gradient(left, var(--brand-primary
|
|
31
|
+
linear-gradient(left, var(--brand-primary), var(--brand-light));
|
|
32
32
|
background-image:
|
|
33
33
|
-webkit-linear-gradient(
|
|
34
34
|
135deg,
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
transparent 66%
|
|
40
40
|
),
|
|
41
41
|
-webkit-linear-gradient(top, rgba(255, 255, 255, 0.25), rgba(0, 0, 0, 0.2)),
|
|
42
|
-
-webkit-linear-gradient(left, var(--brand-primary),
|
|
42
|
+
-webkit-linear-gradient(left, var(--brand-primary), var(--brand-light));
|
|
43
43
|
background-size:
|
|
44
44
|
35px 20px,
|
|
45
45
|
100% 100%,
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
|
+
.ObSpinner-wrapper, .ObSpinner-fullPage {
|
|
3
|
+
--bullet-size: 0.5em;
|
|
4
|
+
}
|
|
2
5
|
|
|
3
6
|
.ObSpinner-root {
|
|
4
7
|
display: inline-block;
|
|
@@ -15,38 +18,38 @@ $bullet-size: 0.5em;
|
|
|
15
18
|
|
|
16
19
|
.ObSpinner-spinner {
|
|
17
20
|
display: inline-block;
|
|
18
|
-
height:
|
|
21
|
+
height: calc(var(--bullet-size) * 1.5);
|
|
19
22
|
position: relative;
|
|
20
|
-
width:
|
|
23
|
+
width: calc(var(--bullet-size) * 5);
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
.ObSpinner-bullet {
|
|
24
27
|
animation-timing-function: cubic-bezier(0, 1, 1, 0);
|
|
25
28
|
background: #666;
|
|
26
29
|
border-radius: 50%;
|
|
27
|
-
height:
|
|
30
|
+
height: var(--bullet-size);
|
|
28
31
|
position: absolute;
|
|
29
32
|
top: 0.2rem;
|
|
30
|
-
width:
|
|
33
|
+
width: var(--bullet-size);
|
|
31
34
|
|
|
32
35
|
&:nth-child(1) {
|
|
33
36
|
animation: scaleAnimation 0.6s infinite ease-in-out;
|
|
34
|
-
left:
|
|
37
|
+
left: calc(var(--bullet-size) * 0.5);
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
&:nth-child(2) {
|
|
38
41
|
animation: translateAnimation 0.6s infinite ease-in-out;
|
|
39
|
-
left:
|
|
42
|
+
left: calc(var(--bullet-size) * 0.5);
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
&:nth-child(3) {
|
|
43
46
|
animation: translateAnimation 0.6s infinite ease-in-out;
|
|
44
|
-
left:
|
|
47
|
+
left: calc(var(--bullet-size) * 2.4);
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
&:nth-child(4) {
|
|
48
51
|
animation: scaleAnimation 0.6s infinite ease-in-out reverse;
|
|
49
|
-
left:
|
|
52
|
+
left: calc(var(--bullet-size) * 4);
|
|
50
53
|
}
|
|
51
54
|
}
|
|
52
55
|
|
|
@@ -66,6 +69,6 @@ $bullet-size: 0.5em;
|
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
to {
|
|
69
|
-
transform: translate(
|
|
72
|
+
transform: translate(calc(var(--bullet-size) * 1.65), 0);
|
|
70
73
|
}
|
|
71
74
|
}
|
package/src/Spinner/index.tsx
CHANGED
|
@@ -60,11 +60,24 @@ type DividerProps = {
|
|
|
60
60
|
className?: string;
|
|
61
61
|
direction: "horizontal" | "vertical";
|
|
62
62
|
isDragging: boolean;
|
|
63
|
+
min: number;
|
|
64
|
+
max: number;
|
|
65
|
+
size: number;
|
|
63
66
|
};
|
|
64
67
|
|
|
65
68
|
const Divider = forwardRef<HTMLDivElement, DividerProps>(function Divider(props: DividerProps, ref) {
|
|
66
69
|
return (
|
|
67
|
-
<div
|
|
70
|
+
<div
|
|
71
|
+
className={clsx("ObSplitContainer-divider", props.className)}
|
|
72
|
+
tabIndex={0}
|
|
73
|
+
ref={ref}
|
|
74
|
+
// biome-ignore lint/a11y/useSemanticElements: this is allowed according to WAI-ARIA
|
|
75
|
+
role="separator"
|
|
76
|
+
aria-orientation={props.direction}
|
|
77
|
+
aria-valuemin={props.min}
|
|
78
|
+
aria-valuemax={props.max}
|
|
79
|
+
aria-valuenow={props.size}
|
|
80
|
+
>
|
|
68
81
|
<span>•••</span>
|
|
69
82
|
</div>
|
|
70
83
|
);
|
|
@@ -115,6 +128,8 @@ export type SplitContainerProps = {
|
|
|
115
128
|
direction?: "horizontal" | "vertical";
|
|
116
129
|
fixedPanel?: 1 | 2;
|
|
117
130
|
defaultFixedSize?: number;
|
|
131
|
+
min?: number;
|
|
132
|
+
max?: number;
|
|
118
133
|
/**
|
|
119
134
|
* When the fixed size is persisted, changing the layoutVersion will force clients
|
|
120
135
|
* to reset to the default value.
|
|
@@ -134,6 +149,8 @@ export function SplitContainer({
|
|
|
134
149
|
panelClassName,
|
|
135
150
|
defaultFixedSize = 400,
|
|
136
151
|
layoutVersion = 1,
|
|
152
|
+
min = 0,
|
|
153
|
+
max,
|
|
137
154
|
id,
|
|
138
155
|
}: SplitContainerProps) {
|
|
139
156
|
let [fixedSize, setFixedSize] = useState<number>(defaultFixedSize);
|
|
@@ -144,18 +161,23 @@ export function SplitContainer({
|
|
|
144
161
|
throw Error("SplitContainer requires exactly 2 children");
|
|
145
162
|
}
|
|
146
163
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
let newValue: number = typeof size === "function" ? size(current) : size;
|
|
164
|
+
let containerRef = useRef<HTMLDivElement>(null);
|
|
165
|
+
let [containerSize, setContainerSize] = useState<number>(0);
|
|
150
166
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
persistState(key, layoutVersion, newValue);
|
|
154
|
-
}
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
if (!containerRef.current) return;
|
|
155
169
|
|
|
156
|
-
|
|
170
|
+
let resizeObserver = new ResizeObserver((entries) => {
|
|
171
|
+
for (let entry of entries) {
|
|
172
|
+
if (entry.contentRect) {
|
|
173
|
+
setContainerSize(direction === "horizontal" ? entry.contentRect.width : entry.contentRect.height);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
157
176
|
});
|
|
158
|
-
|
|
177
|
+
|
|
178
|
+
resizeObserver.observe(containerRef.current);
|
|
179
|
+
return () => resizeObserver.disconnect();
|
|
180
|
+
}, [direction]);
|
|
159
181
|
|
|
160
182
|
useEffect(() => {
|
|
161
183
|
if (id) {
|
|
@@ -175,7 +197,6 @@ export function SplitContainer({
|
|
|
175
197
|
|
|
176
198
|
let childrenArray = Children.toArray(children);
|
|
177
199
|
|
|
178
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
|
|
179
200
|
useEffect(() => {
|
|
180
201
|
let divider = dividerRef.current;
|
|
181
202
|
if (divider) {
|
|
@@ -188,6 +209,17 @@ export function SplitContainer({
|
|
|
188
209
|
fixedPanel === 1 ? divider.previousElementSibling : divider.nextElementSibling
|
|
189
210
|
) as HTMLElement;
|
|
190
211
|
|
|
212
|
+
function clampSize(size: number) {
|
|
213
|
+
let adjustedContainerSize = containerSize - 10; // make sure divider will not be stuck outside the container
|
|
214
|
+
let maxSize = Math.min(max ?? adjustedContainerSize, adjustedContainerSize);
|
|
215
|
+
if (size < min) {
|
|
216
|
+
size = min;
|
|
217
|
+
} else if (size > maxSize) {
|
|
218
|
+
size = maxSize;
|
|
219
|
+
}
|
|
220
|
+
return size;
|
|
221
|
+
}
|
|
222
|
+
|
|
191
223
|
function getNewSize(event: PointerEvent) {
|
|
192
224
|
let deltaSize =
|
|
193
225
|
direction === "horizontal"
|
|
@@ -196,7 +228,20 @@ export function SplitContainer({
|
|
|
196
228
|
|
|
197
229
|
deltaSize *= fixedPanel === 1 ? -1 : 1;
|
|
198
230
|
|
|
199
|
-
return initialPosition.size - deltaSize;
|
|
231
|
+
return clampSize(initialPosition.size - deltaSize);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function handleFixedSizeChanged(size: React.SetStateAction<number>) {
|
|
235
|
+
setFixedSize((current) => {
|
|
236
|
+
let newValue: number = clampSize(typeof size === "function" ? size(current) : size);
|
|
237
|
+
|
|
238
|
+
if (id) {
|
|
239
|
+
let key = `${article}.${id}`;
|
|
240
|
+
persistState(key, layoutVersion, newValue);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return newValue;
|
|
244
|
+
});
|
|
200
245
|
}
|
|
201
246
|
|
|
202
247
|
function handlePointerUp(event: PointerEvent) {
|
|
@@ -212,6 +257,10 @@ export function SplitContainer({
|
|
|
212
257
|
event.preventDefault();
|
|
213
258
|
|
|
214
259
|
let newSize = getNewSize(event);
|
|
260
|
+
let divider = dividerRef.current;
|
|
261
|
+
if (divider) {
|
|
262
|
+
divider.setAttribute("aria-valuenow", String(newSize));
|
|
263
|
+
}
|
|
215
264
|
movingPanel.style.flex = `0 0 ${newSize}px`;
|
|
216
265
|
}
|
|
217
266
|
|
|
@@ -262,14 +311,21 @@ export function SplitContainer({
|
|
|
262
311
|
divider?.removeEventListener("keydown", handleKeyDown);
|
|
263
312
|
};
|
|
264
313
|
}
|
|
265
|
-
}, [direction, fixedPanel]);
|
|
314
|
+
}, [direction, fixedPanel, layoutVersion, id, min, max, containerSize]);
|
|
266
315
|
|
|
267
316
|
return (
|
|
268
|
-
<SplitRoot direction={direction} className={className}>
|
|
317
|
+
<SplitRoot direction={direction} className={className} ref={containerRef}>
|
|
269
318
|
<SplitPanel className={clsx(isDragging && "dragging", panelClassName)} fixed={fixedPanel === 1} size={fixedSize}>
|
|
270
319
|
{childrenArray[0]}
|
|
271
320
|
</SplitPanel>
|
|
272
|
-
<Divider
|
|
321
|
+
<Divider
|
|
322
|
+
ref={dividerRef}
|
|
323
|
+
direction={direction}
|
|
324
|
+
isDragging={isDragging}
|
|
325
|
+
size={fixedSize}
|
|
326
|
+
min={min}
|
|
327
|
+
max={max ?? containerSize}
|
|
328
|
+
/>
|
|
273
329
|
<SplitPanel className={clsx(isDragging && "dragging", panelClassName)} fixed={fixedPanel === 2} size={fixedSize}>
|
|
274
330
|
{childrenArray[1]}
|
|
275
331
|
</SplitPanel>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
.ObValueToggleGroup {
|
|
2
|
+
padding: 0.25rem;
|
|
3
|
+
border: 2px solid rgb(0 0 0 / 0.2);
|
|
4
|
+
border-radius: 4px;
|
|
5
|
+
|
|
6
|
+
display: flex;
|
|
7
|
+
gap: 0.25rem;
|
|
8
|
+
flex-wrap: wrap;
|
|
9
|
+
|
|
10
|
+
&.vertical {
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
align-items: center;
|
|
13
|
+
|
|
14
|
+
& .ObValueToggleOption {
|
|
15
|
+
width: 100%;
|
|
16
|
+
|
|
17
|
+
& .ObValueToggleLabel {
|
|
18
|
+
width: 100%;
|
|
19
|
+
text-align: center;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
&:focus-within {
|
|
25
|
+
border-color: rgb(0 0 0 / 0.8);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.ObValueToggleOption {
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
margin: 0;
|
|
33
|
+
position: relative;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.ObValueToggleOption input {
|
|
37
|
+
position: absolute;
|
|
38
|
+
width: 1px;
|
|
39
|
+
height: 1px;
|
|
40
|
+
padding: 0;
|
|
41
|
+
margin: -1px;
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
clip: rect(0, 0, 0, 0);
|
|
44
|
+
border: 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.ObValueToggleLabel {
|
|
48
|
+
font-size: 0.8em;
|
|
49
|
+
margin: 0 0.2em;
|
|
50
|
+
padding: 0.3em 0.5em;
|
|
51
|
+
text-transform: uppercase;
|
|
52
|
+
transition: background 0.2s ease, color 0.2s ease;
|
|
53
|
+
border-radius: 4px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.ObValueToggleOption:hover input:not(:disabled) + .ObValueToggleLabel {
|
|
57
|
+
cursor: pointer;
|
|
58
|
+
background: #ccc;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.ObValueToggleOption input:checked + .ObValueToggleLabel {
|
|
62
|
+
--color-bg: var(--brand-dark);
|
|
63
|
+
background-color: var(--color-bg);
|
|
64
|
+
color: white;
|
|
65
|
+
cursor: default;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.ObValueToggleOption input:disabled + .ObValueToggleLabel {
|
|
69
|
+
color: rgb(0 0 0 / 0.5);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.ObValueToggleOption input:checked:focus-visible + .ObValueToggleLabel {
|
|
73
|
+
--color-bg: var(--brand-light);
|
|
74
|
+
outline: 2px solid var(--brand-light);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.ObValueToggleOption input:checked:hover + .ObValueToggleLabel {
|
|
78
|
+
background-color: var(--color-bg);
|
|
79
|
+
}
|