@better-forge/ui 0.1.0 → 0.1.1

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.
Files changed (51) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +13 -13
  3. package/icons.json +170 -170
  4. package/package.json +71 -71
  5. package/src/api/client.ts +139 -139
  6. package/src/components/ConfirmIdentityModal.tsx +82 -82
  7. package/src/components/ConfirmModal.tsx +83 -83
  8. package/src/components/CopyButton.tsx +71 -71
  9. package/src/components/CredentialSection.tsx +270 -270
  10. package/src/components/ErrorBoundary.tsx +47 -47
  11. package/src/components/ModalShell.tsx +160 -160
  12. package/src/components/ResetTwoFactorModal.tsx +74 -74
  13. package/src/components/SidePanel.tsx +96 -96
  14. package/src/components/SingleSignOnModal.tsx +161 -161
  15. package/src/components/SsoConfirmation.tsx +96 -96
  16. package/src/components/VaultTypePicker.tsx +131 -131
  17. package/src/components/WhatsNew.tsx +84 -84
  18. package/src/components/formParts.tsx +59 -59
  19. package/src/components/ui/Alert.tsx +45 -45
  20. package/src/components/ui/DataTable.tsx +225 -225
  21. package/src/components/ui/Field.tsx +63 -63
  22. package/src/components/ui/PageHeader.tsx +36 -36
  23. package/src/components/ui/Pagination.tsx +37 -37
  24. package/src/components/ui/QuickStartPanel.tsx +57 -57
  25. package/src/components/ui/StateViews.tsx +135 -135
  26. package/src/components/ui/Switch.tsx +65 -65
  27. package/src/components/ui/Toast.tsx +104 -104
  28. package/src/components/ui/index.ts +9 -9
  29. package/src/config.ts +46 -46
  30. package/src/hooks/useDarkMode.ts +38 -38
  31. package/src/hooks/useDebouncedValue.ts +22 -22
  32. package/src/index.ts +42 -42
  33. package/src/lib/changelog.ts +93 -93
  34. package/src/lib/dateFormat.ts +90 -90
  35. package/src/navigation.ts +6 -6
  36. package/src/pages/ApiTokens.tsx +361 -361
  37. package/src/pages/AuditLog.tsx +388 -388
  38. package/src/pages/Backups.tsx +170 -170
  39. package/src/pages/ChangePassword.tsx +84 -84
  40. package/src/pages/Login.tsx +275 -275
  41. package/src/pages/Settings.tsx +264 -264
  42. package/src/pages/Setup.tsx +438 -438
  43. package/src/pages/SsoConfirm.tsx +26 -26
  44. package/src/pages/SsoTestResult.tsx +104 -104
  45. package/src/pages/Users.tsx +426 -426
  46. package/src/pages/Vault.tsx +1036 -1036
  47. package/src/shell/AdminShell.tsx +256 -256
  48. package/src/shell/guards.tsx +88 -88
  49. package/styles/base.css +521 -521
  50. package/styles/icons.css +4 -4
  51. package/tailwind-preset.js +79 -79
@@ -1,71 +1,71 @@
1
- import { useEffect, useRef, useState } from 'react'
2
-
3
- interface CopyButtonProps {
4
- value: string
5
- label?: string | undefined
6
- copiedLabel?: string | undefined
7
- failedLabel?: string | undefined
8
- /** Show only the icon; the label still names the button for assistive technology. */
9
- iconOnly?: boolean | undefined
10
- }
11
-
12
- type CopyState = 'idle' | 'copied' | 'failed'
13
-
14
- /** Clipboard API first; outside a secure context (plain-HTTP deployments) fall back to a hidden textarea. */
15
- async function writeClipboard(value: string) {
16
- if (navigator.clipboard?.writeText) {
17
- await navigator.clipboard.writeText(value)
18
- return
19
- }
20
- const textarea = document.createElement('textarea')
21
- textarea.value = value
22
- textarea.setAttribute('readonly', '')
23
- textarea.style.position = 'fixed'
24
- textarea.style.opacity = '0'
25
- document.body.appendChild(textarea)
26
- textarea.select()
27
- try {
28
- if (!document.execCommand('copy')) throw new Error('Copy command was rejected')
29
- } finally {
30
- textarea.remove()
31
- }
32
- }
33
-
34
- export function CopyButton({ value, label = 'Copy', copiedLabel = 'Copied!', failedLabel = 'Copy failed', iconOnly = false }: CopyButtonProps) {
35
- const [state, setState] = useState<CopyState>('idle')
36
- const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
37
-
38
- useEffect(() => () => clearTimeout(timer.current), [])
39
-
40
- async function copy() {
41
- let next: CopyState
42
- try {
43
- await writeClipboard(value)
44
- next = 'copied'
45
- } catch {
46
- next = 'failed'
47
- }
48
- setState(next)
49
- clearTimeout(timer.current)
50
- timer.current = setTimeout(() => setState('idle'), 2000)
51
- }
52
-
53
- const text = state === 'copied' ? copiedLabel : state === 'failed' ? failedLabel : label
54
-
55
- return (
56
- <button
57
- type="button"
58
- onClick={() => void copy()}
59
- aria-label={text}
60
- title={iconOnly ? text : undefined}
61
- className="copy-feedback-button flex items-center gap-1.5 text-xs px-3 py-2 rounded-lg font-semibold"
62
- data-copied={state === 'copied' || undefined}
63
- data-failed={state === 'failed' || undefined}
64
- >
65
- <span className="material-symbols-outlined" aria-hidden="true" style={{ fontSize: '14px' }}>
66
- {state === 'copied' ? 'check' : state === 'failed' ? 'error' : 'content_copy'}
67
- </span>
68
- {!iconOnly && <span aria-live="polite">{text}</span>}
69
- </button>
70
- )
71
- }
1
+ import { useEffect, useRef, useState } from 'react'
2
+
3
+ interface CopyButtonProps {
4
+ value: string
5
+ label?: string | undefined
6
+ copiedLabel?: string | undefined
7
+ failedLabel?: string | undefined
8
+ /** Show only the icon; the label still names the button for assistive technology. */
9
+ iconOnly?: boolean | undefined
10
+ }
11
+
12
+ type CopyState = 'idle' | 'copied' | 'failed'
13
+
14
+ /** Clipboard API first; outside a secure context (plain-HTTP deployments) fall back to a hidden textarea. */
15
+ async function writeClipboard(value: string) {
16
+ if (navigator.clipboard?.writeText) {
17
+ await navigator.clipboard.writeText(value)
18
+ return
19
+ }
20
+ const textarea = document.createElement('textarea')
21
+ textarea.value = value
22
+ textarea.setAttribute('readonly', '')
23
+ textarea.style.position = 'fixed'
24
+ textarea.style.opacity = '0'
25
+ document.body.appendChild(textarea)
26
+ textarea.select()
27
+ try {
28
+ if (!document.execCommand('copy')) throw new Error('Copy command was rejected')
29
+ } finally {
30
+ textarea.remove()
31
+ }
32
+ }
33
+
34
+ export function CopyButton({ value, label = 'Copy', copiedLabel = 'Copied!', failedLabel = 'Copy failed', iconOnly = false }: CopyButtonProps) {
35
+ const [state, setState] = useState<CopyState>('idle')
36
+ const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
37
+
38
+ useEffect(() => () => clearTimeout(timer.current), [])
39
+
40
+ async function copy() {
41
+ let next: CopyState
42
+ try {
43
+ await writeClipboard(value)
44
+ next = 'copied'
45
+ } catch {
46
+ next = 'failed'
47
+ }
48
+ setState(next)
49
+ clearTimeout(timer.current)
50
+ timer.current = setTimeout(() => setState('idle'), 2000)
51
+ }
52
+
53
+ const text = state === 'copied' ? copiedLabel : state === 'failed' ? failedLabel : label
54
+
55
+ return (
56
+ <button
57
+ type="button"
58
+ onClick={() => void copy()}
59
+ aria-label={text}
60
+ title={iconOnly ? text : undefined}
61
+ className="copy-feedback-button flex items-center gap-1.5 text-xs px-3 py-2 rounded-lg font-semibold"
62
+ data-copied={state === 'copied' || undefined}
63
+ data-failed={state === 'failed' || undefined}
64
+ >
65
+ <span className="material-symbols-outlined" aria-hidden="true" style={{ fontSize: '14px' }}>
66
+ {state === 'copied' ? 'check' : state === 'failed' ? 'error' : 'content_copy'}
67
+ </span>
68
+ {!iconOnly && <span aria-live="polite">{text}</span>}
69
+ </button>
70
+ )
71
+ }