@bgord/ui 0.5.1 → 0.5.4

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,3 @@
1
1
  export * from "./button";
2
+ export * from "./dialog";
3
+ export * from "./revalidate-on-focus";
@@ -0,0 +1 @@
1
+ export declare function RevalidateOnFocus(): null;
@@ -1,3 +1,4 @@
1
+ export * from "./use-click-outside";
1
2
  export * from "./use-client-filter";
2
3
  export * from "./use-exit-action";
3
4
  export * from "./use-field";
@@ -5,5 +6,6 @@ export * from "./use-focus-shortcut";
5
6
  export * from "./use-hover";
6
7
  export * from "./use-language-selector";
7
8
  export * from "./use-meta-enter-submit";
9
+ export * from "./use-scroll-lock";
8
10
  export * from "./use-shortcuts";
9
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 @@
1
+ export declare function useScrollLock(enabled?: boolean): void;
package/dist/index.js CHANGED
@@ -6,6 +6,41 @@ function Button() {
6
6
  children: "Click"
7
7
  }, undefined, false, undefined, this);
8
8
  }
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")
17
+ return;
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]);
43
+ }
9
44
  // src/hooks/use-client-filter.ts
10
45
  import { useCallback, useMemo } from "react";
11
46
 
@@ -34,7 +69,7 @@ class Field {
34
69
  }
35
70
 
36
71
  // src/hooks/use-field.ts
37
- import { useEffect, useState } from "react";
72
+ import { useEffect as useEffect2, useState } from "react";
38
73
  import { useSearchParams } from "react-router";
39
74
  var useFieldStrategyEnum;
40
75
  ((useFieldStrategyEnum2) => {
@@ -51,7 +86,7 @@ function useField(config) {
51
86
  const candidate = new Field(value2);
52
87
  _setCurrentValue(candidate.get());
53
88
  };
54
- useEffect(() => {
89
+ useEffect2(() => {
55
90
  const current = new Field(currentValue);
56
91
  if (strategy === "params" /* params */) {
57
92
  if (current.isEmpty()) {
@@ -130,12 +165,12 @@ function useExitAction(options) {
130
165
  import { useCallback as useCallback2, useMemo as useMemo3, useRef } from "react";
131
166
 
132
167
  // src/hooks/use-shortcuts.ts
133
- import { useEffect as useEffect2, useMemo as useMemo2 } from "react";
168
+ import { useEffect as useEffect3, useMemo as useMemo2 } from "react";
134
169
  import { tinykeys } from "tinykeys";
135
170
  function useKeyboardShortcuts(config, options) {
136
171
  const enabled = options?.enabled ?? true;
137
172
  const memoizedConfig = useMemo2(() => config, [JSON.stringify(Object.keys(config))]);
138
- useEffect2(() => {
173
+ useEffect3(() => {
139
174
  if (!enabled)
140
175
  return;
141
176
  const unsubscribe = tinykeys(window, memoizedConfig);
@@ -215,7 +250,7 @@ function useHover({
215
250
  }
216
251
  // src/hooks/use-language-selector.tsx
217
252
  import Cookies from "js-cookie";
218
- import { useCallback as useCallback5, useEffect as useEffect3 } from "react";
253
+ import { useCallback as useCallback5, useEffect as useEffect4 } from "react";
219
254
  import { useRevalidator } from "react-router";
220
255
 
221
256
  // src/services/translations.tsx
@@ -293,7 +328,7 @@ function useLanguageSelector(supportedLanguages) {
293
328
  revalidator.revalidate();
294
329
  }
295
330
  }, [field.currentValue, field.changed]);
296
- useEffect3(() => {
331
+ useEffect4(() => {
297
332
  handleLanguageChange();
298
333
  }, [handleLanguageChange]);
299
334
  return field;
@@ -309,19 +344,138 @@ function useMetaEnterSubmit() {
309
344
  }, []);
310
345
  return useMemo4(() => ({ onKeyDown: handleMetaEnterSubmit }), [handleMetaEnterSubmit]);
311
346
  }
312
- // src/services/colorful.ts
313
- function Colorful(color) {
314
- const value = `var(--${color})`;
315
- const options = {
316
- color: { color: value },
317
- background: { background: value }
318
- };
319
- const style = {
320
- color: { style: { color: value } },
321
- background: { style: { background: value } }
322
- };
323
- return { ...options, style };
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]);
324
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/components/revalidate-on-focus.tsx
395
+ import { useEffect as useEffect7 } from "react";
396
+ import { useRevalidator as useRevalidator2 } from "react-router";
397
+ function RevalidateOnFocus() {
398
+ const revalidator = useRevalidator2();
399
+ useEffect7(() => {
400
+ const onFocus = () => revalidator.revalidate();
401
+ window.addEventListener("focus", onFocus);
402
+ return () => window.removeEventListener("focus", onFocus);
403
+ }, [revalidator]);
404
+ return null;
405
+ }
406
+ // src/services/auth-guard.ts
407
+ import { redirect } from "react-router";
408
+
409
+ // src/services/cookies.ts
410
+ class Cookies2 {
411
+ static extractFrom(request) {
412
+ return request.headers.get("cookie") ?? "";
413
+ }
414
+ }
415
+
416
+ // src/services/auth-guard.ts
417
+ class AuthGuard {
418
+ API_URL;
419
+ constructor(BASE_URL) {
420
+ this.API_URL = `${BASE_URL}/api/auth`;
421
+ }
422
+ async getServerSession(request) {
423
+ const cookie = Cookies2.extractFrom(request);
424
+ const res = await fetch(`${this.API_URL}/get-session`, {
425
+ headers: { cookie, accept: "application/json" }
426
+ });
427
+ if (!res.ok)
428
+ return null;
429
+ const session = await res.json();
430
+ return session;
431
+ }
432
+ async requireSession(request) {
433
+ const session = await this.getServerSession(request);
434
+ if (session?.user)
435
+ return session;
436
+ throw redirect("/");
437
+ }
438
+ async requireNoSession(request, target = "/home") {
439
+ const session = await this.getServerSession(request);
440
+ if (session?.user)
441
+ throw redirect(target);
442
+ }
443
+ async removeSession(request, target = "/login") {
444
+ const cookie = Cookies2.extractFrom(request);
445
+ const res = await fetch(`${import.meta.env.VITE_API_URL}/api/auth/sign-out`, {
446
+ method: "POST",
447
+ headers: { cookie }
448
+ });
449
+ const headers = new Headers;
450
+ res.headers.forEach((value, key) => {
451
+ if (key.toLowerCase() === "set-cookie")
452
+ headers.append("set-cookie", value);
453
+ });
454
+ throw redirect(target, { headers });
455
+ }
456
+ }
457
+ // src/services/noop.ts
458
+ function noop() {}
459
+
460
+ // src/services/copy-to-clipboard.ts
461
+ var defaultOnCopyToClipboardFailure = () => console.warn("Copying to clipboard not supported");
462
+ async function copyToClipboard(options) {
463
+ const onFailure = options.onFailure ?? defaultOnCopyToClipboardFailure;
464
+ const onSuccess = options.onSuccess ?? noop;
465
+ if (!navigator.clipboard)
466
+ onFailure();
467
+ try {
468
+ await navigator.clipboard.writeText(options.text);
469
+ onSuccess();
470
+ } catch (error) {
471
+ onFailure(error);
472
+ }
473
+ }
474
+ // src/services/credentials.ts
475
+ var Credentials = {
476
+ email: { inputMode: "email", autoComplete: "email", autoCapitalize: "none", spellCheck: "false" },
477
+ password: { new: { autoComplete: "new-password" }, current: { autoComplete: "current-password" } }
478
+ };
325
479
  // src/services/etag.ts
326
480
  class ETag {
327
481
  static fromRevision(revision) {
@@ -423,6 +577,7 @@ class WeakETag {
423
577
  export {
424
578
  useTranslations,
425
579
  useToggle,
580
+ useScrollLock,
426
581
  usePluralize,
427
582
  useMetaEnterSubmit,
428
583
  useLanguageSelector,
@@ -434,18 +589,25 @@ export {
434
589
  useField,
435
590
  useExitAction,
436
591
  useClientFilter,
592
+ useClickOutside,
437
593
  pluralize,
594
+ noop,
438
595
  getSafeWindow,
439
596
  extractUseToggle,
440
597
  exec,
598
+ copyToClipboard,
441
599
  WeakETag,
442
600
  TranslationsContext,
443
601
  Rhythm,
602
+ RevalidateOnFocus,
444
603
  LocalFields,
445
604
  Form,
446
605
  Fields,
447
606
  Field,
448
607
  ETag,
449
- Colorful,
450
- Button
608
+ Dialog,
609
+ Credentials,
610
+ Cookies2 as Cookies,
611
+ Button,
612
+ AuthGuard
451
613
  };
@@ -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 {};
@@ -1,10 +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";
3
6
  export * from "./exec";
4
7
  export * from "./field";
5
8
  export * from "./fields";
6
9
  export * from "./form";
7
10
  export * from "./get-safe-window";
11
+ export * from "./noop";
8
12
  export * from "./pluralize";
9
13
  export * from "./rhythm";
10
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.5.1",
3
+ "version": "0.5.4",
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",
@@ -29,21 +29,22 @@
29
29
  "@commitlint/cli": "19.8.1",
30
30
  "@commitlint/config-conventional": "19.8.1",
31
31
  "@happy-dom/global-registrator": "18.0.1",
32
- "@testing-library/dom": "10.4.0",
33
- "@testing-library/jest-dom": "6.6.3",
32
+ "@testing-library/dom": "10.4.1",
33
+ "@testing-library/jest-dom": "6.6.4",
34
34
  "@testing-library/react": "16.3.0",
35
35
  "@testing-library/user-event": "14.6.1",
36
- "@types/bun": "1.2.18",
36
+ "@types/bun": "1.2.19",
37
37
  "@types/js-cookie": "^3.0.6",
38
- "@types/react": "19.1.8",
39
- "@types/react-dom": "19.1.6",
40
- "cspell": "9.1.5",
41
- "knip": "5.61.3",
38
+ "@types/react": "19.1.9",
39
+ "@types/react-dom": "19.1.7",
40
+ "cspell": "9.2.0",
41
+ "knip": "5.62.0",
42
42
  "lefthook": "1.12.2",
43
43
  "only-allow": "1.2.1",
44
44
  "shellcheck": "3.1.0"
45
45
  },
46
46
  "dependencies": {
47
+ "better-auth": "1.3.4",
47
48
  "js-cookie": "3.0.5",
48
49
  "polish-plurals": "1.1.0",
49
50
  "tinykeys": "3.0.0"
package/readme.md CHANGED
@@ -26,7 +26,10 @@ Run the tests
26
26
  src/
27
27
  ├── components
28
28
  │   ├── button.tsx
29
+ │   ├── dialog.tsx
30
+ │   └── revalidate-on-focus.tsx
29
31
  ├── hooks
32
+ │   ├── use-click-outside.ts
30
33
  │   ├── use-client-filter.ts
31
34
  │   ├── use-exit-action.ts
32
35
  │   ├── use-field.ts
@@ -34,16 +37,21 @@ src/
34
37
  │   ├── use-hover.ts
35
38
  │   ├── use-language-selector.tsx
36
39
  │   ├── use-meta-enter-submit.tsx
40
+ │   ├── use-scroll-lock.ts
37
41
  │   ├── use-shortcuts.ts
38
42
  │   └── use-toggle.ts
39
43
  └── services
40
- ├── colorful.ts
44
+ ├── auth-guard.ts
45
+ ├── cookies.ts
46
+ ├── copy-to-clipboard.ts
47
+ ├── credentials.ts
41
48
  ├── etag.ts
42
49
  ├── exec.ts
43
50
  ├── field.ts
44
51
  ├── fields.ts
45
52
  ├── form.ts
46
53
  ├── get-safe-window.ts
54
+ ├── noop.ts
47
55
  ├── pluralize.ts
48
56
  ├── rhythm.ts
49
57
  ├── translations.tsx