@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.
@@ -0,0 +1,4 @@
1
+ import React from "react";
2
+ import * as hooks from "../hooks";
3
+ export type DialogPropsType = hooks.UseToggleReturnType & React.JSX.IntrinsicElements["dialog"];
4
+ export declare function Dialog(props: DialogPropsType): import("react/jsx-runtime").JSX.Element;
@@ -1 +1,2 @@
1
1
  export * from "./button";
2
+ export * from "./dialog";
@@ -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,2 @@
1
+ import { RefObject } from "react";
2
+ export declare function useClickOutside<T extends HTMLElement>(ref: RefObject<T | null>, handler: (e: MouseEvent | TouchEvent) => void): void;
@@ -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,8 @@
1
+ import { Ref } from "react";
2
+ type FocusableElement = HTMLElement & {
3
+ focus(): void;
4
+ };
5
+ export declare function useFocusKeyboardShortcut<T extends FocusableElement = HTMLInputElement>(shortcut: string): {
6
+ ref: Ref<T>;
7
+ };
8
+ export {};
@@ -0,0 +1,4 @@
1
+ import { useClientFilterReturnType } from "./use-client-filter";
2
+ type LanguageType = string;
3
+ export declare function useLanguageSelector(supportedLanguages: Record<LanguageType, LanguageType>): useClientFilterReturnType<LanguageType>;
4
+ export {};
@@ -0,0 +1,3 @@
1
+ export declare function useMetaEnterSubmit(): {
2
+ onKeyDown: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void;
3
+ };
@@ -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/hooks/use-exit-action.ts
10
- import React from "react";
11
- function useExitAction(options) {
12
- const [phase, setPhase] = React.useState("idle" /* idle */);
13
- const trigger = (event) => {
14
- event.preventDefault();
15
- if (phase === "idle")
16
- setPhase("exiting" /* exiting */);
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
- options.actionFn();
22
- setPhase("gone" /* gone */);
23
- };
24
- const attach = phase === "exiting" ? { "data-animation": options.animation, onAnimationEnd } : undefined;
25
- return { visible: phase !== "gone", attach, trigger };
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-field.ts
28
- import { useEffect, useState } from "react";
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
- useEffect(() => {
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 = useRef(null);
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 = useCallback((node) => {
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/services/colorful.ts
167
- function Colorful(color) {
168
- const value = `var(--${color})`;
169
- const options = {
170
- color: { color: value },
171
- background: { background: value }
172
- };
173
- const style = {
174
- color: { style: { color: value } },
175
- background: { style: { background: value } }
176
- };
177
- return { ...options, style };
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/pluralize.ts
227
- import { polishPlurals } from "polish-plurals";
228
- function pluralize(options) {
229
- if (options.language === "en" /* en */) {
230
- const plural = options.plural ?? `${options.singular}s`;
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
- Colorful,
338
- Button
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,3 @@
1
+ export declare class Cookies {
2
+ static extractFrom(request: Request): string;
3
+ }
@@ -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,9 @@
1
+ type CredentialsType = {
2
+ email: React.JSX.IntrinsicElements["input"];
3
+ password: {
4
+ new: React.JSX.IntrinsicElements["input"];
5
+ current: React.JSX.IntrinsicElements["input"];
6
+ };
7
+ };
8
+ export declare const Credentials: CredentialsType;
9
+ export {};
@@ -0,0 +1,3 @@
1
+ type ExecFunctionListType = Array<() => void>;
2
+ export declare function exec(list: ExecFunctionListType): () => void;
3
+ export {};
@@ -0,0 +1 @@
1
+ export declare function getSafeWindow(): (Window & typeof globalThis) | undefined;
@@ -1,8 +1,14 @@
1
- export * from "./colorful";
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.4.1",
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.0",
16
- "react-dom": "19.1.0",
17
- "react-router": "7.6.3"
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
- "cspell": "9.1.5",
40
- "knip": "5.61.3",
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
- "polish-plurals": "^1.1.0"
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
- ├── colorful.ts
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