@olenbetong/synergi-react 1.0.5 → 2.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/dist/esm/ob.react.css +216 -146
- package/dist/esm/ob.react.js +227 -131
- 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 +216 -146
- package/dist/iife/ob.react.js +230 -134
- 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 +8 -5
- package/es/ColorCard/index.d.ts +5 -2
- package/es/ColorCard/index.js +6 -0
- package/es/DateNavigator/index.css +1 -1
- package/es/DateNavigator/index.d.ts +3 -1
- package/es/DateNavigator/index.js +82 -28
- package/es/LinkedCardList/index.css +1 -1
- package/es/LinkedCardList/index.d.ts +2 -2
- package/es/LinkedCardList/index.js +2 -2
- package/es/PageBanner/index.js +2 -1
- package/es/Portal/index.js +5 -7
- package/es/ProgressBar/index.css +1 -1
- package/es/Sidebar/index.js +1 -0
- package/es/Spinner/index.d.ts +1 -1
- package/es/SplitContainer/index.d.ts +4 -2
- package/es/SplitContainer/index.js +45 -13
- 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/es/useTranslation/index.js +2 -1
- package/package.json +88 -90
- package/src/Checkbox/index.scss +12 -5
- package/src/Checkbox/index.tsx +25 -7
- package/src/ColorCard/{index.scss → index.css} +50 -59
- package/src/ColorCard/index.tsx +21 -9
- package/src/DateNavigator/index.scss +66 -33
- package/src/DateNavigator/index.tsx +98 -33
- package/src/LinkedCardList/index.scss +8 -1
- package/src/LinkedCardList/index.tsx +2 -2
- package/src/PageBanner/index.tsx +2 -1
- package/src/Portal/index.tsx +8 -7
- package/src/ProgressBar/index.scss +2 -2
- package/src/Sidebar/index.tsx +1 -0
- package/src/Spinner/{index.scss → index.css} +13 -10
- package/src/Spinner/index.tsx +2 -2
- package/src/SplitContainer/index.tsx +75 -17
- package/src/ValueToggleGroup/index.css +79 -0
- package/src/ValueToggleGroup/index.tsx +52 -0
- package/src/index.scss +3 -2
- package/src/index.ts +12 -12
- package/src/useTranslation/index.ts +2 -1
- package/LICENSE +0 -21
- package/es/ColorCard/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/ValueToggle/index.scss +0 -50
- package/src/ValueToggle/index.tsx +0 -75
|
@@ -3,58 +3,91 @@
|
|
|
3
3
|
$date-select-height: 3rem;
|
|
4
4
|
|
|
5
5
|
.ObDateNavigator-icon {
|
|
6
|
-
|
|
6
|
+
display: inline-block;
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
svg {
|
|
9
|
+
width: 0.625em;
|
|
10
|
+
position: relative;
|
|
11
|
+
top: -1px;
|
|
12
|
+
}
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
.ObDateNavigator-root {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
+
}
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
.ObDateNavigator-arrow {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
@include mixins.btn-reset;
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
flex: 0 0 $date-select-height;
|
|
33
|
+
justify-content: center;
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
height: $date-select-height;
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
text-align: center;
|
|
33
39
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
&:hover {
|
|
41
|
+
background-color: #e3e3e3;
|
|
42
|
+
background-color: var(--block-header-button-hover);
|
|
43
|
+
}
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
.ObDateNavigator-arrowLeft {
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
border-right: 1px solid #dddddd;
|
|
48
|
+
order: 1;
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
.ObDateNavigator-arrowRight {
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
border-left: 1px solid #dddddd;
|
|
53
|
+
order: 3;
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
.ObDateNavigator-date {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
display: flex;
|
|
58
|
+
flex: 1 1 100%;
|
|
59
|
+
flex-direction: column;
|
|
60
|
+
justify-content: center;
|
|
61
|
+
order: 2;
|
|
56
62
|
|
|
57
|
-
|
|
63
|
+
height: $date-select-height;
|
|
58
64
|
|
|
59
|
-
|
|
65
|
+
text-align: center;
|
|
60
66
|
}
|
|
67
|
+
|
|
68
|
+
.ObDateNavigator-today {
|
|
69
|
+
@include mixins.btn-reset;
|
|
70
|
+
order: 4;
|
|
71
|
+
padding: 0 1rem;
|
|
72
|
+
height: $date-select-height;
|
|
73
|
+
display: flex;
|
|
74
|
+
align-items: center;
|
|
75
|
+
justify-content: center;
|
|
76
|
+
font-size: 0.875rem;
|
|
77
|
+
color: var(--brand-dark, #336699);
|
|
78
|
+
text-transform: uppercase;
|
|
79
|
+
cursor: pointer;
|
|
80
|
+
|
|
81
|
+
text-wrap: nowrap;
|
|
82
|
+
|
|
83
|
+
border-left: 1px solid #dddddd;
|
|
84
|
+
|
|
85
|
+
&:hover {
|
|
86
|
+
background-color: var(--block-header-button-hover);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
&:focus-visible {
|
|
90
|
+
outline: 2px solid var(--brand-dark, #336699);
|
|
91
|
+
outline-offset: 2px;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -1,35 +1,36 @@
|
|
|
1
1
|
import "./index.scss";
|
|
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
18
|
"ObDateNavigator-arrow",
|
|
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,109 @@ 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 function DateNavigator({
|
|
55
|
+
export function DateNavigator({
|
|
56
|
+
date: dateProp,
|
|
57
|
+
onChange,
|
|
58
|
+
minDate,
|
|
59
|
+
maxDate,
|
|
60
|
+
}: {
|
|
61
|
+
date?: Date;
|
|
62
|
+
onChange?: (date: Date) => void;
|
|
63
|
+
minDate?: Date;
|
|
64
|
+
maxDate?: Date;
|
|
65
|
+
}) {
|
|
57
66
|
let [internalDate, setInternalDate] = useState(new Date());
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
let
|
|
61
|
-
|
|
62
|
-
let
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
let date = dateProp ?? internalDate;
|
|
68
|
+
let dateUtc = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate());
|
|
69
|
+
let today = new Date();
|
|
70
|
+
let todayUtc = Date.UTC(today.getFullYear(), today.getMonth(), today.getDate());
|
|
71
|
+
let liveRef = useRef<HTMLDivElement>(null);
|
|
72
|
+
|
|
73
|
+
let text = (() => {
|
|
74
|
+
let base = formatter.format(new Date(dateUtc));
|
|
75
|
+
if (todayUtc === dateUtc) return `${getLocalizedString("Today")}, ${base}`;
|
|
76
|
+
if (todayUtc === dateUtc + oneDay) return `${getLocalizedString("Yesterday")}, ${base}`;
|
|
77
|
+
if (todayUtc === dateUtc - oneDay) return `${getLocalizedString("Tomorrow")}, ${base}`;
|
|
78
|
+
return base;
|
|
79
|
+
})();
|
|
80
|
+
|
|
81
|
+
let disablePrev = !!minDate && dateUtc <= Date.UTC(minDate.getFullYear(), minDate.getMonth(), minDate.getDate());
|
|
82
|
+
let disableNext = !!maxDate && dateUtc >= Date.UTC(maxDate.getFullYear(), maxDate.getMonth(), maxDate.getDate());
|
|
83
|
+
|
|
84
|
+
function updateDate(newDate: Date) {
|
|
85
|
+
if (minDate && newDate < minDate) {
|
|
86
|
+
newDate = new Date(minDate); // Ensure we don't go below minDate
|
|
87
|
+
} else if (maxDate && newDate > maxDate) {
|
|
88
|
+
newDate = new Date(maxDate); // Ensure we don't go above maxDate
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (onChange) onChange(newDate);
|
|
92
|
+
else setInternalDate(newDate);
|
|
71
93
|
}
|
|
72
94
|
|
|
73
|
-
|
|
74
|
-
|
|
95
|
+
function jumpToToday() {
|
|
96
|
+
updateDate(new Date());
|
|
75
97
|
}
|
|
76
98
|
|
|
77
|
-
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (liveRef.current) {
|
|
101
|
+
liveRef.current.textContent = text;
|
|
102
|
+
}
|
|
103
|
+
}, [text]);
|
|
104
|
+
|
|
105
|
+
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
|
|
106
|
+
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
|
|
107
|
+
if (event.defaultPrevented) return;
|
|
108
|
+
if (event.altKey || event.metaKey) return; // Ignore if alt or meta key is pressed
|
|
109
|
+
|
|
110
|
+
if (disablePrev && event.key === "ArrowLeft") return;
|
|
111
|
+
if (disableNext && event.key === "ArrowRight") return;
|
|
112
|
+
|
|
113
|
+
if (event.ctrlKey && event.shiftKey) {
|
|
114
|
+
// If both ctrl and shift are pressed, navigate by 1 year
|
|
115
|
+
let newDate = new Date(dateUtc);
|
|
116
|
+
if (event.key === "ArrowLeft") {
|
|
117
|
+
newDate.setFullYear(newDate.getFullYear() - 1);
|
|
118
|
+
} else if (event.key === "ArrowRight") {
|
|
119
|
+
newDate.setFullYear(newDate.getFullYear() + 1);
|
|
120
|
+
}
|
|
121
|
+
updateDate(newDate);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (event.ctrlKey) {
|
|
126
|
+
// Navigate by month
|
|
127
|
+
let newDate = new Date(dateUtc);
|
|
128
|
+
if (event.key === "ArrowLeft") {
|
|
129
|
+
newDate.setMonth(newDate.getMonth() - 1);
|
|
130
|
+
} else if (event.key === "ArrowRight") {
|
|
131
|
+
newDate.setMonth(newDate.getMonth() + 1);
|
|
132
|
+
}
|
|
133
|
+
updateDate(newDate);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let modifier = event.shiftKey ? 7 : 1;
|
|
138
|
+
if (event.key === "ArrowLeft") {
|
|
139
|
+
updateDate(new Date(dateUtc - oneDay * modifier));
|
|
140
|
+
} else if (event.key === "ArrowRight") {
|
|
141
|
+
updateDate(new Date(dateUtc + oneDay * modifier));
|
|
82
142
|
}
|
|
83
143
|
}
|
|
84
144
|
|
|
85
145
|
return (
|
|
86
|
-
|
|
87
|
-
|
|
146
|
+
// biome-ignore lint/a11y/noNoninteractiveTabindex: I hate this component
|
|
147
|
+
<div className="ObDateNavigator-root" onKeyDown={handleKeyDown} tabIndex={0} aria-label="Date navigator">
|
|
148
|
+
<Arrow direction="left" onClick={() => updateDate(new Date(dateUtc - oneDay))} disabled={disablePrev} />
|
|
88
149
|
<div className="ObDateNavigator-date">{text}</div>
|
|
89
|
-
<Arrow direction="right" onClick={() =>
|
|
150
|
+
<Arrow direction="right" onClick={() => updateDate(new Date(dateUtc + oneDay))} disabled={disableNext} />
|
|
151
|
+
<button type="button" className="ObDateNavigator-today" onClick={jumpToToday}>
|
|
152
|
+
{getLocalizedString("Today")}
|
|
153
|
+
</button>
|
|
154
|
+
<div aria-live="polite" ref={liveRef} style={{ position: "absolute", left: "-9999px" }} />
|
|
90
155
|
</div>
|
|
91
156
|
);
|
|
92
157
|
}
|
|
@@ -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,
|
package/src/PageBanner/index.tsx
CHANGED
|
@@ -26,7 +26,8 @@ export const BannerImage = React.forwardRef<HTMLImageElement, BannerImageProps>(
|
|
|
26
26
|
ref,
|
|
27
27
|
) {
|
|
28
28
|
const { className, ...other } = props;
|
|
29
|
-
|
|
29
|
+
// biome-ignore lint/a11y/useAltText: should be provided by the user
|
|
30
|
+
return <img ref={ref} className={clsx("ObPageBanner-bannerImage", className)} alt="" {...other} />;
|
|
30
31
|
});
|
|
31
32
|
|
|
32
33
|
export type TabListProps = Pick<React.HTMLProps<HTMLDivElement>, "id" | "className" | "children"> & {
|
package/src/Portal/index.tsx
CHANGED
|
@@ -10,15 +10,16 @@ function useForceUpdate() {
|
|
|
10
10
|
export function Portal({ children, root, type = "div" }) {
|
|
11
11
|
const forceUpdate = useForceUpdate();
|
|
12
12
|
const mountNode = useRef<HTMLDivElement>(null);
|
|
13
|
-
const portalNode = useRef<HTMLElement>(null);
|
|
13
|
+
const portalNode = useRef<HTMLElement | null>(null);
|
|
14
14
|
|
|
15
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: because reasons
|
|
15
16
|
useEffect(() => {
|
|
16
17
|
const ownerDocument = mountNode.current?.ownerDocument;
|
|
17
|
-
|
|
18
|
-
if (ownerDocument && portalElem) {
|
|
18
|
+
if (ownerDocument && !portalNode.current) {
|
|
19
19
|
let parent = root ? ownerDocument.querySelector(root) || ownerDocument.body : ownerDocument.body;
|
|
20
|
+
|
|
20
21
|
const node = ownerDocument.createElement(type);
|
|
21
|
-
|
|
22
|
+
portalNode.current = node;
|
|
22
23
|
parent.appendChild(node);
|
|
23
24
|
forceUpdate();
|
|
24
25
|
|
|
@@ -26,11 +27,11 @@ export function Portal({ children, root, type = "div" }) {
|
|
|
26
27
|
parent.removeChild(node);
|
|
27
28
|
};
|
|
28
29
|
}
|
|
29
|
-
}, []);
|
|
30
|
+
}, [root, type]);
|
|
30
31
|
|
|
31
32
|
if (!portalNode.current) {
|
|
32
33
|
return <div ref={mountNode} />;
|
|
33
|
-
} else {
|
|
34
|
-
return createPortal(children, portalNode.current);
|
|
35
34
|
}
|
|
35
|
+
|
|
36
|
+
return createPortal(children, portalNode.current);
|
|
36
37
|
}
|
|
@@ -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%,
|
package/src/Sidebar/index.tsx
CHANGED
|
@@ -49,6 +49,7 @@ export function Sidebar(props: SidebarProps) {
|
|
|
49
49
|
return () => element?.removeEventListener("transitionend", toggleHiddenContent);
|
|
50
50
|
}, [open]);
|
|
51
51
|
|
|
52
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: TODO
|
|
52
53
|
useEffect(() => {
|
|
53
54
|
function handleCloseSidebar(evt) {
|
|
54
55
|
if (evt.key === "Escape") {
|
|
@@ -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
|
@@ -2,7 +2,8 @@ import "./index.scss";
|
|
|
2
2
|
|
|
3
3
|
import clsx from "clsx";
|
|
4
4
|
import localforage from "localforage";
|
|
5
|
-
import React
|
|
5
|
+
import type React from "react";
|
|
6
|
+
import { Children, forwardRef, useEffect, useRef, useState } from "react";
|
|
6
7
|
|
|
7
8
|
const article = globalThis.af?.article?.id ?? globalThis.window?.location.pathname.substring(1).split("/")[0];
|
|
8
9
|
const settingsStore = localforage.createInstance({
|
|
@@ -59,11 +60,24 @@ type DividerProps = {
|
|
|
59
60
|
className?: string;
|
|
60
61
|
direction: "horizontal" | "vertical";
|
|
61
62
|
isDragging: boolean;
|
|
63
|
+
min: number;
|
|
64
|
+
max: number;
|
|
65
|
+
size: number;
|
|
62
66
|
};
|
|
63
67
|
|
|
64
68
|
const Divider = forwardRef<HTMLDivElement, DividerProps>(function Divider(props: DividerProps, ref) {
|
|
65
69
|
return (
|
|
66
|
-
<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
|
+
>
|
|
67
81
|
<span>•••</span>
|
|
68
82
|
</div>
|
|
69
83
|
);
|
|
@@ -114,6 +128,8 @@ export type SplitContainerProps = {
|
|
|
114
128
|
direction?: "horizontal" | "vertical";
|
|
115
129
|
fixedPanel?: 1 | 2;
|
|
116
130
|
defaultFixedSize?: number;
|
|
131
|
+
min?: number;
|
|
132
|
+
max?: number;
|
|
117
133
|
/**
|
|
118
134
|
* When the fixed size is persisted, changing the layoutVersion will force clients
|
|
119
135
|
* to reset to the default value.
|
|
@@ -133,6 +149,8 @@ export function SplitContainer({
|
|
|
133
149
|
panelClassName,
|
|
134
150
|
defaultFixedSize = 400,
|
|
135
151
|
layoutVersion = 1,
|
|
152
|
+
min = 0,
|
|
153
|
+
max,
|
|
136
154
|
id,
|
|
137
155
|
}: SplitContainerProps) {
|
|
138
156
|
let [fixedSize, setFixedSize] = useState<number>(defaultFixedSize);
|
|
@@ -143,18 +161,23 @@ export function SplitContainer({
|
|
|
143
161
|
throw Error("SplitContainer requires exactly 2 children");
|
|
144
162
|
}
|
|
145
163
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
let newValue: number = typeof size === "function" ? size(current) : size;
|
|
164
|
+
let containerRef = useRef<HTMLDivElement>(null);
|
|
165
|
+
let [containerSize, setContainerSize] = useState<number>(0);
|
|
149
166
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
persistState(key, layoutVersion, newValue);
|
|
153
|
-
}
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
if (!containerRef.current) return;
|
|
154
169
|
|
|
155
|
-
|
|
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
|
+
}
|
|
156
176
|
});
|
|
157
|
-
|
|
177
|
+
|
|
178
|
+
resizeObserver.observe(containerRef.current);
|
|
179
|
+
return () => resizeObserver.disconnect();
|
|
180
|
+
}, [direction]);
|
|
158
181
|
|
|
159
182
|
useEffect(() => {
|
|
160
183
|
if (id) {
|
|
@@ -186,6 +209,17 @@ export function SplitContainer({
|
|
|
186
209
|
fixedPanel === 1 ? divider.previousElementSibling : divider.nextElementSibling
|
|
187
210
|
) as HTMLElement;
|
|
188
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
|
+
|
|
189
223
|
function getNewSize(event: PointerEvent) {
|
|
190
224
|
let deltaSize =
|
|
191
225
|
direction === "horizontal"
|
|
@@ -194,7 +228,20 @@ export function SplitContainer({
|
|
|
194
228
|
|
|
195
229
|
deltaSize *= fixedPanel === 1 ? -1 : 1;
|
|
196
230
|
|
|
197
|
-
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
|
+
});
|
|
198
245
|
}
|
|
199
246
|
|
|
200
247
|
function handlePointerUp(event: PointerEvent) {
|
|
@@ -210,6 +257,10 @@ export function SplitContainer({
|
|
|
210
257
|
event.preventDefault();
|
|
211
258
|
|
|
212
259
|
let newSize = getNewSize(event);
|
|
260
|
+
let divider = dividerRef.current;
|
|
261
|
+
if (divider) {
|
|
262
|
+
divider.setAttribute("aria-valuenow", String(newSize));
|
|
263
|
+
}
|
|
213
264
|
movingPanel.style.flex = `0 0 ${newSize}px`;
|
|
214
265
|
}
|
|
215
266
|
|
|
@@ -229,8 +280,8 @@ export function SplitContainer({
|
|
|
229
280
|
}
|
|
230
281
|
|
|
231
282
|
function handleKeyDown(event: KeyboardEvent) {
|
|
232
|
-
let reduce
|
|
233
|
-
let shouldHandle
|
|
283
|
+
let reduce = true;
|
|
284
|
+
let shouldHandle = false;
|
|
234
285
|
if (!event.altKey && !event.ctrlKey && !event.metaKey) {
|
|
235
286
|
if (direction === "horizontal" && ["ArrowLeft", "ArrowRight"].includes(event.key)) {
|
|
236
287
|
shouldHandle = true;
|
|
@@ -260,14 +311,21 @@ export function SplitContainer({
|
|
|
260
311
|
divider?.removeEventListener("keydown", handleKeyDown);
|
|
261
312
|
};
|
|
262
313
|
}
|
|
263
|
-
}, [direction, fixedPanel]);
|
|
314
|
+
}, [direction, fixedPanel, layoutVersion, id, min, max, containerSize]);
|
|
264
315
|
|
|
265
316
|
return (
|
|
266
|
-
<SplitRoot direction={direction} className={className}>
|
|
317
|
+
<SplitRoot direction={direction} className={className} ref={containerRef}>
|
|
267
318
|
<SplitPanel className={clsx(isDragging && "dragging", panelClassName)} fixed={fixedPanel === 1} size={fixedSize}>
|
|
268
319
|
{childrenArray[0]}
|
|
269
320
|
</SplitPanel>
|
|
270
|
-
<Divider
|
|
321
|
+
<Divider
|
|
322
|
+
ref={dividerRef}
|
|
323
|
+
direction={direction}
|
|
324
|
+
isDragging={isDragging}
|
|
325
|
+
size={fixedSize}
|
|
326
|
+
min={min}
|
|
327
|
+
max={max ?? containerSize}
|
|
328
|
+
/>
|
|
271
329
|
<SplitPanel className={clsx(isDragging && "dragging", panelClassName)} fixed={fixedPanel === 2} size={fixedSize}>
|
|
272
330
|
{childrenArray[1]}
|
|
273
331
|
</SplitPanel>
|