@torch-ui/solid 0.1.3

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 (118) hide show
  1. package/README.md +166 -0
  2. package/package.json +67 -0
  3. package/src/components/actions/Button.tsx +612 -0
  4. package/src/components/actions/ButtonGroup.tsx +728 -0
  5. package/src/components/actions/Copy.tsx +98 -0
  6. package/src/components/actions/DarkModeToggle.tsx +80 -0
  7. package/src/components/actions/Link.tsx +37 -0
  8. package/src/components/actions/index.ts +19 -0
  9. package/src/components/actions/useCopyToClipboard.ts +90 -0
  10. package/src/components/charts/Chart.tsx +331 -0
  11. package/src/components/charts/Sparkline.tsx +156 -0
  12. package/src/components/charts/index.ts +13 -0
  13. package/src/components/data-display/Avatar.tsx +208 -0
  14. package/src/components/data-display/AvatarGroup.tsx +228 -0
  15. package/src/components/data-display/Badge.tsx +70 -0
  16. package/src/components/data-display/Carousel.tsx +214 -0
  17. package/src/components/data-display/ColorSwatch.tsx +56 -0
  18. package/src/components/data-display/DataTable.tsx +886 -0
  19. package/src/components/data-display/EmptyState.tsx +61 -0
  20. package/src/components/data-display/Image.tsx +277 -0
  21. package/src/components/data-display/Kbd.tsx +114 -0
  22. package/src/components/data-display/Persona.tsx +78 -0
  23. package/src/components/data-display/StatCard.tsx +338 -0
  24. package/src/components/data-display/Table.tsx +147 -0
  25. package/src/components/data-display/Tag.tsx +91 -0
  26. package/src/components/data-display/Timeline.tsx +200 -0
  27. package/src/components/data-display/TreeView.tsx +172 -0
  28. package/src/components/data-display/Video.tsx +95 -0
  29. package/src/components/data-display/avatar-utils.ts +32 -0
  30. package/src/components/data-display/index.ts +81 -0
  31. package/src/components/feedback/Loading.tsx +159 -0
  32. package/src/components/feedback/Progress.tsx +321 -0
  33. package/src/components/feedback/Skeleton.tsx +62 -0
  34. package/src/components/feedback/SkeletonBlocks.tsx +222 -0
  35. package/src/components/feedback/Toast.tsx +648 -0
  36. package/src/components/feedback/index.ts +44 -0
  37. package/src/components/feedback/password/PasswordStrengthIndicator.tsx +232 -0
  38. package/src/components/feedback/password/password-strength.ts +115 -0
  39. package/src/components/feedback/password/password-validation-data.ts +66 -0
  40. package/src/components/feedback/password/password-validation.ts +93 -0
  41. package/src/components/forms/Autocomplete.tsx +268 -0
  42. package/src/components/forms/Checkbox.tsx +155 -0
  43. package/src/components/forms/CodeInput.tsx +237 -0
  44. package/src/components/forms/ColorPicker/ColorPicker.tsx +469 -0
  45. package/src/components/forms/ColorPicker/color-utils.ts +75 -0
  46. package/src/components/forms/ColorPicker/index.ts +2 -0
  47. package/src/components/forms/DatePicker.tsx +516 -0
  48. package/src/components/forms/DateRangePicker.tsx +464 -0
  49. package/src/components/forms/FieldPicker.tsx +64 -0
  50. package/src/components/forms/FileUpload.tsx +614 -0
  51. package/src/components/forms/FilterBuilder/FilterGroupBlock.ts +6 -0
  52. package/src/components/forms/FilterBuilder.tsx +16 -0
  53. package/src/components/forms/FilterRuleRow.tsx +68 -0
  54. package/src/components/forms/Input.tsx +200 -0
  55. package/src/components/forms/MultiSelect.tsx +361 -0
  56. package/src/components/forms/NumberField.tsx +145 -0
  57. package/src/components/forms/RadioGroup.tsx +135 -0
  58. package/src/components/forms/RelativeDateDefaultInput.tsx +62 -0
  59. package/src/components/forms/ReorderableList.tsx +163 -0
  60. package/src/components/forms/Select.tsx +268 -0
  61. package/src/components/forms/Slider.tsx +260 -0
  62. package/src/components/forms/Switch.tsx +135 -0
  63. package/src/components/forms/TextArea.tsx +202 -0
  64. package/src/components/forms/ViewCustomizer.tsx +44 -0
  65. package/src/components/forms/index.ts +43 -0
  66. package/src/components/layout/Accordion.tsx +110 -0
  67. package/src/components/layout/Alert.tsx +156 -0
  68. package/src/components/layout/BlockQuote.tsx +70 -0
  69. package/src/components/layout/Card.tsx +166 -0
  70. package/src/components/layout/CodeBlock/CodeBlock.tsx +477 -0
  71. package/src/components/layout/CodeBlock/code-block-tokens.css +104 -0
  72. package/src/components/layout/CodeBlock/prism.ts +81 -0
  73. package/src/components/layout/Collapsible.tsx +84 -0
  74. package/src/components/layout/Container.tsx +55 -0
  75. package/src/components/layout/Divider.tsx +64 -0
  76. package/src/components/layout/Form.tsx +39 -0
  77. package/src/components/layout/FormActions.tsx +50 -0
  78. package/src/components/layout/Grid.tsx +53 -0
  79. package/src/components/layout/PageHeading.tsx +46 -0
  80. package/src/components/layout/PromptWithAction.tsx +49 -0
  81. package/src/components/layout/Section.tsx +60 -0
  82. package/src/components/layout/TablePanel.tsx +24 -0
  83. package/src/components/layout/TableView/TableView.tsx +1018 -0
  84. package/src/components/layout/TableView/index.ts +3 -0
  85. package/src/components/layout/TableView/types.ts +51 -0
  86. package/src/components/layout/WizardStep.tsx +40 -0
  87. package/src/components/layout/WizardStepper.tsx +173 -0
  88. package/src/components/layout/index.ts +96 -0
  89. package/src/components/navigation/Breadcrumbs.tsx +66 -0
  90. package/src/components/navigation/DropdownMenu.tsx +86 -0
  91. package/src/components/navigation/MegaMenu.tsx +480 -0
  92. package/src/components/navigation/NavigationMenu.tsx +305 -0
  93. package/src/components/navigation/Pagination.tsx +298 -0
  94. package/src/components/navigation/Sidebar.tsx +280 -0
  95. package/src/components/navigation/Tabs.tsx +122 -0
  96. package/src/components/navigation/ViewSwitcher.tsx +314 -0
  97. package/src/components/navigation/index.ts +66 -0
  98. package/src/components/overlays/AlertDialog.tsx +174 -0
  99. package/src/components/overlays/ContextMenu.tsx +65 -0
  100. package/src/components/overlays/Dialog.tsx +279 -0
  101. package/src/components/overlays/Drawer.tsx +370 -0
  102. package/src/components/overlays/HoverCard.tsx +107 -0
  103. package/src/components/overlays/Popover.tsx +73 -0
  104. package/src/components/overlays/Tooltip.tsx +31 -0
  105. package/src/components/overlays/index.ts +71 -0
  106. package/src/components/typography/Code.tsx +72 -0
  107. package/src/components/typography/Icon.tsx +36 -0
  108. package/src/components/typography/index.ts +10 -0
  109. package/src/env.d.ts +9 -0
  110. package/src/index.ts +13 -0
  111. package/src/styles/theme.css +226 -0
  112. package/src/types/avatar-types.ts +11 -0
  113. package/src/types/filter-types.ts +35 -0
  114. package/src/utilities/classNames.ts +6 -0
  115. package/src/utilities/componentSize.ts +46 -0
  116. package/src/utilities/i18n.tsx +60 -0
  117. package/src/utilities/mergeRefs.ts +12 -0
  118. package/src/utilities/relativeDateDefault.ts +14 -0
@@ -0,0 +1,232 @@
1
+ /**
2
+
3
+ * Password strength indicator - minimal implementation.
4
+
5
+ * Pass password as a prop; updates reactively when parent re-renders with new value.
6
+
7
+ *
8
+
9
+ * Usage: <PasswordStrengthIndicator password={password()} showHelperText />
10
+
11
+ */
12
+
13
+ import { createMemo, createUniqueId, Show, splitProps } from 'solid-js'
14
+
15
+ import { cn } from '../../../utilities/classNames'
16
+
17
+ import { getPasswordAnalysis } from './password-strength'
18
+
19
+ import type { PasswordStrength } from './password-strength'
20
+
21
+ import { Progress } from '../Progress'
22
+
23
+
24
+
25
+ const SEGMENT_COUNT = 8
26
+
27
+
28
+
29
+ const CONFIG: Record<
30
+
31
+ PasswordStrength,
32
+
33
+ { label: string; bg: string; textColor: string; helperText: string }
34
+
35
+ > = {
36
+
37
+ empty: { label: '', bg: '', textColor: 'text-ink-500', helperText: '' },
38
+
39
+ poor: {
40
+
41
+ label: 'Poor',
42
+
43
+ bg: 'bg-danger-500',
44
+
45
+ textColor: 'text-danger-600',
46
+
47
+ helperText: 'Your password is easily guessable. You can do better.',
48
+
49
+ },
50
+
51
+ fair: {
52
+
53
+ label: 'Average',
54
+
55
+ bg: 'bg-amber-500',
56
+
57
+ textColor: 'text-amber-600',
58
+
59
+ helperText: 'Getting there, but could be stronger.',
60
+
61
+ },
62
+
63
+ good: {
64
+
65
+ label: 'Strong',
66
+
67
+ bg: 'bg-success-500',
68
+
69
+ textColor: 'text-success-600',
70
+
71
+ helperText: 'Your password is great. Nice work!',
72
+
73
+ },
74
+
75
+ excellent: {
76
+
77
+ label: 'Very Strong',
78
+
79
+ bg: 'bg-success-500',
80
+
81
+ textColor: 'text-success-600',
82
+
83
+ helperText: 'Excellent password. Nice work!',
84
+
85
+ },
86
+
87
+ }
88
+
89
+
90
+
91
+ export interface PasswordStrengthIndicatorProps {
92
+
93
+ /** Current password value - pass the signal's value, e.g. password={newPassword()} */
94
+
95
+ password?: string
96
+
97
+ class?: string
98
+
99
+ /** Show helper text below the bar. Default: true. */
100
+
101
+ showHelperText?: boolean
102
+
103
+ }
104
+
105
+
106
+
107
+ export function PasswordStrengthIndicator(props: PasswordStrengthIndicatorProps) {
108
+
109
+ const [local] = splitProps(props, ['password', 'class', 'showHelperText'])
110
+
111
+
112
+
113
+ const helperId = `psi-helper-${createUniqueId()}`
114
+
115
+ const pwd = () => local.password ?? ''
116
+
117
+ const analysis = createMemo(() => getPasswordAnalysis(pwd()))
118
+
119
+ const strength = createMemo(() => analysis().strength)
120
+
121
+ const cfg = createMemo(() => CONFIG[strength()])
122
+
123
+ const segmentValue = createMemo(() => (analysis().segmentScore / SEGMENT_COUNT) * 100)
124
+
125
+
126
+
127
+ const missing = createMemo(() => {
128
+
129
+ const r = analysis().requirements
130
+
131
+ const m: string[] = []
132
+
133
+ if (!r.hasMinLength) m.push('at least 8 characters')
134
+
135
+ if (!r.hasUppercase) m.push('an uppercase letter')
136
+
137
+ if (!r.hasLowercase) m.push('a lowercase letter')
138
+
139
+ if (!r.hasNumber) m.push('a number')
140
+
141
+ if (!r.hasSymbol) m.push('a symbol')
142
+
143
+ return m
144
+
145
+ })
146
+
147
+
148
+
149
+ const helperText = () => {
150
+
151
+ if (strength() === 'empty') return 'Enter a password to see strength.'
152
+
153
+ if (strength() === 'poor' || strength() === 'fair') {
154
+
155
+ const m = missing()
156
+
157
+ if (m.length === 0) return cfg().helperText
158
+
159
+ if (m.length === 1) return `Must contain ${m[0]}.`
160
+
161
+ return `Must contain ${m.slice(0, -1).join(', ')}, and ${m[m.length - 1]}.`
162
+
163
+ }
164
+
165
+ return cfg().helperText
166
+
167
+ }
168
+
169
+
170
+
171
+ const isEmpty = () => strength() === 'empty'
172
+
173
+
174
+
175
+ return (
176
+
177
+ <div class={cn('mt-1.5', local.class)}>
178
+
179
+ <div class="flex items-center justify-between mb-1.5">
180
+
181
+ <span class="text-sm font-medium text-ink-700">Password Strength</span>
182
+
183
+ <span class={cn('text-sm font-medium', cfg().textColor)}>{cfg().label}</span>
184
+
185
+ </div>
186
+
187
+ <Progress
188
+
189
+ value={segmentValue()}
190
+
191
+ segments={SEGMENT_COUNT}
192
+
193
+ fillClass={cfg().bg}
194
+
195
+ trackClass="bg-transparent"
196
+
197
+ showValueLabel={false}
198
+
199
+ aria-label={isEmpty() ? 'Password strength: not set' : `Password strength: ${cfg().label}`}
200
+
201
+ aria-describedby={local.showHelperText !== false ? helperId : undefined}
202
+
203
+ />
204
+
205
+ <Show when={local.showHelperText !== false}>
206
+
207
+ <p
208
+
209
+ id={helperId}
210
+
211
+ class={cn(
212
+
213
+ 'mt-1.5 text-sm',
214
+
215
+ (strength() === 'poor' || strength() === 'fair') ? 'text-ink-600' : 'text-ink-500'
216
+
217
+ )}
218
+
219
+ >
220
+
221
+ {helperText()}
222
+
223
+ </p>
224
+
225
+ </Show>
226
+
227
+ </div>
228
+
229
+ )
230
+
231
+ }
232
+
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Password strength and requirements.
3
+ *
4
+ * Callers should NOT trim the password before passing it in; these functions
5
+ * operate on the raw value. isPasswordWeak (from password-validation) trims
6
+ * internally for its own pattern checks — that asymmetry is intentional so
7
+ * that leading/trailing spaces count toward length and character requirements
8
+ * but don't bypass weak-pattern detection.
9
+ */
10
+ import { isPasswordWeak } from './password-validation'
11
+
12
+ export type PasswordStrength = 'empty' | 'poor' | 'fair' | 'good' | 'excellent'
13
+
14
+ export interface PasswordRequirements {
15
+ hasMinLength: boolean
16
+ hasUppercase: boolean
17
+ hasLowercase: boolean
18
+ hasNumber: boolean
19
+ hasSymbol: boolean
20
+ }
21
+
22
+ export interface PasswordAnalysis {
23
+ requirements: PasswordRequirements
24
+ /** Number of requirements met (0–5). */
25
+ met: number
26
+ strength: PasswordStrength
27
+ /** 0–8 segment score for smooth progress bar fill. */
28
+ segmentScore: number
29
+ }
30
+
31
+ /**
32
+ * Single-pass analysis: requirements, strength, and segment score.
33
+ * Use this instead of calling getPasswordRequirements / getPasswordStrength /
34
+ * getPasswordSegmentScore individually which avoids redundant computation on every keystroke.
35
+ */
36
+ export function getPasswordAnalysis(password: string): PasswordAnalysis {
37
+ if (password.length === 0) {
38
+ return {
39
+ requirements: { hasMinLength: false, hasUppercase: false, hasLowercase: false, hasNumber: false, hasSymbol: false },
40
+ met: 0,
41
+ strength: 'empty',
42
+ segmentScore: 0,
43
+ }
44
+ }
45
+
46
+ const requirements = computeRequirements(password)
47
+ const met = [
48
+ requirements.hasMinLength,
49
+ requirements.hasUppercase,
50
+ requirements.hasLowercase,
51
+ requirements.hasNumber,
52
+ requirements.hasSymbol,
53
+ ].filter(Boolean).length
54
+
55
+ const weak = isPasswordWeak(password)
56
+
57
+ const strength: PasswordStrength =
58
+ weak || met < 3 ? 'poor'
59
+ : met === 3 ? 'fair'
60
+ : met === 4 ? 'good'
61
+ : 'excellent'
62
+
63
+ // Segment score: 0–8 for smooth progress bar fill.
64
+ // When weak, cap at 2 regardless of met count.
65
+ let segmentScore: number
66
+ if (weak) {
67
+ segmentScore = Math.min(2, Math.max(1, met))
68
+ } else if (met <= 2) {
69
+ segmentScore = met
70
+ } else if (met === 3) {
71
+ segmentScore = requirements.hasMinLength && password.length >= 10 ? 4 : 3
72
+ } else if (met === 4) {
73
+ segmentScore = requirements.hasMinLength && password.length >= 12 ? 6 : 5
74
+ } else {
75
+ segmentScore = requirements.hasMinLength && password.length >= 14 ? 8 : 7
76
+ }
77
+
78
+ return { requirements, met, strength, segmentScore }
79
+ }
80
+
81
+ // Convenience wrappers (backward-compat, delegate to getPasswordAnalysis)
82
+
83
+ export function getPasswordRequirements(password: string): PasswordRequirements {
84
+ return getPasswordAnalysis(password).requirements
85
+ }
86
+
87
+ export function getPasswordStrength(password: string): PasswordStrength {
88
+ return getPasswordAnalysis(password).strength
89
+ }
90
+
91
+ /** Returns 0–8 for smooth segment progression. */
92
+ export function getPasswordSegmentScore(password: string): number {
93
+ return getPasswordAnalysis(password).segmentScore
94
+ }
95
+
96
+ // Internal
97
+
98
+ function computeRequirements(password: string): PasswordRequirements {
99
+ let upper = false, lower = false, digit = false, symbol = false
100
+ for (let i = 0; i < password.length; i++) {
101
+ const c = password.charCodeAt(i)
102
+ if (c >= 65 && c <= 90) upper = true
103
+ else if (c >= 97 && c <= 122) lower = true
104
+ else if (c >= 48 && c <= 57) digit = true
105
+ else if (c >= 33 && c <= 126) symbol = true
106
+ if (upper && lower && digit && symbol) break
107
+ }
108
+ return {
109
+ hasMinLength: password.length >= 8,
110
+ hasUppercase: upper,
111
+ hasLowercase: lower,
112
+ hasNumber: digit,
113
+ hasSymbol: symbol,
114
+ }
115
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Pattern data for password validation (common words, keyboard walks, sequential).
3
+ * Edit this file to add or remove patterns; used by password-validation.ts.
4
+ *
5
+ * Matching uses `includes()`, so longer entries implicitly cover shorter substrings.
6
+ * Only add a new entry when it is NOT a substring of an existing one in the same list.
7
+ */
8
+
9
+ export const COMMON_WORDS = [
10
+ 'password',
11
+ 'admin',
12
+ 'welcome',
13
+ 'letmein',
14
+ 'welcome1',
15
+ 'monkey',
16
+ 'dragon',
17
+ 'master',
18
+ 'sunshine',
19
+ 'princess',
20
+ 'football',
21
+ 'iloveyou',
22
+ 'admin123',
23
+ 'root',
24
+ 'pass',
25
+ 'passw0rd',
26
+ 'password1',
27
+ 'qwerty123',
28
+ 'abc123',
29
+ 'admin1',
30
+ 'welcome123',
31
+ ] as const
32
+
33
+ // Longer walks cover shorter prefixes (e.g. 'qwertyuiop' covers 'qwerty').
34
+ // Only unique, non-substring walks are listed.
35
+ export const KEYBOARD_WALKS = [
36
+ 'qwertyuiop', // covers 'qwerty'
37
+ 'asdfghjkl', // covers 'asdfgh', 'asdf'
38
+ 'zxcvbnm', // covers 'zxcvbn'
39
+ 'qazwsx',
40
+ '1qaz2wsx',
41
+ 'qweasd',
42
+ '123qwe',
43
+ 'qwe123',
44
+ 'poiuyt',
45
+ 'lkjhgf',
46
+ 'mnbvcx',
47
+ '1q2w3e4r',
48
+ 'q1w2e3r4',
49
+ 'zaq1wsx',
50
+ 'xsw2zaq',
51
+ ] as const
52
+
53
+ // Full forward + reverse covers all shorter subsequences (e.g. '0123456789' covers '1234', '012', etc.).
54
+ // '1234567890' and '0987654321' wrap around (9→0) and are NOT substrings of the straight sequences.
55
+ export const SEQUENTIAL_DIGITS = [
56
+ '0123456789',
57
+ '1234567890',
58
+ '9876543210',
59
+ '0987654321',
60
+ ] as const
61
+
62
+ // Full forward + reverse covers all shorter subsequences (e.g. 'abc', 'abcdef', 'cba', 'fedcba').
63
+ export const SEQUENTIAL_LETTERS = [
64
+ 'abcdefghijklmnopqrstuvwxyz',
65
+ 'zyxwvutsrqponmlkjihgfedcba',
66
+ ] as const
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Password validation: blocks weak patterns and returns human-readable errors.
3
+ * Inlined for TorchUI standalone; patterns: sequential, keyboard walks, repetitive, common words.
4
+ *
5
+ * Note: common-word check uses substring matching (`includes`), so short words like
6
+ * "pass" or "root" may cause false positives on longer passwords. This is intentional —
7
+ * passwords that embed common words are weaker even in a longer context.
8
+ */
9
+ import {
10
+ COMMON_WORDS,
11
+ KEYBOARD_WALKS,
12
+ SEQUENTIAL_DIGITS,
13
+ SEQUENTIAL_LETTERS,
14
+ } from './password-validation-data'
15
+
16
+ export interface PasswordValidationResult {
17
+ valid: boolean
18
+ error?: string
19
+ }
20
+
21
+ /**
22
+ * Checks the (already-trimmed) password against blocked patterns.
23
+ * Returns a human-readable error string if a pattern is found, or `null` if clean.
24
+ */
25
+ function containsBlockedPattern(trimmed: string): string | null {
26
+ const lower = trimmed.toLowerCase()
27
+
28
+ for (const word of COMMON_WORDS) {
29
+ if (lower === word || lower.includes(word)) {
30
+ return 'Password contains a common word. Choose something more unique.'
31
+ }
32
+ }
33
+
34
+ for (const walk of KEYBOARD_WALKS) {
35
+ if (lower.includes(walk)) {
36
+ return 'Password contains a common keyboard pattern.'
37
+ }
38
+ }
39
+
40
+ for (const seq of SEQUENTIAL_DIGITS) {
41
+ if (lower.includes(seq)) {
42
+ return 'Password contains sequential numbers.'
43
+ }
44
+ }
45
+
46
+ for (const seq of SEQUENTIAL_LETTERS) {
47
+ if (lower.includes(seq)) {
48
+ return 'Password contains sequential letters.'
49
+ }
50
+ }
51
+
52
+ // No more than 2 identical characters in a row
53
+ if (/(.)\1{2,}/.test(trimmed)) {
54
+ return 'Password contains too many repeated characters.'
55
+ }
56
+
57
+ // Repetitive patterns (e.g. "abcabcabc"): checks 1-3 char repeating prefix
58
+ if (trimmed.length >= 6) {
59
+ for (let len = 1; len <= 3; len++) {
60
+ const pattern = lower.slice(0, len)
61
+ let repeats = 1
62
+ for (let i = len; i < lower.length; i += len) {
63
+ if (lower.slice(i, i + len) === pattern) repeats++
64
+ else break
65
+ }
66
+ if (repeats >= 3 && repeats * len >= 6) {
67
+ return 'Password is too repetitive.'
68
+ }
69
+ }
70
+ }
71
+
72
+ return null
73
+ }
74
+
75
+ /** Validate a password for form submission. Checks minimum length (8) and blocked patterns. */
76
+ export function validatePassword(password: string): PasswordValidationResult {
77
+ const p = password.trim()
78
+ if (p.length < 8) {
79
+ return { valid: false, error: 'Password must be at least 8 characters' }
80
+ }
81
+ const error = containsBlockedPattern(p)
82
+ return error ? { valid: false, error } : { valid: true }
83
+ }
84
+
85
+ /**
86
+ * Check if a password contains blocked patterns (sequential, keyboard walks, etc.).
87
+ * Does NOT check length — use this for strength indicators, not form validation.
88
+ */
89
+ export function isPasswordWeak(password: string): boolean {
90
+ const p = password.trim()
91
+ if (!p) return false // Empty is not "weak", just empty
92
+ return containsBlockedPattern(p) !== null
93
+ }