@bgord/ui 0.4.1 → 0.5.2
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/components/dialog.d.ts +4 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/hooks/index.d.ts +7 -0
- package/dist/hooks/use-click-outside.d.ts +2 -0
- package/dist/hooks/use-client-filter.d.ts +20 -0
- package/dist/hooks/use-focus-shortcut.d.ts +8 -0
- package/dist/hooks/use-language-selector.d.ts +4 -0
- package/dist/hooks/use-meta-enter-submit.d.ts +3 -0
- package/dist/hooks/use-scroll-lock.d.ts +1 -0
- package/dist/hooks/use-shortcuts.d.ts +8 -0
- package/dist/index.js +352 -91
- package/dist/services/auth-guard.d.ts +9 -0
- package/dist/services/cookies.d.ts +3 -0
- package/dist/services/copy-to-clipboard.d.ts +10 -0
- package/dist/services/credentials.d.ts +9 -0
- package/dist/services/exec.d.ts +3 -0
- package/dist/services/get-safe-window.d.ts +1 -0
- package/dist/services/index.d.ts +7 -1
- package/dist/services/noop.d.ts +1 -0
- package/package.json +19 -15
- package/readme.md +15 -1
package/dist/hooks/index.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
+
export * from "./use-click-outside";
|
|
2
|
+
export * from "./use-client-filter";
|
|
1
3
|
export * from "./use-exit-action";
|
|
2
4
|
export * from "./use-field";
|
|
5
|
+
export * from "./use-focus-shortcut";
|
|
3
6
|
export * from "./use-hover";
|
|
7
|
+
export * from "./use-language-selector";
|
|
8
|
+
export * from "./use-meta-enter-submit";
|
|
9
|
+
export * from "./use-scroll-lock";
|
|
10
|
+
export * from "./use-shortcuts";
|
|
4
11
|
export * from "./use-toggle";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { FieldValueAllowedTypes } from "../services/field";
|
|
2
|
+
import { useFieldConfigType, useFieldReturnType, useFieldStrategyEnum } from "./use-field";
|
|
3
|
+
export type useClientFilterQueryType = string | undefined;
|
|
4
|
+
type useClientFilterConfigType<T extends FieldValueAllowedTypes> = Omit<useFieldConfigType<T>, "strategy"> & {
|
|
5
|
+
enum: {
|
|
6
|
+
[key: string]: useClientFilterQueryType;
|
|
7
|
+
};
|
|
8
|
+
filterFn?: (value: T) => boolean;
|
|
9
|
+
};
|
|
10
|
+
export type useClientFilterReturnType<T extends FieldValueAllowedTypes> = useFieldReturnType<T> & {
|
|
11
|
+
filterFn: NonNullable<useClientFilterConfigType<T>["filterFn"]>;
|
|
12
|
+
options: {
|
|
13
|
+
name: string;
|
|
14
|
+
value: useClientFilterConfigType<T>["enum"][0];
|
|
15
|
+
}[];
|
|
16
|
+
} & {
|
|
17
|
+
strategy: useFieldStrategyEnum.local;
|
|
18
|
+
};
|
|
19
|
+
export declare function useClientFilter<T extends FieldValueAllowedTypes>(config: useClientFilterConfigType<T>): useClientFilterReturnType<T>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useScrollLock(enabled?: boolean): void;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface UseKeyboardShortcutsConfigType {
|
|
2
|
+
[keybinding: string]: (event: KeyboardEvent) => void;
|
|
3
|
+
}
|
|
4
|
+
type UseKeyboardShortcutsOptionsType = {
|
|
5
|
+
enabled?: boolean;
|
|
6
|
+
};
|
|
7
|
+
export declare function useKeyboardShortcuts(config: UseKeyboardShortcutsConfigType, options?: UseKeyboardShortcutsOptionsType): void;
|
|
8
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -6,27 +6,43 @@ function Button() {
|
|
|
6
6
|
children: "Click"
|
|
7
7
|
}, undefined, false, undefined, this);
|
|
8
8
|
}
|
|
9
|
-
// src/
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
};
|
|
18
|
-
const onAnimationEnd = (event) => {
|
|
19
|
-
if (event.animationName !== options.animation)
|
|
9
|
+
// src/components/dialog.tsx
|
|
10
|
+
import { useEffect as useEffect6, useRef as useRef3 } from "react";
|
|
11
|
+
|
|
12
|
+
// src/hooks/use-click-outside.ts
|
|
13
|
+
import { useEffect } from "react";
|
|
14
|
+
function useClickOutside(ref, handler) {
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (typeof document === "undefined")
|
|
20
17
|
return;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
function listener(event) {
|
|
19
|
+
const el = ref.current;
|
|
20
|
+
if (!el)
|
|
21
|
+
return;
|
|
22
|
+
if (el.contains(event.target)) {
|
|
23
|
+
if (event.target === el) {
|
|
24
|
+
const { left, right, top, bottom } = el.getBoundingClientRect();
|
|
25
|
+
const clientX = event instanceof MouseEvent ? event.clientX : event.touches[0].clientX;
|
|
26
|
+
const clientY = event instanceof MouseEvent ? event.clientY : event.touches[0].clientY;
|
|
27
|
+
const isInRect = clientX >= left && clientX <= right && clientY >= top && clientY <= bottom;
|
|
28
|
+
if (isInRect)
|
|
29
|
+
return;
|
|
30
|
+
} else {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
handler(event);
|
|
35
|
+
}
|
|
36
|
+
document.addEventListener("mousedown", listener);
|
|
37
|
+
document.addEventListener("touchstart", listener);
|
|
38
|
+
return () => {
|
|
39
|
+
document.removeEventListener("mousedown", listener);
|
|
40
|
+
document.removeEventListener("touchstart", listener);
|
|
41
|
+
};
|
|
42
|
+
}, [ref, handler]);
|
|
26
43
|
}
|
|
27
|
-
// src/hooks/use-
|
|
28
|
-
import {
|
|
29
|
-
import { useSearchParams } from "react-router";
|
|
44
|
+
// src/hooks/use-client-filter.ts
|
|
45
|
+
import { useCallback, useMemo } from "react";
|
|
30
46
|
|
|
31
47
|
// src/services/field.ts
|
|
32
48
|
class Field {
|
|
@@ -53,6 +69,8 @@ class Field {
|
|
|
53
69
|
}
|
|
54
70
|
|
|
55
71
|
// src/hooks/use-field.ts
|
|
72
|
+
import { useEffect as useEffect2, useState } from "react";
|
|
73
|
+
import { useSearchParams } from "react-router";
|
|
56
74
|
var useFieldStrategyEnum;
|
|
57
75
|
((useFieldStrategyEnum2) => {
|
|
58
76
|
useFieldStrategyEnum2["params"] = "params";
|
|
@@ -68,7 +86,7 @@ function useField(config) {
|
|
|
68
86
|
const candidate = new Field(value2);
|
|
69
87
|
_setCurrentValue(candidate.get());
|
|
70
88
|
};
|
|
71
|
-
|
|
89
|
+
useEffect2(() => {
|
|
72
90
|
const current = new Field(currentValue);
|
|
73
91
|
if (strategy === "params" /* params */) {
|
|
74
92
|
if (current.isEmpty()) {
|
|
@@ -104,8 +122,75 @@ class LocalFields {
|
|
|
104
122
|
return () => fields.forEach((field) => field.clear());
|
|
105
123
|
}
|
|
106
124
|
}
|
|
125
|
+
|
|
126
|
+
// src/hooks/use-client-filter.ts
|
|
127
|
+
function useClientFilter(config) {
|
|
128
|
+
const query = useField({
|
|
129
|
+
...config,
|
|
130
|
+
strategy: "local" /* local */
|
|
131
|
+
});
|
|
132
|
+
const defaultFilterFn = useCallback((given) => {
|
|
133
|
+
if (query.empty)
|
|
134
|
+
return true;
|
|
135
|
+
return Field.compare(given, query.currentValue);
|
|
136
|
+
}, [query.empty, query.currentValue]);
|
|
137
|
+
const filterFn = useMemo(() => config.filterFn ?? defaultFilterFn, [config.filterFn, defaultFilterFn]);
|
|
138
|
+
const options = useMemo(() => Object.entries(config.enum).map(([name, value]) => ({ name, value })), [config.enum]);
|
|
139
|
+
return useMemo(() => ({
|
|
140
|
+
...query,
|
|
141
|
+
filterFn,
|
|
142
|
+
options,
|
|
143
|
+
strategy: "local" /* local */
|
|
144
|
+
}), [query, filterFn, options]);
|
|
145
|
+
}
|
|
146
|
+
// src/hooks/use-exit-action.ts
|
|
147
|
+
import React from "react";
|
|
148
|
+
function useExitAction(options) {
|
|
149
|
+
const [phase, setPhase] = React.useState("idle" /* idle */);
|
|
150
|
+
const trigger = (event) => {
|
|
151
|
+
event.preventDefault();
|
|
152
|
+
if (phase === "idle")
|
|
153
|
+
setPhase("exiting" /* exiting */);
|
|
154
|
+
};
|
|
155
|
+
const onAnimationEnd = (event) => {
|
|
156
|
+
if (event.animationName !== options.animation)
|
|
157
|
+
return;
|
|
158
|
+
options.actionFn();
|
|
159
|
+
setPhase("gone" /* gone */);
|
|
160
|
+
};
|
|
161
|
+
const attach = phase === "exiting" ? { "data-animation": options.animation, onAnimationEnd } : undefined;
|
|
162
|
+
return { visible: phase !== "gone", attach, trigger };
|
|
163
|
+
}
|
|
164
|
+
// src/hooks/use-focus-shortcut.ts
|
|
165
|
+
import { useCallback as useCallback2, useMemo as useMemo3, useRef } from "react";
|
|
166
|
+
|
|
167
|
+
// src/hooks/use-shortcuts.ts
|
|
168
|
+
import { useEffect as useEffect3, useMemo as useMemo2 } from "react";
|
|
169
|
+
import { tinykeys } from "tinykeys";
|
|
170
|
+
function useKeyboardShortcuts(config, options) {
|
|
171
|
+
const enabled = options?.enabled ?? true;
|
|
172
|
+
const memoizedConfig = useMemo2(() => config, [JSON.stringify(Object.keys(config))]);
|
|
173
|
+
useEffect3(() => {
|
|
174
|
+
if (!enabled)
|
|
175
|
+
return;
|
|
176
|
+
const unsubscribe = tinykeys(window, memoizedConfig);
|
|
177
|
+
return () => unsubscribe();
|
|
178
|
+
}, [memoizedConfig, enabled]);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/hooks/use-focus-shortcut.ts
|
|
182
|
+
function useFocusKeyboardShortcut(shortcut) {
|
|
183
|
+
const ref = useRef(null);
|
|
184
|
+
const handleFocus = useCallback2(() => {
|
|
185
|
+
if (ref.current) {
|
|
186
|
+
ref.current.focus();
|
|
187
|
+
}
|
|
188
|
+
}, []);
|
|
189
|
+
useKeyboardShortcuts({ [shortcut]: handleFocus });
|
|
190
|
+
return useMemo3(() => ({ ref }), []);
|
|
191
|
+
}
|
|
107
192
|
// src/hooks/use-hover.ts
|
|
108
|
-
import { useCallback, useRef } from "react";
|
|
193
|
+
import { useCallback as useCallback3, useRef as useRef2 } from "react";
|
|
109
194
|
|
|
110
195
|
// src/hooks/use-toggle.ts
|
|
111
196
|
import { useState as useState2 } from "react";
|
|
@@ -143,10 +228,10 @@ function useHover({
|
|
|
143
228
|
enabled = true
|
|
144
229
|
} = {}) {
|
|
145
230
|
const { on: isOn, enable, disable } = useToggle({ name: "is-hovering" });
|
|
146
|
-
const nodeRef =
|
|
231
|
+
const nodeRef = useRef2(null);
|
|
147
232
|
const enterEvent = typeof window !== "undefined" && "PointerEvent" in window ? "pointerenter" : "mouseenter";
|
|
148
233
|
const leaveEvent = typeof window !== "undefined" && "PointerEvent" in window ? "pointerleave" : "mouseleave";
|
|
149
|
-
const ref =
|
|
234
|
+
const ref = useCallback3((node) => {
|
|
150
235
|
const prev = nodeRef.current;
|
|
151
236
|
if (prev) {
|
|
152
237
|
prev.removeEventListener(enterEvent, enable);
|
|
@@ -163,25 +248,236 @@ function useHover({
|
|
|
163
248
|
isHovering: isOn && enabled
|
|
164
249
|
};
|
|
165
250
|
}
|
|
166
|
-
// src/
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
251
|
+
// src/hooks/use-language-selector.tsx
|
|
252
|
+
import Cookies from "js-cookie";
|
|
253
|
+
import { useCallback as useCallback5, useEffect as useEffect4 } from "react";
|
|
254
|
+
import { useRevalidator } from "react-router";
|
|
255
|
+
|
|
256
|
+
// src/services/translations.tsx
|
|
257
|
+
import { createContext, use, useCallback as useCallback4 } from "react";
|
|
258
|
+
|
|
259
|
+
// src/services/pluralize.ts
|
|
260
|
+
import { polishPlurals } from "polish-plurals";
|
|
261
|
+
function pluralize(options) {
|
|
262
|
+
if (options.language === "en" /* en */) {
|
|
263
|
+
const plural = options.plural ?? `${options.singular}s`;
|
|
264
|
+
if (options.value === 1)
|
|
265
|
+
return options.singular;
|
|
266
|
+
return plural;
|
|
267
|
+
}
|
|
268
|
+
if (options.language === "pl" /* pl */) {
|
|
269
|
+
const value = options.value ?? 1;
|
|
270
|
+
if (value === 1)
|
|
271
|
+
return options.singular;
|
|
272
|
+
return polishPlurals(options.singular, String(options.plural), String(options.genitive), value);
|
|
273
|
+
}
|
|
274
|
+
console.warn(`[@bgord/frontend] missing pluralization function for language: ${options.language}.`);
|
|
275
|
+
return options.singular;
|
|
178
276
|
}
|
|
277
|
+
|
|
278
|
+
// src/services/translations.tsx
|
|
279
|
+
var TranslationsContext = createContext({
|
|
280
|
+
translations: {},
|
|
281
|
+
language: "en"
|
|
282
|
+
});
|
|
283
|
+
function useTranslations() {
|
|
284
|
+
const value = use(TranslationsContext);
|
|
285
|
+
if (value === undefined) {
|
|
286
|
+
throw new Error("useTranslations must be used within the TranslationsContext");
|
|
287
|
+
}
|
|
288
|
+
const translate = useCallback4((key, variables) => {
|
|
289
|
+
const translation = value.translations[key];
|
|
290
|
+
if (!translation) {
|
|
291
|
+
console.warn(`[@bgord/ui] missing translation for key: ${key}`);
|
|
292
|
+
return key;
|
|
293
|
+
}
|
|
294
|
+
if (!variables)
|
|
295
|
+
return translation;
|
|
296
|
+
return Object.entries(variables).reduce((result, [placeholder, value2]) => {
|
|
297
|
+
const regex = new RegExp(`{{${placeholder}}}`, "g");
|
|
298
|
+
return result.replace(regex, String(value2));
|
|
299
|
+
}, translation);
|
|
300
|
+
}, [value.translations]);
|
|
301
|
+
return translate;
|
|
302
|
+
}
|
|
303
|
+
function useLanguage() {
|
|
304
|
+
const value = use(TranslationsContext);
|
|
305
|
+
if (value === undefined) {
|
|
306
|
+
throw new Error("useLanguage must be used within the TranslationsContext");
|
|
307
|
+
}
|
|
308
|
+
return value.language;
|
|
309
|
+
}
|
|
310
|
+
function usePluralize() {
|
|
311
|
+
const language = useLanguage();
|
|
312
|
+
return (options) => pluralize({ ...options, language });
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/hooks/use-language-selector.tsx
|
|
316
|
+
function useLanguageSelector(supportedLanguages) {
|
|
317
|
+
const language = useLanguage();
|
|
318
|
+
const revalidator = useRevalidator();
|
|
319
|
+
const field = useClientFilter({
|
|
320
|
+
enum: supportedLanguages,
|
|
321
|
+
defaultValue: language,
|
|
322
|
+
name: "language"
|
|
323
|
+
});
|
|
324
|
+
const handleLanguageChange = useCallback5(() => {
|
|
325
|
+
const current = new Field(field.currentValue);
|
|
326
|
+
if (!current.isEmpty() && field.changed) {
|
|
327
|
+
Cookies.set("language", String(current.get()));
|
|
328
|
+
revalidator.revalidate();
|
|
329
|
+
}
|
|
330
|
+
}, [field.currentValue, field.changed]);
|
|
331
|
+
useEffect4(() => {
|
|
332
|
+
handleLanguageChange();
|
|
333
|
+
}, [handleLanguageChange]);
|
|
334
|
+
return field;
|
|
335
|
+
}
|
|
336
|
+
// src/hooks/use-meta-enter-submit.tsx
|
|
337
|
+
import { useCallback as useCallback6, useMemo as useMemo4 } from "react";
|
|
338
|
+
function useMetaEnterSubmit() {
|
|
339
|
+
const handleMetaEnterSubmit = useCallback6((event) => {
|
|
340
|
+
if (event.key !== "Enter" || !event.metaKey)
|
|
341
|
+
return;
|
|
342
|
+
event.preventDefault();
|
|
343
|
+
event.currentTarget.form?.requestSubmit();
|
|
344
|
+
}, []);
|
|
345
|
+
return useMemo4(() => ({ onKeyDown: handleMetaEnterSubmit }), [handleMetaEnterSubmit]);
|
|
346
|
+
}
|
|
347
|
+
// src/hooks/use-scroll-lock.ts
|
|
348
|
+
import { useEffect as useEffect5 } from "react";
|
|
349
|
+
function useScrollLock(enabled = true) {
|
|
350
|
+
useEffect5(() => {
|
|
351
|
+
if (typeof document === "undefined")
|
|
352
|
+
return;
|
|
353
|
+
const originalOverflow = document.body.style.overflow;
|
|
354
|
+
if (enabled) {
|
|
355
|
+
document.body.style.overflow = "hidden";
|
|
356
|
+
}
|
|
357
|
+
return () => {
|
|
358
|
+
document.body.style.overflow = originalOverflow;
|
|
359
|
+
};
|
|
360
|
+
}, [enabled]);
|
|
361
|
+
}
|
|
362
|
+
// src/components/dialog.tsx
|
|
363
|
+
import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
|
|
364
|
+
function Dialog(props) {
|
|
365
|
+
const { toggle: dialog, rest } = extractUseToggle(props);
|
|
366
|
+
const ref = useRef3(null);
|
|
367
|
+
useEffect6(() => {
|
|
368
|
+
if (props.on) {
|
|
369
|
+
ref.current?.showModal();
|
|
370
|
+
} else {
|
|
371
|
+
ref.current?.close();
|
|
372
|
+
}
|
|
373
|
+
}, [props.on]);
|
|
374
|
+
useKeyboardShortcuts({ Escape: dialog.disable });
|
|
375
|
+
useScrollLock(props.on);
|
|
376
|
+
useClickOutside(ref, dialog.disable);
|
|
377
|
+
return /* @__PURE__ */ jsxDEV2("dialog", {
|
|
378
|
+
ref,
|
|
379
|
+
tabIndex: 0,
|
|
380
|
+
"aria-modal": "true",
|
|
381
|
+
"data-disp": props.on ? "flex" : "none",
|
|
382
|
+
"data-dir": "column",
|
|
383
|
+
"data-mx": "auto",
|
|
384
|
+
"data-p": "5",
|
|
385
|
+
"data-position": "fixed",
|
|
386
|
+
"data-z": "2",
|
|
387
|
+
"data-bg": "neutral-900",
|
|
388
|
+
"data-br": "xs",
|
|
389
|
+
"data-backdrop": "stronger",
|
|
390
|
+
"data-animation": "grow-fade-in",
|
|
391
|
+
...rest
|
|
392
|
+
}, undefined, false, undefined, this);
|
|
393
|
+
}
|
|
394
|
+
// src/services/auth-guard.ts
|
|
395
|
+
import { redirect } from "react-router";
|
|
396
|
+
|
|
397
|
+
// src/services/cookies.ts
|
|
398
|
+
class Cookies2 {
|
|
399
|
+
static extractFrom(request) {
|
|
400
|
+
return request.headers.get("cookie") ?? "";
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// src/services/auth-guard.ts
|
|
405
|
+
class AuthGuard {
|
|
406
|
+
API_URL;
|
|
407
|
+
constructor(BASE_URL) {
|
|
408
|
+
this.API_URL = `${BASE_URL}/api/auth`;
|
|
409
|
+
}
|
|
410
|
+
async getServerSession(request) {
|
|
411
|
+
const cookie = Cookies2.extractFrom(request);
|
|
412
|
+
const res = await fetch(`${this.API_URL}/get-session`, {
|
|
413
|
+
headers: { cookie, accept: "application/json" }
|
|
414
|
+
});
|
|
415
|
+
if (!res.ok)
|
|
416
|
+
return null;
|
|
417
|
+
const session = await res.json();
|
|
418
|
+
return session;
|
|
419
|
+
}
|
|
420
|
+
async requireSession(request) {
|
|
421
|
+
const session = await this.getServerSession(request);
|
|
422
|
+
if (session?.user)
|
|
423
|
+
return session;
|
|
424
|
+
throw redirect("/");
|
|
425
|
+
}
|
|
426
|
+
async requireNoSession(request, target = "/home") {
|
|
427
|
+
const session = await this.getServerSession(request);
|
|
428
|
+
if (session?.user)
|
|
429
|
+
throw redirect(target);
|
|
430
|
+
}
|
|
431
|
+
async removeSession(request, target = "/login") {
|
|
432
|
+
const cookie = Cookies2.extractFrom(request);
|
|
433
|
+
const res = await fetch(`${import.meta.env.VITE_API_URL}/api/auth/sign-out`, {
|
|
434
|
+
method: "POST",
|
|
435
|
+
headers: { cookie }
|
|
436
|
+
});
|
|
437
|
+
const headers = new Headers;
|
|
438
|
+
res.headers.forEach((value, key) => {
|
|
439
|
+
if (key.toLowerCase() === "set-cookie")
|
|
440
|
+
headers.append("set-cookie", value);
|
|
441
|
+
});
|
|
442
|
+
throw redirect(target, { headers });
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
// src/services/noop.ts
|
|
446
|
+
function noop() {}
|
|
447
|
+
|
|
448
|
+
// src/services/copy-to-clipboard.ts
|
|
449
|
+
var defaultOnCopyToClipboardFailure = () => console.warn("Copying to clipboard not supported");
|
|
450
|
+
async function copyToClipboard(options) {
|
|
451
|
+
const onFailure = options.onFailure ?? defaultOnCopyToClipboardFailure;
|
|
452
|
+
const onSuccess = options.onSuccess ?? noop;
|
|
453
|
+
if (!navigator.clipboard)
|
|
454
|
+
onFailure();
|
|
455
|
+
try {
|
|
456
|
+
await navigator.clipboard.writeText(options.text);
|
|
457
|
+
onSuccess();
|
|
458
|
+
} catch (error) {
|
|
459
|
+
onFailure(error);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
// src/services/credentials.ts
|
|
463
|
+
var Credentials = {
|
|
464
|
+
email: { inputMode: "email", autoComplete: "email", autoCapitalize: "none", spellCheck: "false" },
|
|
465
|
+
password: { new: { autoComplete: "new-password" }, current: { autoComplete: "current-password" } }
|
|
466
|
+
};
|
|
179
467
|
// src/services/etag.ts
|
|
180
468
|
class ETag {
|
|
181
469
|
static fromRevision(revision) {
|
|
182
470
|
return { "if-match": String(revision) };
|
|
183
471
|
}
|
|
184
472
|
}
|
|
473
|
+
// src/services/exec.ts
|
|
474
|
+
function exec(list) {
|
|
475
|
+
return function() {
|
|
476
|
+
for (const item of list) {
|
|
477
|
+
item();
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
}
|
|
185
481
|
// src/services/fields.ts
|
|
186
482
|
class Fields {
|
|
187
483
|
static allUnchanged(fields) {
|
|
@@ -223,23 +519,11 @@ class Form {
|
|
|
223
519
|
return { required };
|
|
224
520
|
}
|
|
225
521
|
}
|
|
226
|
-
// src/services/
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (options.value === 1)
|
|
232
|
-
return options.singular;
|
|
233
|
-
return plural;
|
|
234
|
-
}
|
|
235
|
-
if (options.language === "pl" /* pl */) {
|
|
236
|
-
const value = options.value ?? 1;
|
|
237
|
-
if (value === 1)
|
|
238
|
-
return options.singular;
|
|
239
|
-
return polishPlurals(options.singular, String(options.plural), String(options.genitive), value);
|
|
240
|
-
}
|
|
241
|
-
console.warn(`[@bgord/frontend] missing pluralization function for language: ${options.language}.`);
|
|
242
|
-
return options.singular;
|
|
522
|
+
// src/services/get-safe-window.ts
|
|
523
|
+
function getSafeWindow() {
|
|
524
|
+
if (typeof window === "undefined")
|
|
525
|
+
return;
|
|
526
|
+
return window;
|
|
243
527
|
}
|
|
244
528
|
// src/services/rhythm.ts
|
|
245
529
|
var DEFAULT_BASE_PX = 12;
|
|
@@ -272,43 +556,6 @@ function Rhythm(base = DEFAULT_BASE_PX) {
|
|
|
272
556
|
function px(number) {
|
|
273
557
|
return `${number}px`;
|
|
274
558
|
}
|
|
275
|
-
// src/services/translations.tsx
|
|
276
|
-
import { createContext, use, useCallback as useCallback2 } from "react";
|
|
277
|
-
var TranslationsContext = createContext({
|
|
278
|
-
translations: {},
|
|
279
|
-
language: "en"
|
|
280
|
-
});
|
|
281
|
-
function useTranslations() {
|
|
282
|
-
const value = use(TranslationsContext);
|
|
283
|
-
if (value === undefined) {
|
|
284
|
-
throw new Error("useTranslations must be used within the TranslationsContext");
|
|
285
|
-
}
|
|
286
|
-
const translate = useCallback2((key, variables) => {
|
|
287
|
-
const translation = value.translations[key];
|
|
288
|
-
if (!translation) {
|
|
289
|
-
console.warn(`[@bgord/ui] missing translation for key: ${key}`);
|
|
290
|
-
return key;
|
|
291
|
-
}
|
|
292
|
-
if (!variables)
|
|
293
|
-
return translation;
|
|
294
|
-
return Object.entries(variables).reduce((result, [placeholder, value2]) => {
|
|
295
|
-
const regex = new RegExp(`{{${placeholder}}}`, "g");
|
|
296
|
-
return result.replace(regex, String(value2));
|
|
297
|
-
}, translation);
|
|
298
|
-
}, [value.translations]);
|
|
299
|
-
return translate;
|
|
300
|
-
}
|
|
301
|
-
function useLanguage() {
|
|
302
|
-
const value = use(TranslationsContext);
|
|
303
|
-
if (value === undefined) {
|
|
304
|
-
throw new Error("useLanguage must be used within the TranslationsContext");
|
|
305
|
-
}
|
|
306
|
-
return value.language;
|
|
307
|
-
}
|
|
308
|
-
function usePluralize() {
|
|
309
|
-
const language = useLanguage();
|
|
310
|
-
return (options) => pluralize({ ...options, language });
|
|
311
|
-
}
|
|
312
559
|
// src/services/weak-etag.ts
|
|
313
560
|
class WeakETag {
|
|
314
561
|
static fromRevision(revision) {
|
|
@@ -318,14 +565,25 @@ class WeakETag {
|
|
|
318
565
|
export {
|
|
319
566
|
useTranslations,
|
|
320
567
|
useToggle,
|
|
568
|
+
useScrollLock,
|
|
321
569
|
usePluralize,
|
|
570
|
+
useMetaEnterSubmit,
|
|
571
|
+
useLanguageSelector,
|
|
322
572
|
useLanguage,
|
|
573
|
+
useKeyboardShortcuts,
|
|
323
574
|
useHover,
|
|
575
|
+
useFocusKeyboardShortcut,
|
|
324
576
|
useFieldStrategyEnum,
|
|
325
577
|
useField,
|
|
326
578
|
useExitAction,
|
|
579
|
+
useClientFilter,
|
|
580
|
+
useClickOutside,
|
|
327
581
|
pluralize,
|
|
582
|
+
noop,
|
|
583
|
+
getSafeWindow,
|
|
328
584
|
extractUseToggle,
|
|
585
|
+
exec,
|
|
586
|
+
copyToClipboard,
|
|
329
587
|
WeakETag,
|
|
330
588
|
TranslationsContext,
|
|
331
589
|
Rhythm,
|
|
@@ -334,6 +592,9 @@ export {
|
|
|
334
592
|
Fields,
|
|
335
593
|
Field,
|
|
336
594
|
ETag,
|
|
337
|
-
|
|
338
|
-
|
|
595
|
+
Dialog,
|
|
596
|
+
Credentials,
|
|
597
|
+
Cookies2 as Cookies,
|
|
598
|
+
Button,
|
|
599
|
+
AuthGuard
|
|
339
600
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { createAuthClient } from "better-auth/react";
|
|
2
|
+
export declare class AuthGuard<T extends ReturnType<typeof createAuthClient>["$Infer"]["Session"]> {
|
|
3
|
+
private readonly API_URL;
|
|
4
|
+
constructor(BASE_URL: string);
|
|
5
|
+
getServerSession(request: Request): Promise<T | null>;
|
|
6
|
+
requireSession(request: Request): Promise<T | null>;
|
|
7
|
+
requireNoSession(request: Request, target?: string): Promise<void>;
|
|
8
|
+
removeSession(request: Request, target?: string): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type CopyToClipboardTextType = string;
|
|
2
|
+
type OnCopyToClipboardFailureType = (error?: unknown) => void;
|
|
3
|
+
type OnCopyToClipboardSuccessType = VoidFunction;
|
|
4
|
+
export type CopyToClipboardOptionsType = {
|
|
5
|
+
text: CopyToClipboardTextType;
|
|
6
|
+
onFailure?: OnCopyToClipboardFailureType;
|
|
7
|
+
onSuccess?: OnCopyToClipboardSuccessType;
|
|
8
|
+
};
|
|
9
|
+
export declare function copyToClipboard(options: CopyToClipboardOptionsType): Promise<void>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getSafeWindow(): (Window & typeof globalThis) | undefined;
|
package/dist/services/index.d.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
export * from "./
|
|
1
|
+
export * from "./auth-guard";
|
|
2
|
+
export * from "./cookies";
|
|
3
|
+
export * from "./copy-to-clipboard";
|
|
4
|
+
export * from "./credentials";
|
|
2
5
|
export * from "./etag";
|
|
6
|
+
export * from "./exec";
|
|
3
7
|
export * from "./field";
|
|
4
8
|
export * from "./fields";
|
|
5
9
|
export * from "./form";
|
|
10
|
+
export * from "./get-safe-window";
|
|
11
|
+
export * from "./noop";
|
|
6
12
|
export * from "./pluralize";
|
|
7
13
|
export * from "./rhythm";
|
|
8
14
|
export * from "./translations";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function noop(): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bgord/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
"dist"
|
|
13
13
|
],
|
|
14
14
|
"peerDependencies": {
|
|
15
|
-
"react": "19.1.
|
|
16
|
-
"react-dom": "19.1.
|
|
17
|
-
"react-router": "7.
|
|
15
|
+
"react": "19.1.1",
|
|
16
|
+
"react-dom": "19.1.1",
|
|
17
|
+
"react-router": "7.7.1"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build:js": "bun build src/index.ts --format esm --outdir dist --packages external --external react --external react-dom --external react/jsx-runtime --external react-router",
|
|
@@ -25,24 +25,28 @@
|
|
|
25
25
|
"access": "public"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@happy-dom/global-registrator": "18.0.1",
|
|
29
|
-
"@testing-library/dom": "10.4.0",
|
|
30
|
-
"@testing-library/jest-dom": "6.6.3",
|
|
31
|
-
"@testing-library/react": "16.3.0",
|
|
32
|
-
"@testing-library/user-event": "14.6.1",
|
|
33
|
-
"@types/bun": "1.2.18",
|
|
34
|
-
"@types/react": "19.1.8",
|
|
35
|
-
"@types/react-dom": "19.1.6",
|
|
36
28
|
"@biomejs/biome": "2.0.6",
|
|
37
29
|
"@commitlint/cli": "19.8.1",
|
|
38
30
|
"@commitlint/config-conventional": "19.8.1",
|
|
39
|
-
"
|
|
40
|
-
"
|
|
31
|
+
"@happy-dom/global-registrator": "18.0.1",
|
|
32
|
+
"@testing-library/dom": "10.4.1",
|
|
33
|
+
"@testing-library/jest-dom": "6.6.4",
|
|
34
|
+
"@testing-library/react": "16.3.0",
|
|
35
|
+
"@testing-library/user-event": "14.6.1",
|
|
36
|
+
"@types/bun": "1.2.19",
|
|
37
|
+
"@types/js-cookie": "^3.0.6",
|
|
38
|
+
"@types/react": "19.1.9",
|
|
39
|
+
"@types/react-dom": "19.1.7",
|
|
40
|
+
"cspell": "9.2.0",
|
|
41
|
+
"knip": "5.62.0",
|
|
41
42
|
"lefthook": "1.12.2",
|
|
42
43
|
"only-allow": "1.2.1",
|
|
43
44
|
"shellcheck": "3.1.0"
|
|
44
45
|
},
|
|
45
46
|
"dependencies": {
|
|
46
|
-
"
|
|
47
|
+
"better-auth": "1.3.4",
|
|
48
|
+
"js-cookie": "3.0.5",
|
|
49
|
+
"polish-plurals": "1.1.0",
|
|
50
|
+
"tinykeys": "3.0.0"
|
|
47
51
|
}
|
|
48
52
|
}
|
package/readme.md
CHANGED
|
@@ -26,17 +26,31 @@ Run the tests
|
|
|
26
26
|
src/
|
|
27
27
|
├── components
|
|
28
28
|
│ ├── button.tsx
|
|
29
|
+
│ ├── dialog.tsx
|
|
29
30
|
├── hooks
|
|
31
|
+
│ ├── use-click-outside.ts
|
|
32
|
+
│ ├── use-client-filter.ts
|
|
30
33
|
│ ├── use-exit-action.ts
|
|
31
34
|
│ ├── use-field.ts
|
|
35
|
+
│ ├── use-focus-shortcut.ts
|
|
32
36
|
│ ├── use-hover.ts
|
|
37
|
+
│ ├── use-language-selector.tsx
|
|
38
|
+
│ ├── use-meta-enter-submit.tsx
|
|
39
|
+
│ ├── use-scroll-lock.ts
|
|
40
|
+
│ ├── use-shortcuts.ts
|
|
33
41
|
│ └── use-toggle.ts
|
|
34
42
|
└── services
|
|
35
|
-
├──
|
|
43
|
+
├── auth-guard.ts
|
|
44
|
+
├── cookies.ts
|
|
45
|
+
├── copy-to-clipboard.ts
|
|
46
|
+
├── credentials.ts
|
|
36
47
|
├── etag.ts
|
|
48
|
+
├── exec.ts
|
|
37
49
|
├── field.ts
|
|
38
50
|
├── fields.ts
|
|
39
51
|
├── form.ts
|
|
52
|
+
├── get-safe-window.ts
|
|
53
|
+
├── noop.ts
|
|
40
54
|
├── pluralize.ts
|
|
41
55
|
├── rhythm.ts
|
|
42
56
|
├── translations.tsx
|