@bgord/ui 0.5.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,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,126 @@ 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]);
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
+ }
324
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
+ };
325
467
  // src/services/etag.ts
326
468
  class ETag {
327
469
  static fromRevision(revision) {
@@ -423,6 +565,7 @@ class WeakETag {
423
565
  export {
424
566
  useTranslations,
425
567
  useToggle,
568
+ useScrollLock,
426
569
  usePluralize,
427
570
  useMetaEnterSubmit,
428
571
  useLanguageSelector,
@@ -434,10 +577,13 @@ export {
434
577
  useField,
435
578
  useExitAction,
436
579
  useClientFilter,
580
+ useClickOutside,
437
581
  pluralize,
582
+ noop,
438
583
  getSafeWindow,
439
584
  extractUseToggle,
440
585
  exec,
586
+ copyToClipboard,
441
587
  WeakETag,
442
588
  TranslationsContext,
443
589
  Rhythm,
@@ -446,6 +592,9 @@ export {
446
592
  Fields,
447
593
  Field,
448
594
  ETag,
449
- Colorful,
450
- Button
595
+ Dialog,
596
+ Credentials,
597
+ Cookies2 as Cookies,
598
+ Button,
599
+ AuthGuard
451
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 {};
@@ -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.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",
@@ -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,9 @@ Run the tests
26
26
  src/
27
27
  ├── components
28
28
  │   ├── button.tsx
29
+ │   ├── dialog.tsx
29
30
  ├── hooks
31
+ │   ├── use-click-outside.ts
30
32
  │   ├── use-client-filter.ts
31
33
  │   ├── use-exit-action.ts
32
34
  │   ├── use-field.ts
@@ -34,16 +36,21 @@ src/
34
36
  │   ├── use-hover.ts
35
37
  │   ├── use-language-selector.tsx
36
38
  │   ├── use-meta-enter-submit.tsx
39
+ │   ├── use-scroll-lock.ts
37
40
  │   ├── use-shortcuts.ts
38
41
  │   └── use-toggle.ts
39
42
  └── services
40
- ├── colorful.ts
43
+ ├── auth-guard.ts
44
+ ├── cookies.ts
45
+ ├── copy-to-clipboard.ts
46
+ ├── credentials.ts
41
47
  ├── etag.ts
42
48
  ├── exec.ts
43
49
  ├── field.ts
44
50
  ├── fields.ts
45
51
  ├── form.ts
46
52
  ├── get-safe-window.ts
53
+ ├── noop.ts
47
54
  ├── pluralize.ts
48
55
  ├── rhythm.ts
49
56
  ├── translations.tsx