@olenbetong/synergi-react 0.6.7
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 +708 -0
- package/dist/esm/ob.react.js +3077 -0
- package/dist/esm/ob.react.min.css +2 -0
- package/dist/esm/ob.react.min.css.map +7 -0
- package/dist/esm/ob.react.min.js +8 -0
- package/dist/esm/ob.react.min.js.map +7 -0
- package/dist/iife/ob.react.css +708 -0
- package/dist/iife/ob.react.js +3089 -0
- package/dist/iife/ob.react.min.css +2 -0
- package/dist/iife/ob.react.min.css.map +7 -0
- package/dist/iife/ob.react.min.js +8 -0
- package/dist/iife/ob.react.min.js.map +7 -0
- package/dist/styles/styles.css +1 -0
- package/dist/styles/styles.css.map +1 -0
- package/es/Checkbox/index.css +1 -0
- package/es/ColorCard/index.css +1 -0
- package/es/DateNavigator/index.css +1 -0
- package/es/LinkedCardList/index.css +1 -0
- package/es/PageBanner/index.css +1 -0
- package/es/ProgressBar/index.css +1 -0
- package/es/Sidebar/index.css +1 -0
- package/es/Spinner/index.css +1 -0
- package/es/SplitContainer/index.css +1 -0
- package/es/ValueToggle/index.css +1 -0
- package/es/appframe-react/src/useDebounce.d.ts +8 -0
- package/es/appframe-react/src/useDebounce.js +16 -0
- package/es/index.css +1 -0
- package/es/styles/_mixins.css +0 -0
- package/es/styles/_variables.css +0 -0
- package/es/synergi-react/src/Checkbox/index.d.ts +9 -0
- package/es/synergi-react/src/Checkbox/index.js +41 -0
- package/es/synergi-react/src/ColorCard/index.d.ts +14 -0
- package/es/synergi-react/src/ColorCard/index.js +16 -0
- package/es/synergi-react/src/DateNavigator/index.d.ts +6 -0
- package/es/synergi-react/src/DateNavigator/index.js +51 -0
- package/es/synergi-react/src/LinkedCardList/index.d.ts +9 -0
- package/es/synergi-react/src/LinkedCardList/index.js +13 -0
- package/es/synergi-react/src/PageBanner/index.d.ts +27 -0
- package/es/synergi-react/src/PageBanner/index.js +24 -0
- package/es/synergi-react/src/Portal/index.d.ts +6 -0
- package/es/synergi-react/src/Portal/index.js +32 -0
- package/es/synergi-react/src/ProgressBar/index.d.ts +4 -0
- package/es/synergi-react/src/ProgressBar/index.js +7 -0
- package/es/synergi-react/src/Sidebar/index.d.ts +10 -0
- package/es/synergi-react/src/Sidebar/index.js +57 -0
- package/es/synergi-react/src/Spinner/index.d.ts +10 -0
- package/es/synergi-react/src/Spinner/index.js +6 -0
- package/es/synergi-react/src/SplitContainer/index.d.ts +20 -0
- package/es/synergi-react/src/SplitContainer/index.js +151 -0
- package/es/synergi-react/src/ValueToggle/index.d.ts +19 -0
- package/es/synergi-react/src/ValueToggle/index.js +38 -0
- package/es/synergi-react/src/index.d.ts +15 -0
- package/es/synergi-react/src/index.js +15 -0
- package/es/synergi-react/src/useDebounce.d.ts +9 -0
- package/es/synergi-react/src/useDebounce.js +17 -0
- package/es/synergi-react/src/usePersistedSetting.d.ts +7 -0
- package/es/synergi-react/src/usePersistedSetting.js +53 -0
- package/es/synergi-react/src/useSearchParamState.d.ts +13 -0
- package/es/synergi-react/src/useSearchParamState.js +42 -0
- package/es/synergi-react/src/useTranslation/index.d.ts +7 -0
- package/es/synergi-react/src/useTranslation/index.js +64 -0
- package/package.json +90 -0
- package/src/Checkbox/index.scss +119 -0
- package/src/Checkbox/index.tsx +81 -0
- package/src/ColorCard/index.scss +154 -0
- package/src/ColorCard/index.tsx +58 -0
- package/src/DateNavigator/index.scss +60 -0
- package/src/DateNavigator/index.tsx +92 -0
- package/src/LinkedCardList/index.scss +86 -0
- package/src/LinkedCardList/index.tsx +38 -0
- package/src/PageBanner/index.scss +99 -0
- package/src/PageBanner/index.tsx +79 -0
- package/src/Portal/index.tsx +36 -0
- package/src/ProgressBar/index.scss +86 -0
- package/src/ProgressBar/index.tsx +13 -0
- package/src/Sidebar/index.scss +53 -0
- package/src/Sidebar/index.tsx +117 -0
- package/src/Spinner/index.scss +71 -0
- package/src/Spinner/index.tsx +21 -0
- package/src/SplitContainer/index.scss +43 -0
- package/src/SplitContainer/index.tsx +271 -0
- package/src/ValueToggle/index.scss +50 -0
- package/src/ValueToggle/index.tsx +75 -0
- package/src/global.d.ts +2 -0
- package/src/index.scss +9 -0
- package/src/index.ts +27 -0
- package/src/styles/_mixins.scss +19 -0
- package/src/styles/_variables.scss +70 -0
- package/src/useDebounce.ts +21 -0
- package/src/usePersistedSetting.ts +78 -0
- package/src/useSearchParamState.ts +78 -0
- package/src/useTranslation/index.ts +74 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import "./index.css";
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import localforage from "localforage";
|
|
5
|
+
import { Children, forwardRef, useEffect, useRef, useState } from "react";
|
|
6
|
+
const settingsStore = localforage.createInstance({
|
|
7
|
+
name: `AfSplitContainers.${af.userSession.login}`,
|
|
8
|
+
});
|
|
9
|
+
const settingsCache = {};
|
|
10
|
+
async function persistState(key, layoutVersion, fixedSize) {
|
|
11
|
+
if (fixedSize != null) {
|
|
12
|
+
let settings = {
|
|
13
|
+
layoutVersion,
|
|
14
|
+
fixedSize,
|
|
15
|
+
};
|
|
16
|
+
settingsCache[key] = settings;
|
|
17
|
+
await settingsStore.setItem(key, settings);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function getSettings(key, layoutVersion) {
|
|
21
|
+
let settings = settingsCache[key] ?? (await settingsStore.getItem(key));
|
|
22
|
+
if (settings && settings.layoutVersion === layoutVersion) {
|
|
23
|
+
return settings.fixedSize;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
// Load all article grid layouts even if the grid is not mounted yet.
|
|
28
|
+
// This ways the persisted state can be restored faster after the initial
|
|
29
|
+
// page load.
|
|
30
|
+
settingsStore.keys().then((keys) => Promise.allSettled(keys
|
|
31
|
+
.filter((key) => key.startsWith(af.article.id))
|
|
32
|
+
.map((key) => settingsStore.getItem(key).then((settings) => {
|
|
33
|
+
if (settings && !settingsCache[key]) {
|
|
34
|
+
settingsCache[key] = settings;
|
|
35
|
+
}
|
|
36
|
+
}))));
|
|
37
|
+
const Divider = forwardRef(function Divider(props, ref) {
|
|
38
|
+
return _jsx("div", { tabIndex: 0, className: clsx("ObSplitContainer-divider", props.className), ref: ref });
|
|
39
|
+
});
|
|
40
|
+
function SplitRoot({ className, direction, ...props }) {
|
|
41
|
+
return _jsx("div", { className: clsx(className, "ObSplitContainer-root", direction), ...props });
|
|
42
|
+
}
|
|
43
|
+
function SplitPanel(props) {
|
|
44
|
+
let style = {
|
|
45
|
+
flex: props.fixed ? `0 0 ${Math.floor(props.size)}px` : "1 1 100%",
|
|
46
|
+
...props.style,
|
|
47
|
+
};
|
|
48
|
+
return (_jsx("div", { className: clsx(props.className, "ObSplitContainer-panel"), style: style, children: props.children }));
|
|
49
|
+
}
|
|
50
|
+
export function SplitContainer({ className, children, direction = "vertical", fixedPanel = 1, panelClassName, defaultFixedSize = 400, layoutVersion = 1, id, }) {
|
|
51
|
+
let [fixedSize, setFixedSize] = useState(defaultFixedSize);
|
|
52
|
+
let [isDragging, setIsDragging] = useState(false);
|
|
53
|
+
let dividerRef = useRef(null);
|
|
54
|
+
if (Children.count(children) !== 2) {
|
|
55
|
+
throw Error("SplitContainer requires exactly 2 children");
|
|
56
|
+
}
|
|
57
|
+
function handleFixedSizeChanged(size) {
|
|
58
|
+
setFixedSize((current) => {
|
|
59
|
+
let newValue = typeof size === "function" ? size(current) : size;
|
|
60
|
+
if (id) {
|
|
61
|
+
let key = `${af.article.id}.${id}`;
|
|
62
|
+
persistState(key, layoutVersion, newValue);
|
|
63
|
+
}
|
|
64
|
+
return newValue;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (id) {
|
|
69
|
+
let key = `${af.article.id}.${id}`;
|
|
70
|
+
getSettings(key, layoutVersion).then((data) => {
|
|
71
|
+
if (data) {
|
|
72
|
+
// Persisted state exists, and matches the current layout version,
|
|
73
|
+
// so restore the state.
|
|
74
|
+
setFixedSize(data);
|
|
75
|
+
return Promise.resolve();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}, [id, layoutVersion]);
|
|
80
|
+
let childrenArray = Children.toArray(children);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
let divider = dividerRef.current;
|
|
83
|
+
if (divider) {
|
|
84
|
+
let initialPosition = {
|
|
85
|
+
clientX: 0,
|
|
86
|
+
clientY: 0,
|
|
87
|
+
size: 0,
|
|
88
|
+
};
|
|
89
|
+
let movingPanel = (fixedPanel === 1 ? divider.previousElementSibling : divider.nextElementSibling);
|
|
90
|
+
function getNewSize(event) {
|
|
91
|
+
let deltaSize = direction === "horizontal"
|
|
92
|
+
? event.clientX - initialPosition.clientX
|
|
93
|
+
: event.clientY - initialPosition.clientY;
|
|
94
|
+
deltaSize *= fixedPanel === 1 ? -1 : 1;
|
|
95
|
+
return initialPosition.size - deltaSize;
|
|
96
|
+
}
|
|
97
|
+
function handlePointerUp(event) {
|
|
98
|
+
let newSize = getNewSize(event);
|
|
99
|
+
handleFixedSizeChanged(newSize);
|
|
100
|
+
setIsDragging(false);
|
|
101
|
+
movingPanel.style.flex = `0 0 ${Math.floor(newSize)}px`;
|
|
102
|
+
window.removeEventListener("pointermove", handlePointerMove);
|
|
103
|
+
}
|
|
104
|
+
function handlePointerMove(event) {
|
|
105
|
+
event.preventDefault();
|
|
106
|
+
let newSize = getNewSize(event);
|
|
107
|
+
movingPanel.style.flex = `0 0 ${newSize}px`;
|
|
108
|
+
}
|
|
109
|
+
function handlePointerDown(event) {
|
|
110
|
+
setIsDragging(true);
|
|
111
|
+
let rect = movingPanel.getBoundingClientRect();
|
|
112
|
+
let size = direction === "horizontal" ? rect.width : rect.height;
|
|
113
|
+
initialPosition = {
|
|
114
|
+
clientX: event.clientX,
|
|
115
|
+
clientY: event.clientY,
|
|
116
|
+
size,
|
|
117
|
+
};
|
|
118
|
+
window.addEventListener("pointermove", handlePointerMove);
|
|
119
|
+
window.addEventListener("pointerup", handlePointerUp, { once: true });
|
|
120
|
+
}
|
|
121
|
+
function handleKeyDown(event) {
|
|
122
|
+
let reduce = true;
|
|
123
|
+
let shouldHandle = false;
|
|
124
|
+
if (!event.altKey && !event.ctrlKey && !event.metaKey) {
|
|
125
|
+
if (direction === "horizontal" && ["ArrowLeft", "ArrowRight"].includes(event.key)) {
|
|
126
|
+
shouldHandle = true;
|
|
127
|
+
reduce =
|
|
128
|
+
(fixedPanel === 1 && event.key === "ArrowLeft") || (fixedPanel === 2 && event.key === "ArrowRight");
|
|
129
|
+
}
|
|
130
|
+
else if (direction === "vertical" && ["ArrowUp", "ArrowDown"].includes(event.key)) {
|
|
131
|
+
shouldHandle = true;
|
|
132
|
+
reduce = (fixedPanel === 1 && event.key === "ArrowUp") || (fixedPanel === 2 && event.key === "ArrowDown");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (shouldHandle) {
|
|
136
|
+
event.preventDefault();
|
|
137
|
+
event.stopPropagation();
|
|
138
|
+
let delta = event.shiftKey ? 25 : 5;
|
|
139
|
+
handleFixedSizeChanged((size) => size + (reduce ? -delta : delta));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
divider.addEventListener("pointerdown", handlePointerDown);
|
|
143
|
+
divider.addEventListener("keydown", handleKeyDown);
|
|
144
|
+
return () => {
|
|
145
|
+
divider?.removeEventListener("pointerdown", handlePointerDown);
|
|
146
|
+
divider?.removeEventListener("keydown", handleKeyDown);
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}, [direction, fixedPanel]);
|
|
150
|
+
return (_jsxs(SplitRoot, { direction: direction, className: className, children: [_jsx(SplitPanel, { className: panelClassName, fixed: fixedPanel === 1, size: fixedSize, children: childrenArray[0] }), _jsx(Divider, { ref: dividerRef, direction: direction, isDragging: isDragging }), _jsx(SplitPanel, { className: panelClassName, fixed: fixedPanel === 2, size: fixedSize, children: childrenArray[1] })] }));
|
|
151
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import "./index.css";
|
|
3
|
+
export declare type ValueToggleProps = {
|
|
4
|
+
className?: string;
|
|
5
|
+
onChange?: (value: string | number) => void;
|
|
6
|
+
value?: string | number;
|
|
7
|
+
defaultValue?: string | number;
|
|
8
|
+
id?: string;
|
|
9
|
+
options: Array<string | {
|
|
10
|
+
value: string | number;
|
|
11
|
+
label?: string;
|
|
12
|
+
}>;
|
|
13
|
+
style?: React.CSSProperties;
|
|
14
|
+
};
|
|
15
|
+
export declare const ValueToggle: import("react").ForwardRefExoticComponent<ValueToggleProps & import("react").RefAttributes<{
|
|
16
|
+
focus: () => void;
|
|
17
|
+
blur: () => void;
|
|
18
|
+
value: string | number;
|
|
19
|
+
}>>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import "./index.css";
|
|
3
|
+
import { forwardRef, useImperativeHandle, useRef, useState } from "react";
|
|
4
|
+
let valueToggleId = 0;
|
|
5
|
+
function ValueToggleInner({ className, onChange, defaultValue, options, value, ...other }, ref) {
|
|
6
|
+
const wrapperRef = useRef(null);
|
|
7
|
+
const [toggleName] = useState("obet-value-toggle-" + String(++valueToggleId));
|
|
8
|
+
const isControlled = value !== undefined;
|
|
9
|
+
useImperativeHandle(ref, () => {
|
|
10
|
+
const e = wrapperRef.current?.querySelector("input");
|
|
11
|
+
return {
|
|
12
|
+
focus: () => e?.focus(),
|
|
13
|
+
blur: () => e?.blur(),
|
|
14
|
+
value: (e?.querySelector("input:checked")).value,
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
return (_jsx("fieldset", { className: className, ...other, children: _jsx("div", { className: "ObValueToggle-root", ref: wrapperRef, children: options.map((option) => {
|
|
18
|
+
const isObj = typeof option === "object";
|
|
19
|
+
const optValue = isObj ? option.value : option;
|
|
20
|
+
const props = {
|
|
21
|
+
value: optValue,
|
|
22
|
+
};
|
|
23
|
+
if (isControlled) {
|
|
24
|
+
props.checked = value === optValue;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
props.defaultChecked = defaultValue === optValue;
|
|
28
|
+
}
|
|
29
|
+
if (typeof onChange === "function") {
|
|
30
|
+
props.onChange = () => onChange(optValue);
|
|
31
|
+
}
|
|
32
|
+
if (isObj) {
|
|
33
|
+
Object.assign(props, option);
|
|
34
|
+
}
|
|
35
|
+
return (_jsxs("label", { className: "ObValueToggle-option", children: [_jsx("input", { type: "radio", className: "ObValueToggle-input", name: toggleName, ...props }), _jsx("span", { className: "ObValueToggle-label", children: isObj ? option.label || optValue : optValue })] }, optValue));
|
|
36
|
+
}) }) }));
|
|
37
|
+
}
|
|
38
|
+
export const ValueToggle = forwardRef(ValueToggleInner);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { Checkbox, CheckboxProps } from "./Checkbox/index.js";
|
|
2
|
+
export { ColorCard, CardColumn, CardDetail, CardLabel } from "./ColorCard/index.js";
|
|
3
|
+
export { DateNavigator } from "./DateNavigator/index.js";
|
|
4
|
+
export { Card, List as LinkedList, ListItem as LinkedListItem } from "./LinkedCardList/index.js";
|
|
5
|
+
export { BannerImage as PageBannerImage, BannerImageProps, PageBanner, PageBannerProps, Tab as PageBannerTab, TabProps as PageBannerTabProps, TabList as PageBannerTabList, TabListProps as PageBannerTabListProps, Title as PageBannerTitle, TitleProps as PageBannerTitleProps, } from "./PageBanner/index.js";
|
|
6
|
+
export { Portal } from "./Portal/index.js";
|
|
7
|
+
export { ProgressBar } from "./ProgressBar/index.js";
|
|
8
|
+
export { Sidebar, SidebarProps } from "./Sidebar/index.js";
|
|
9
|
+
export { Spinner, SpinnerProps } from "./Spinner/index.js";
|
|
10
|
+
export { SplitContainer, SplitContainerProps } from "./SplitContainer/index.js";
|
|
11
|
+
export { useTranslation } from "./useTranslation/index.js";
|
|
12
|
+
export { ValueToggle, ValueToggleProps } from "./ValueToggle/index.js";
|
|
13
|
+
export { useDebounce } from "../../appframe-react/src/useDebounce.js";
|
|
14
|
+
export { usePersistedSetting } from "./usePersistedSetting.js";
|
|
15
|
+
export { useSearchParamState } from "./useSearchParamState.js";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { Checkbox } from "./Checkbox/index.js";
|
|
2
|
+
export { ColorCard, CardColumn, CardDetail, CardLabel } from "./ColorCard/index.js";
|
|
3
|
+
export { DateNavigator } from "./DateNavigator/index.js";
|
|
4
|
+
export { Card, List as LinkedList, ListItem as LinkedListItem } from "./LinkedCardList/index.js";
|
|
5
|
+
export { BannerImage as PageBannerImage, PageBanner, Tab as PageBannerTab, TabList as PageBannerTabList, Title as PageBannerTitle, } from "./PageBanner/index.js";
|
|
6
|
+
export { Portal } from "./Portal/index.js";
|
|
7
|
+
export { ProgressBar } from "./ProgressBar/index.js";
|
|
8
|
+
export { Sidebar } from "./Sidebar/index.js";
|
|
9
|
+
export { Spinner } from "./Spinner/index.js";
|
|
10
|
+
export { SplitContainer } from "./SplitContainer/index.js";
|
|
11
|
+
export { useTranslation } from "./useTranslation/index.js";
|
|
12
|
+
export { ValueToggle } from "./ValueToggle/index.js";
|
|
13
|
+
export { useDebounce } from "../../appframe-react/src/useDebounce.js";
|
|
14
|
+
export { usePersistedSetting } from "./usePersistedSetting.js";
|
|
15
|
+
export { useSearchParamState } from "./useSearchParamState.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Returns a debounced version of the given value.
|
|
4
|
+
*
|
|
5
|
+
* @param value
|
|
6
|
+
* @param delay
|
|
7
|
+
* @returns
|
|
8
|
+
* @deprecated Use equivalent hook in @olenbetong/appframe-react instead
|
|
9
|
+
*/
|
|
10
|
+
export function useDebounce(value, delay = 300) {
|
|
11
|
+
let [debouncedValue, setValue] = useState(value);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
let timeout = setTimeout(() => setValue(value), delay);
|
|
14
|
+
return () => clearTimeout(timeout);
|
|
15
|
+
}, [delay, value]);
|
|
16
|
+
return debouncedValue;
|
|
17
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On mount, load the persisted state from Indexed DB, and if the persisted
|
|
3
|
+
* layout version matches, restore the layout. Then listen to changes to the
|
|
4
|
+
* state, and store the latest version in Indexed DB.
|
|
5
|
+
* @deprecated Use equivalent hook in @olenbetong/appframe-react instead
|
|
6
|
+
*/
|
|
7
|
+
export declare function usePersistedSetting<T = unknown>(key: string, defaultValue: T): [T, (newValue: T) => void];
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import mitt from "mitt";
|
|
2
|
+
import localforage from "localforage";
|
|
3
|
+
import { useSyncExternalStore } from "react";
|
|
4
|
+
const settingsStore = localforage.createInstance({
|
|
5
|
+
name: `AfUserSettings.${af.userSession.login}`,
|
|
6
|
+
});
|
|
7
|
+
class SettingsCache {
|
|
8
|
+
#mitt;
|
|
9
|
+
#cache = {};
|
|
10
|
+
constructor() {
|
|
11
|
+
this.#mitt = mitt();
|
|
12
|
+
// Load all article user settings even if the setting is not needed yet.
|
|
13
|
+
// This ways the persisted state can be restored faster after the initial
|
|
14
|
+
// page load.'
|
|
15
|
+
settingsStore.keys().then((keys) => Promise.allSettled(keys
|
|
16
|
+
.filter((key) => key.startsWith(af.article.id))
|
|
17
|
+
.map((storeKey) => settingsStore.getItem(storeKey).then((storedValue) => {
|
|
18
|
+
let key = storeKey.replace(`${af.article.id}.`, "");
|
|
19
|
+
if (storedValue !== undefined && this.#cache[key] === undefined) {
|
|
20
|
+
this.setItem(key, storedValue);
|
|
21
|
+
}
|
|
22
|
+
}))));
|
|
23
|
+
}
|
|
24
|
+
setItem(key, value) {
|
|
25
|
+
this.#cache[key] = value;
|
|
26
|
+
this.#mitt.emit("change", { key, value });
|
|
27
|
+
let storeKey = `${af.article.id}.${key}`;
|
|
28
|
+
settingsStore.setItem(storeKey, value);
|
|
29
|
+
}
|
|
30
|
+
getItem(key) {
|
|
31
|
+
return this.#cache[key];
|
|
32
|
+
}
|
|
33
|
+
listen(setting, callback) {
|
|
34
|
+
function handler({ key, value }) {
|
|
35
|
+
if (setting === key) {
|
|
36
|
+
callback(value);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
this.#mitt.on("change", handler);
|
|
40
|
+
return () => this.#mitt.off("change", handler);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const settingsCache = new SettingsCache();
|
|
44
|
+
/**
|
|
45
|
+
* On mount, load the persisted state from Indexed DB, and if the persisted
|
|
46
|
+
* layout version matches, restore the layout. Then listen to changes to the
|
|
47
|
+
* state, and store the latest version in Indexed DB.
|
|
48
|
+
* @deprecated Use equivalent hook in @olenbetong/appframe-react instead
|
|
49
|
+
*/
|
|
50
|
+
export function usePersistedSetting(key, defaultValue) {
|
|
51
|
+
let value = useSyncExternalStore((onChange) => settingsCache.listen(key, () => onChange()), () => settingsCache.getItem(key) ?? defaultValue);
|
|
52
|
+
return [value, (newValue) => settingsCache.setItem(key, newValue)];
|
|
53
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
declare type SearchParamValue = string | null;
|
|
3
|
+
/**
|
|
4
|
+
* A hook that wraps useState, and keeps the state value synced with
|
|
5
|
+
* a search parameter.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} key They key used to store the value in the search parameters
|
|
8
|
+
* @param {SearchParamValue} fallbackValue Default value for the state. Only used if the search parameters do not contains a value for the key parameter
|
|
9
|
+
* @deprecated Use equivalent hook in @olenbetong/appframe-react instead
|
|
10
|
+
*/
|
|
11
|
+
export declare function useSearchParamState(key: string, fallbackValue?: SearchParamValue | (() => SearchParamValue), multiple?: false): [SearchParamValue, React.Dispatch<React.SetStateAction<SearchParamValue>>];
|
|
12
|
+
export declare function useSearchParamState(key: string, fallbackValue: string[] | (() => string[]), multiple: true): [string[], React.Dispatch<React.SetStateAction<string[]>>];
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
export function useSearchParamState(key, fallbackValue, multiple = false) {
|
|
3
|
+
let defaultRef = useRef(multiple ? [] : null);
|
|
4
|
+
let [value, setValue] = useState(() => {
|
|
5
|
+
let params = new URLSearchParams(window.location.search);
|
|
6
|
+
let value = multiple ? params.getAll(key) : params.get(key);
|
|
7
|
+
// Even if we don't use the default value here, we want to keep it for later
|
|
8
|
+
// so we can see if the default value is the active value. If that's the case,
|
|
9
|
+
// we remove the parameter from the search parameters.
|
|
10
|
+
if (typeof fallbackValue === "function") {
|
|
11
|
+
defaultRef.current = fallbackValue();
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
defaultRef.current = fallbackValue ?? (multiple ? [] : null);
|
|
15
|
+
}
|
|
16
|
+
if (!value) {
|
|
17
|
+
return defaultRef.current;
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
});
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
let params = new URLSearchParams(window.location.search);
|
|
23
|
+
if (multiple) {
|
|
24
|
+
params.delete(key);
|
|
25
|
+
value.forEach((v) => params.append(key, v));
|
|
26
|
+
}
|
|
27
|
+
else if (value && value !== defaultRef.current) {
|
|
28
|
+
params.set(key, String(value));
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
params.delete(key);
|
|
32
|
+
}
|
|
33
|
+
let newPath = window.location.pathname;
|
|
34
|
+
let paramString = params.toString();
|
|
35
|
+
if (paramString.length) {
|
|
36
|
+
newPath += "?" + paramString;
|
|
37
|
+
}
|
|
38
|
+
window.history.replaceState({ path: newPath }, "", newPath);
|
|
39
|
+
}, [key, value]);
|
|
40
|
+
// @ts-ignore i give up
|
|
41
|
+
return [value, setValue];
|
|
42
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
async function translateText(text, target, apiKey) {
|
|
3
|
+
let url = `https://translation.googleapis.com/language/translate/v2?key=${apiKey}`;
|
|
4
|
+
let body = JSON.stringify({
|
|
5
|
+
q: text,
|
|
6
|
+
target,
|
|
7
|
+
});
|
|
8
|
+
let options = {
|
|
9
|
+
body,
|
|
10
|
+
method: "POST",
|
|
11
|
+
};
|
|
12
|
+
let result = await fetch(url, options);
|
|
13
|
+
let json = await result.json();
|
|
14
|
+
let { data } = json;
|
|
15
|
+
if (data && data.translations) {
|
|
16
|
+
let [translation] = data.translations;
|
|
17
|
+
return {
|
|
18
|
+
from: translation.detectedSourceLanguage,
|
|
19
|
+
translation: translation.translatedText,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
export function useTranslation(text, targetLanguage, apiKey) {
|
|
25
|
+
let [translation, setTranslation] = useState("");
|
|
26
|
+
let [error, setError] = useState(null);
|
|
27
|
+
let [from, setSourceLang] = useState(null);
|
|
28
|
+
let [isTranslating, setIsTranslating] = useState(false);
|
|
29
|
+
let textRef = useRef(text);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
textRef.current = text;
|
|
32
|
+
return () => {
|
|
33
|
+
setTranslation("");
|
|
34
|
+
setError(null);
|
|
35
|
+
setSourceLang(null);
|
|
36
|
+
setIsTranslating(false);
|
|
37
|
+
};
|
|
38
|
+
}, [apiKey, targetLanguage, text]);
|
|
39
|
+
async function translate() {
|
|
40
|
+
setIsTranslating(true);
|
|
41
|
+
try {
|
|
42
|
+
let result = await translateText(text, targetLanguage, apiKey);
|
|
43
|
+
if (result && textRef.current === text) {
|
|
44
|
+
let { from, translation } = result;
|
|
45
|
+
setSourceLang(from);
|
|
46
|
+
setTranslation(translation);
|
|
47
|
+
setIsTranslating(false);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (textRef.current === text) {
|
|
52
|
+
setError(error.message);
|
|
53
|
+
setIsTranslating(false);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
error,
|
|
59
|
+
from,
|
|
60
|
+
isTranslating,
|
|
61
|
+
translation,
|
|
62
|
+
translate,
|
|
63
|
+
};
|
|
64
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@olenbetong/synergi-react",
|
|
3
|
+
"version": "0.6.7",
|
|
4
|
+
"description": "Standalone React component for SynergiWeb and Partner Portal",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"types": "./es/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./es/index.js"
|
|
10
|
+
},
|
|
11
|
+
"./Checkbox": {
|
|
12
|
+
"import": "./es/Checkbox/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./ColorCard": {
|
|
15
|
+
"import": "./es/ColorCard/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./DateNavigator": {
|
|
18
|
+
"import": "./es/DateNavigator/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./LinkedCardList": {
|
|
21
|
+
"import": "./es/LinkedCardList/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./PageBanner": {
|
|
24
|
+
"import": "./es/PageBanner/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./Portal": {
|
|
27
|
+
"import": "./es/Portal/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./ProgressBar": {
|
|
30
|
+
"import": "./es/ProgressBar/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./Sidebar": {
|
|
33
|
+
"import": "./es/Sidebar/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./Spinner": {
|
|
36
|
+
"import": "./es/Spinner/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./SplitContainer": {
|
|
39
|
+
"import": "./es/SplitContainer/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./useTranslation": {
|
|
42
|
+
"import": "./es/useTranslation/index.js"
|
|
43
|
+
},
|
|
44
|
+
"./ValueToggle": {
|
|
45
|
+
"import": "./es/ValueToggle/index.js"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"prepack": "npm run build",
|
|
50
|
+
"build": "npm run build:tsc && npm run build:tsc:post && npm run build:esbuild && npm run build:sass && npm run build:sass:components",
|
|
51
|
+
"build:tsc": "rm -rf ./es && tsc",
|
|
52
|
+
"build:esbuild": "rm -rf ./dist && node --loader ts-node/esm.mjs ../../shared/esbuild.js",
|
|
53
|
+
"build:sass": "sass ./src/index.scss ./dist/styles/styles.css --source-map-urls=relative --style compressed",
|
|
54
|
+
"build:sass:components": "node ./scripts/buildSassComponents.mjs",
|
|
55
|
+
"build:tsc:post": "node ./scripts/replaceCss.mjs"
|
|
56
|
+
},
|
|
57
|
+
"files": [
|
|
58
|
+
"src",
|
|
59
|
+
"dist",
|
|
60
|
+
"es"
|
|
61
|
+
],
|
|
62
|
+
"keywords": [],
|
|
63
|
+
"author": "Bjørnar Vister Hansen <bvh@olenbetong.no>",
|
|
64
|
+
"license": "MIT",
|
|
65
|
+
"peerDependencies": {
|
|
66
|
+
"react": "^18.0.0",
|
|
67
|
+
"react-dom": "^18.0.0"
|
|
68
|
+
},
|
|
69
|
+
"sideEffects": false,
|
|
70
|
+
"dependencies": {
|
|
71
|
+
"@olenbetong/appframe-core": "^2.0.4",
|
|
72
|
+
"clsx": "^1.1.1",
|
|
73
|
+
"localforage": "^1.10.0",
|
|
74
|
+
"mitt": "^3.0.0"
|
|
75
|
+
},
|
|
76
|
+
"repository": {
|
|
77
|
+
"type": "git",
|
|
78
|
+
"url": "https://github.com/olenbetong/ui-kit.git"
|
|
79
|
+
},
|
|
80
|
+
"buildConfig": {
|
|
81
|
+
"entries": [
|
|
82
|
+
{
|
|
83
|
+
"libraryName": "ob.react"
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
"devDependencies": {
|
|
88
|
+
"replace-in-files": "^3.0.0"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
.ObCheckbox-root {
|
|
2
|
+
--check-width: 2rem;
|
|
3
|
+
--bg-color: var(--brand-dark, rgba(42, 65, 50, 0.5));
|
|
4
|
+
--bg-color-hover: var(--brand-primary, rgba(42, 65, 50, 0.8));
|
|
5
|
+
--bg-color-selected: var(--brand-light);
|
|
6
|
+
|
|
7
|
+
display: inline-block;
|
|
8
|
+
height: var(--check-width);
|
|
9
|
+
margin-bottom: 0;
|
|
10
|
+
|
|
11
|
+
&:focus-within .ObCheckbox-box {
|
|
12
|
+
border: 0.1rem solid black;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.ObCheckbox-box {
|
|
17
|
+
position: relative;
|
|
18
|
+
|
|
19
|
+
background: var(--bg-color);
|
|
20
|
+
transition: background 300ms ease;
|
|
21
|
+
|
|
22
|
+
overflow: hidden;
|
|
23
|
+
|
|
24
|
+
border-radius: calc(var(--check-width) / 15);
|
|
25
|
+
height: var(--check-width);
|
|
26
|
+
width: var(--check-width);
|
|
27
|
+
|
|
28
|
+
&:hover {
|
|
29
|
+
background: var(--bg-color-hover);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.ObCheckbox-label {
|
|
34
|
+
position: relative;
|
|
35
|
+
margin-bottom: 0;
|
|
36
|
+
overflow: hidden;
|
|
37
|
+
|
|
38
|
+
&:focus {
|
|
39
|
+
outline: none;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.ObCheckbox-mark {
|
|
44
|
+
color: #fff;
|
|
45
|
+
display: inline-block;
|
|
46
|
+
font-size: calc(var(--check-width) * 0.4);
|
|
47
|
+
left: 50%;
|
|
48
|
+
opacity: 0;
|
|
49
|
+
pointer-events: none;
|
|
50
|
+
position: absolute;
|
|
51
|
+
top: 50%;
|
|
52
|
+
transition: all 300ms ease-in-out;
|
|
53
|
+
transition-delay: 0ms;
|
|
54
|
+
transform: translate(-50%, -50%) scale(6);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.ObCheckbox-input {
|
|
58
|
+
clip: rect(0 0 0 0);
|
|
59
|
+
clip-path: inset(50%);
|
|
60
|
+
height: 1px;
|
|
61
|
+
overflow: hidden;
|
|
62
|
+
position: absolute;
|
|
63
|
+
white-space: nowrap;
|
|
64
|
+
width: 1px;
|
|
65
|
+
|
|
66
|
+
&:focus {
|
|
67
|
+
outline: none;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.check_Label {
|
|
72
|
+
margin-bottom: 0;
|
|
73
|
+
flex-grow: 1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.ObCheckbox-input:checked:not(:disabled) + .ObCheckbox-label {
|
|
77
|
+
animation: animOn 800ms 1 forwards;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.ObCheckbox-input:checked:not(:disabled) + .ObCheckbox-label .ObCheckbox-box {
|
|
81
|
+
background: var(--bg-color-selected);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.ObCheckbox-input:checked + .ObCheckbox-label .ObCheckbox-box .ObCheckbox-mark {
|
|
85
|
+
transform: translate(-50%, -50%) scale(1);
|
|
86
|
+
transition-delay: 200ms;
|
|
87
|
+
opacity: 1;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.check_Disabled {
|
|
91
|
+
--bg-color: rgba(150, 150, 150, 0.5);
|
|
92
|
+
--bg-color-hover: var(--bg-color);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.ObCheckbox-root.Ob-disabled .ObCheckbox-box {
|
|
96
|
+
cursor: not-allowed;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@keyframes animOn {
|
|
100
|
+
40% {
|
|
101
|
+
transform: scaleY(0.4) scaleX(2);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
50% {
|
|
105
|
+
transform: scaleY(1.2) scaleX(0.6);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
60% {
|
|
109
|
+
transform: scaleY(0.8) scaleX(1.4);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
70% {
|
|
113
|
+
transform: scaleY(1.1) scaleX(0.9);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
100% {
|
|
117
|
+
transform: scaleY(1) scaleX(1);
|
|
118
|
+
}
|
|
119
|
+
}
|