@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,59 +1,59 @@
1
- import type { ReactNode } from 'react'
2
-
3
- // Building blocks shared by the forms of the interface and the sections extracted from them.
4
-
5
- export interface VaultSummary { id: number; name: string }
6
- export interface SecretSummary { id: number; name: string; type: 'userpass' | 'value' | 'json' }
7
-
8
- /** What every vault-aware credential picker needs to list vaults and lazily load their secrets. */
9
- export interface VaultPickerProps {
10
- vaults: { id: number; name: string }[]
11
- secretsByVault: Record<number, { id: number; name: string; type: string }[]>
12
- onLoadSecrets: (vaultId: number) => Promise<void>
13
- }
14
-
15
- // Fields that can be sourced from a json vault secret, per context
16
- export const JSON_MAPPING_FIELDS: Record<'basic' | 'oauth2' | 'cas' | 'database', { key: string; label: string }[]> = {
17
- basic: [{ key: 'username', label: 'Username' }, { key: 'password', label: 'Password' }],
18
- oauth2: [{ key: 'clientId', label: 'Client ID' }, { key: 'clientSecret', label: 'Client Secret' }],
19
- cas: [{ key: 'username', label: 'Username' }, { key: 'password', label: 'Password' }],
20
- database: [{ key: 'username', label: 'Username' }, { key: 'password', label: 'Password' }],
21
- }
22
-
23
- /** The shared caps-style form field: its label is programmatically tied to the control it wraps. */
24
- export { Field } from './ui'
25
-
26
- export function SectionDivider({ label }: { label: string }) {
27
- return (
28
- <div className="flex items-center gap-3 pt-1">
29
- <div style={{ flex: 1, borderTop: '1px solid var(--m3-outline-variant)' }} />
30
- <span className="font-mono text-xs uppercase tracking-wider" style={{ color: 'var(--m3-secondary)' }}>{label}</span>
31
- <div style={{ flex: 1, borderTop: '1px solid var(--m3-outline-variant)' }} />
32
- </div>
33
- )
34
- }
35
-
36
- /** Tinted callout: `info` uses the accent blue, `warning` the degraded amber (text colour, not the bar colour). */
37
- export function Note({ tone, children }: { tone: 'info' | 'warning'; children: ReactNode }) {
38
- const color = tone === 'info' ? 'var(--m3-on-primary-container)' : 'var(--m3-degraded)'
39
- const tint = tone === 'info' ? color : 'var(--m3-degraded-bar)'
40
- return (
41
- <div
42
- className="flex items-start gap-2 rounded-lg px-3 py-2"
43
- style={{ background: `color-mix(in srgb, ${tint} 8%, transparent)`, border: `1px solid color-mix(in srgb, ${tint} 20%, transparent)` }}
44
- >
45
- {tone === 'info'
46
- ? <span className="material-symbols-outlined" aria-hidden="true" style={{ fontSize: '16px', color, lineHeight: '20px' }}>info</span>
47
- : <span aria-hidden="true" style={{ color, lineHeight: '20px' }}>⚠</span>}
48
- <p className="text-xs" style={{ color: 'var(--m3-on-surface-variant)' }}>{children}</p>
49
- </div>
50
- )
51
- }
52
-
53
- /** Adds or removes one id from a selection set without mutating it. */
54
- export function toggleInSet(set: Set<number>, id: number, on: boolean): Set<number> {
55
- const next = new Set(set)
56
- if (on) next.add(id)
57
- else next.delete(id)
58
- return next
59
- }
1
+ import type { ReactNode } from 'react'
2
+
3
+ // Building blocks shared by the forms of the interface and the sections extracted from them.
4
+
5
+ export interface VaultSummary { id: number; name: string }
6
+ export interface SecretSummary { id: number; name: string; type: 'userpass' | 'value' | 'json' }
7
+
8
+ /** What every vault-aware credential picker needs to list vaults and lazily load their secrets. */
9
+ export interface VaultPickerProps {
10
+ vaults: { id: number; name: string }[]
11
+ secretsByVault: Record<number, { id: number; name: string; type: string }[]>
12
+ onLoadSecrets: (vaultId: number) => Promise<void>
13
+ }
14
+
15
+ // Fields that can be sourced from a json vault secret, per context
16
+ export const JSON_MAPPING_FIELDS: Record<'basic' | 'oauth2' | 'cas' | 'database', { key: string; label: string }[]> = {
17
+ basic: [{ key: 'username', label: 'Username' }, { key: 'password', label: 'Password' }],
18
+ oauth2: [{ key: 'clientId', label: 'Client ID' }, { key: 'clientSecret', label: 'Client Secret' }],
19
+ cas: [{ key: 'username', label: 'Username' }, { key: 'password', label: 'Password' }],
20
+ database: [{ key: 'username', label: 'Username' }, { key: 'password', label: 'Password' }],
21
+ }
22
+
23
+ /** The shared caps-style form field: its label is programmatically tied to the control it wraps. */
24
+ export { Field } from './ui'
25
+
26
+ export function SectionDivider({ label }: { label: string }) {
27
+ return (
28
+ <div className="flex items-center gap-3 pt-1">
29
+ <div style={{ flex: 1, borderTop: '1px solid var(--m3-outline-variant)' }} />
30
+ <span className="font-mono text-xs uppercase tracking-wider" style={{ color: 'var(--m3-secondary)' }}>{label}</span>
31
+ <div style={{ flex: 1, borderTop: '1px solid var(--m3-outline-variant)' }} />
32
+ </div>
33
+ )
34
+ }
35
+
36
+ /** Tinted callout: `info` uses the accent blue, `warning` the degraded amber (text colour, not the bar colour). */
37
+ export function Note({ tone, children }: { tone: 'info' | 'warning'; children: ReactNode }) {
38
+ const color = tone === 'info' ? 'var(--m3-on-primary-container)' : 'var(--m3-degraded)'
39
+ const tint = tone === 'info' ? color : 'var(--m3-degraded-bar)'
40
+ return (
41
+ <div
42
+ className="flex items-start gap-2 rounded-lg px-3 py-2"
43
+ style={{ background: `color-mix(in srgb, ${tint} 8%, transparent)`, border: `1px solid color-mix(in srgb, ${tint} 20%, transparent)` }}
44
+ >
45
+ {tone === 'info'
46
+ ? <span className="material-symbols-outlined" aria-hidden="true" style={{ fontSize: '16px', color, lineHeight: '20px' }}>info</span>
47
+ : <span aria-hidden="true" style={{ color, lineHeight: '20px' }}>⚠</span>}
48
+ <p className="text-xs" style={{ color: 'var(--m3-on-surface-variant)' }}>{children}</p>
49
+ </div>
50
+ )
51
+ }
52
+
53
+ /** Adds or removes one id from a selection set without mutating it. */
54
+ export function toggleInSet(set: Set<number>, id: number, on: boolean): Set<number> {
55
+ const next = new Set(set)
56
+ if (on) next.add(id)
57
+ else next.delete(id)
58
+ return next
59
+ }
@@ -1,45 +1,45 @@
1
- import clsx from 'clsx'
2
- import type { CSSProperties, ReactNode } from 'react'
3
-
4
- export type AlertTone = 'error' | 'success' | 'info' | 'warning'
5
-
6
- const TONES: Record<AlertTone, { icon: string; style: CSSProperties }> = {
7
- error: { icon: 'error', style: { background: 'var(--m3-down-bg)', color: 'var(--m3-down)', borderColor: 'color-mix(in srgb, var(--m3-down) 30%, transparent)' } },
8
- success: { icon: 'check_circle', style: { background: 'var(--m3-up-bg)', color: 'var(--m3-up)', borderColor: 'color-mix(in srgb, var(--m3-up) 30%, transparent)' } },
9
- warning: { icon: 'warning', style: { background: 'var(--m3-degraded-bg)', color: 'var(--m3-degraded)', borderColor: 'color-mix(in srgb, var(--m3-degraded) 30%, transparent)' } },
10
- info: { icon: 'info', style: { background: 'var(--m3-surface-container-high)', color: 'var(--m3-on-surface)', borderColor: 'var(--m3-outline-variant)' } },
11
- }
12
-
13
- interface AlertProps {
14
- tone: AlertTone
15
- children: ReactNode
16
- /** Optional bold first line. */
17
- title?: ReactNode | undefined
18
- /** Renders a × button (aria-label "Dismiss") that calls this. */
19
- onDismiss?: (() => void) | undefined
20
- className?: string | undefined
21
- }
22
-
23
- /** Inline, token-coloured message box. Errors are announced assertively (role="alert"); other tones politely. */
24
- export function Alert({ tone, children, title, onDismiss, className }: AlertProps) {
25
- const { icon, style } = TONES[tone]
26
- return (
27
- <div
28
- role={tone === 'error' ? 'alert' : 'status'}
29
- data-tone={tone}
30
- className={clsx('flex items-start gap-3 rounded-xl border px-4 py-3 text-sm', className)}
31
- style={style}
32
- >
33
- <span className="material-symbols-outlined flex-shrink-0 mt-px" aria-hidden="true" style={{ fontSize: '18px' }}>{icon}</span>
34
- <div className="flex-1 min-w-0 break-words">
35
- {title && <p className="font-semibold">{title}</p>}
36
- <div className={title ? 'mt-0.5' : undefined}>{children}</div>
37
- </div>
38
- {onDismiss && (
39
- <button type="button" onClick={onDismiss} aria-label="Dismiss" className="btn-icon -my-1 -mr-2" style={{ color: 'inherit' }}>
40
- <span className="material-symbols-outlined" aria-hidden="true" style={{ fontSize: '18px' }}>close</span>
41
- </button>
42
- )}
43
- </div>
44
- )
45
- }
1
+ import clsx from 'clsx'
2
+ import type { CSSProperties, ReactNode } from 'react'
3
+
4
+ export type AlertTone = 'error' | 'success' | 'info' | 'warning'
5
+
6
+ const TONES: Record<AlertTone, { icon: string; style: CSSProperties }> = {
7
+ error: { icon: 'error', style: { background: 'var(--m3-down-bg)', color: 'var(--m3-down)', borderColor: 'color-mix(in srgb, var(--m3-down) 30%, transparent)' } },
8
+ success: { icon: 'check_circle', style: { background: 'var(--m3-up-bg)', color: 'var(--m3-up)', borderColor: 'color-mix(in srgb, var(--m3-up) 30%, transparent)' } },
9
+ warning: { icon: 'warning', style: { background: 'var(--m3-degraded-bg)', color: 'var(--m3-degraded)', borderColor: 'color-mix(in srgb, var(--m3-degraded) 30%, transparent)' } },
10
+ info: { icon: 'info', style: { background: 'var(--m3-surface-container-high)', color: 'var(--m3-on-surface)', borderColor: 'var(--m3-outline-variant)' } },
11
+ }
12
+
13
+ interface AlertProps {
14
+ tone: AlertTone
15
+ children: ReactNode
16
+ /** Optional bold first line. */
17
+ title?: ReactNode | undefined
18
+ /** Renders a × button (aria-label "Dismiss") that calls this. */
19
+ onDismiss?: (() => void) | undefined
20
+ className?: string | undefined
21
+ }
22
+
23
+ /** Inline, token-coloured message box. Errors are announced assertively (role="alert"); other tones politely. */
24
+ export function Alert({ tone, children, title, onDismiss, className }: AlertProps) {
25
+ const { icon, style } = TONES[tone]
26
+ return (
27
+ <div
28
+ role={tone === 'error' ? 'alert' : 'status'}
29
+ data-tone={tone}
30
+ className={clsx('flex items-start gap-3 rounded-xl border px-4 py-3 text-sm', className)}
31
+ style={style}
32
+ >
33
+ <span className="material-symbols-outlined flex-shrink-0 mt-px" aria-hidden="true" style={{ fontSize: '18px' }}>{icon}</span>
34
+ <div className="flex-1 min-w-0 break-words">
35
+ {title && <p className="font-semibold">{title}</p>}
36
+ <div className={title ? 'mt-0.5' : undefined}>{children}</div>
37
+ </div>
38
+ {onDismiss && (
39
+ <button type="button" onClick={onDismiss} aria-label="Dismiss" className="btn-icon -my-1 -mr-2" style={{ color: 'inherit' }}>
40
+ <span className="material-symbols-outlined" aria-hidden="true" style={{ fontSize: '18px' }}>close</span>
41
+ </button>
42
+ )}
43
+ </div>
44
+ )
45
+ }