@exegia/corpora-ui 0.5.0

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 (71) hide show
  1. package/README.md +21 -0
  2. package/dist-lib/components/blocks/auth/auth-shell.d.ts +56 -0
  3. package/dist-lib/components/blocks/auth/code-auth-block.d.ts +22 -0
  4. package/dist-lib/components/blocks/auth/forgot-password-block.d.ts +15 -0
  5. package/dist-lib/components/blocks/auth/login-block.d.ts +25 -0
  6. package/dist-lib/components/blocks/auth/signup-block.d.ts +26 -0
  7. package/dist-lib/components/composed/password-input.d.ts +30 -0
  8. package/dist-lib/components/composed/social-providers.d.ts +22 -0
  9. package/dist-lib/components/docs/code-block.d.ts +4 -0
  10. package/dist-lib/components/docs/component-preview.d.ts +10 -0
  11. package/dist-lib/components/docs/demo-controls.d.ts +28 -0
  12. package/dist-lib/components/docs/props-table.d.ts +4 -0
  13. package/dist-lib/components/theme-provider.d.ts +15 -0
  14. package/dist-lib/components/ui/button.d.ts +28 -0
  15. package/dist-lib/components/ui/card.d.ts +16 -0
  16. package/dist-lib/components/ui/checkbox.d.ts +7 -0
  17. package/dist-lib/components/ui/field.d.ts +10 -0
  18. package/dist-lib/components/ui/frame.d.ts +7 -0
  19. package/dist-lib/components/ui/glasscn/glass-button.d.ts +6 -0
  20. package/dist-lib/components/ui/glasscn/liquid-glass.d.ts +31 -0
  21. package/dist-lib/components/ui/input-group.d.ts +13 -0
  22. package/dist-lib/components/ui/input.d.ts +9 -0
  23. package/dist-lib/components/ui/label.d.ts +11 -0
  24. package/dist-lib/components/ui/otp-field.d.ts +11 -0
  25. package/dist-lib/components/ui/separator.d.ts +4 -0
  26. package/dist-lib/components/ui/spinner.d.ts +3 -0
  27. package/dist-lib/components/ui/textarea.d.ts +8 -0
  28. package/dist-lib/index.d.ts +29 -0
  29. package/dist-lib/index.js +1617 -0
  30. package/dist-lib/index.js.map +1 -0
  31. package/dist-lib/lib/auth-accent.d.ts +33 -0
  32. package/dist-lib/lib/glass-variants.d.ts +6 -0
  33. package/dist-lib/lib/sound.d.ts +13 -0
  34. package/dist-lib/lib/utils.d.ts +2 -0
  35. package/dist-lib/lib/utils.test.d.ts +1 -0
  36. package/package.json +80 -0
  37. package/src/components/blocks/.gitkeep +0 -0
  38. package/src/components/blocks/auth/auth-shell.tsx +266 -0
  39. package/src/components/blocks/auth/code-auth-block.tsx +190 -0
  40. package/src/components/blocks/auth/forgot-password-block.tsx +123 -0
  41. package/src/components/blocks/auth/login-block.tsx +239 -0
  42. package/src/components/blocks/auth/signup-block.tsx +260 -0
  43. package/src/components/composed/.gitkeep +0 -0
  44. package/src/components/composed/password-input.tsx +179 -0
  45. package/src/components/composed/social-providers.tsx +89 -0
  46. package/src/components/docs/code-block.tsx +8 -0
  47. package/src/components/docs/component-preview.tsx +28 -0
  48. package/src/components/docs/demo-controls.tsx +118 -0
  49. package/src/components/docs/props-table.tsx +35 -0
  50. package/src/components/theme-provider.tsx +230 -0
  51. package/src/components/ui/button.tsx +199 -0
  52. package/src/components/ui/card.tsx +253 -0
  53. package/src/components/ui/checkbox.tsx +83 -0
  54. package/src/components/ui/field.tsx +89 -0
  55. package/src/components/ui/frame.tsx +87 -0
  56. package/src/components/ui/glasscn/glass-button.tsx +25 -0
  57. package/src/components/ui/glasscn/liquid-glass.tsx +276 -0
  58. package/src/components/ui/input-group.tsx +105 -0
  59. package/src/components/ui/input.tsx +70 -0
  60. package/src/components/ui/label.tsx +36 -0
  61. package/src/components/ui/otp-field.tsx +88 -0
  62. package/src/components/ui/separator.tsx +23 -0
  63. package/src/components/ui/spinner.tsx +17 -0
  64. package/src/components/ui/textarea.tsx +58 -0
  65. package/src/index.css +171 -0
  66. package/src/index.ts +37 -0
  67. package/src/lib/auth-accent.ts +49 -0
  68. package/src/lib/glass-variants.ts +57 -0
  69. package/src/lib/sound.ts +39 -0
  70. package/src/lib/utils.test.ts +22 -0
  71. package/src/lib/utils.ts +6 -0
@@ -0,0 +1,89 @@
1
+ "use client";
2
+
3
+ import {
4
+ RiAppleFill,
5
+ RiGithubFill,
6
+ RiGoogleFill,
7
+ RiTwitterXFill,
8
+ } from "@remixicon/react";
9
+ import type * as React from "react";
10
+
11
+ import { Button } from "@/components/ui/button";
12
+ import { cn } from "@/lib/utils";
13
+
14
+ export type SocialProvider = "google" | "apple" | "github" | "x";
15
+
16
+ const PROVIDERS: Record<
17
+ SocialProvider,
18
+ { label: string; Icon: React.ComponentType<{ className?: string }> }
19
+ > = {
20
+ google: { label: "Google", Icon: RiGoogleFill },
21
+ apple: { label: "Apple", Icon: RiAppleFill },
22
+ github: { label: "GitHub", Icon: RiGithubFill },
23
+ x: { label: "X", Icon: RiTwitterXFill },
24
+ };
25
+
26
+ const ACTION_LABELS = {
27
+ login: "Login with",
28
+ signup: "Sign up with",
29
+ continue: "Continue with",
30
+ } as const;
31
+
32
+ export interface SocialProvidersProps {
33
+ providers?: SocialProvider[];
34
+ /** Verb used in the stacked layout labels. */
35
+ action?: keyof typeof ACTION_LABELS;
36
+ /** "stack" = full-width labeled buttons; "row" = icon-only buttons. */
37
+ layout?: "stack" | "row";
38
+ /** Shows the matching button in its loading state and disables the rest. */
39
+ loadingProvider?: SocialProvider | null;
40
+ disabled?: boolean;
41
+ onSelect?: (provider: SocialProvider) => void;
42
+ className?: string;
43
+ }
44
+
45
+ /** Social auth buttons (coss particle p-button-38). UI only — wire onSelect. */
46
+ export function SocialProviders({
47
+ providers = ["google", "apple", "github"],
48
+ action = "continue",
49
+ layout = "stack",
50
+ loadingProvider = null,
51
+ disabled = false,
52
+ onSelect,
53
+ className,
54
+ }: SocialProvidersProps) {
55
+ const row = layout === "row";
56
+
57
+ return (
58
+ <div
59
+ className={cn(
60
+ "flex w-full gap-2",
61
+ row ? "flex-row justify-center" : "flex-col",
62
+ className,
63
+ )}
64
+ >
65
+ {providers.map((provider) => {
66
+ const { label, Icon } = PROVIDERS[provider];
67
+ return (
68
+ <Button
69
+ key={provider}
70
+ variant="outline"
71
+ size={row ? "icon" : "default"}
72
+ className={row ? undefined : "w-full"}
73
+ aria-label={`${ACTION_LABELS[action]} ${label}`}
74
+ loading={loadingProvider === provider}
75
+ disabled={disabled || (loadingProvider !== null && loadingProvider !== provider)}
76
+ onClick={() => onSelect?.(provider)}
77
+ >
78
+ <Icon aria-hidden="true" />
79
+ {!row && (
80
+ <span className="flex-1">
81
+ {ACTION_LABELS[action]} {label}
82
+ </span>
83
+ )}
84
+ </Button>
85
+ );
86
+ })}
87
+ </div>
88
+ );
89
+ }
@@ -0,0 +1,8 @@
1
+ /** Plain code snippet shell — syntax highlighting can be layered in later. */
2
+ export function CodeBlock({ code }: { code: string }) {
3
+ return (
4
+ <pre className="overflow-x-auto rounded-lg border bg-muted/50 p-4 font-mono text-xs leading-relaxed">
5
+ <code>{code}</code>
6
+ </pre>
7
+ )
8
+ }
@@ -0,0 +1,28 @@
1
+ import * as React from "react"
2
+
3
+ import type { RegistryEntry } from "@/registry"
4
+
5
+ /**
6
+ * Renders an entry's lazy demo (created at registry module scope) inside a
7
+ * bordered canvas. Falls back to a placeholder while the demo is not
8
+ * implemented yet.
9
+ */
10
+ export function ComponentPreview({ entry }: { entry: RegistryEntry }) {
11
+ const Demo = entry.preview
12
+
13
+ return (
14
+ <div className="flex min-h-48 items-center justify-center rounded-lg border p-8">
15
+ {Demo ? (
16
+ <React.Suspense
17
+ fallback={<p className="text-sm text-muted-foreground">Loading…</p>}
18
+ >
19
+ <Demo />
20
+ </React.Suspense>
21
+ ) : (
22
+ <p className="text-sm text-muted-foreground">
23
+ Preview coming soon ({entry.status}).
24
+ </p>
25
+ )}
26
+ </div>
27
+ )
28
+ }
@@ -0,0 +1,118 @@
1
+ import type * as React from "react"
2
+
3
+ /**
4
+ * Lightweight playground controls for registry demos. Plain native elements
5
+ * on purpose — docs-only, never part of the published library.
6
+ */
7
+
8
+ export function DemoSelect<T extends string>({
9
+ label,
10
+ value,
11
+ options,
12
+ onChange,
13
+ }: {
14
+ label: string
15
+ value: T
16
+ options: readonly T[]
17
+ onChange: (value: T) => void
18
+ }) {
19
+ return (
20
+ <label className="flex flex-col gap-1 text-xs text-muted-foreground">
21
+ {label}
22
+ <select
23
+ value={value}
24
+ onChange={(event) => onChange(event.target.value as T)}
25
+ className="h-7 rounded-md border bg-background px-2 text-xs text-foreground"
26
+ >
27
+ {options.map((option) => (
28
+ <option key={option} value={option}>
29
+ {option}
30
+ </option>
31
+ ))}
32
+ </select>
33
+ </label>
34
+ )
35
+ }
36
+
37
+ /**
38
+ * Stand-in brand mark for demos of the auth blocks' `logo` slot. Deliberately
39
+ * unbranded — it exists to show the slot's spacing and default sizing, not to
40
+ * represent a real product.
41
+ */
42
+ export function DemoBrandMark() {
43
+ return (
44
+ <svg
45
+ aria-label="Example logo"
46
+ role="img"
47
+ viewBox="0 0 32 32"
48
+ className="text-foreground"
49
+ >
50
+ <rect
51
+ x="2"
52
+ y="2"
53
+ width="28"
54
+ height="28"
55
+ rx="9"
56
+ fill="none"
57
+ stroke="currentColor"
58
+ strokeWidth="2.5"
59
+ />
60
+ <path
61
+ d="M11 20.5V11.5h4.5a3.5 3.5 0 0 1 0 7H11"
62
+ fill="none"
63
+ stroke="currentColor"
64
+ strokeWidth="2.5"
65
+ strokeLinecap="round"
66
+ strokeLinejoin="round"
67
+ />
68
+ </svg>
69
+ )
70
+ }
71
+
72
+ export function DemoToggle({
73
+ label,
74
+ checked,
75
+ onChange,
76
+ }: {
77
+ label: string
78
+ checked: boolean
79
+ onChange: (checked: boolean) => void
80
+ }) {
81
+ return (
82
+ <label className="flex items-center gap-1.5 text-xs text-muted-foreground">
83
+ <input
84
+ type="checkbox"
85
+ checked={checked}
86
+ onChange={(event) => onChange(event.target.checked)}
87
+ />
88
+ {label}
89
+ </label>
90
+ )
91
+ }
92
+
93
+ /** Standard layout: centered preview canvas above a wrapping controls row. */
94
+ export function DemoStage({
95
+ controls,
96
+ children,
97
+ canvasClassName,
98
+ }: {
99
+ controls: React.ReactNode
100
+ children: React.ReactNode
101
+ canvasClassName?: string
102
+ }) {
103
+ return (
104
+ <div className="flex w-full flex-col items-center gap-6">
105
+ <div
106
+ className={
107
+ canvasClassName ??
108
+ "flex min-h-28 w-full items-center justify-center rounded-lg"
109
+ }
110
+ >
111
+ {children}
112
+ </div>
113
+ <div className="flex flex-wrap items-end justify-center gap-x-4 gap-y-3">
114
+ {controls}
115
+ </div>
116
+ </div>
117
+ )
118
+ }
@@ -0,0 +1,35 @@
1
+ import type { PropDef } from "@/registry"
2
+
3
+ export function PropsTable({ props }: { props: PropDef[] }) {
4
+ return (
5
+ <div className="overflow-x-auto rounded-lg border">
6
+ <table className="w-full text-left text-sm">
7
+ <thead className="border-b bg-muted/50">
8
+ <tr>
9
+ <th className="p-3 font-medium">Prop</th>
10
+ <th className="p-3 font-medium">Type</th>
11
+ <th className="p-3 font-medium">Default</th>
12
+ <th className="p-3 font-medium">Description</th>
13
+ </tr>
14
+ </thead>
15
+ <tbody>
16
+ {props.map((prop) => (
17
+ <tr key={prop.name} className="border-b last:border-b-0">
18
+ <td className="p-3 font-mono text-xs">
19
+ {prop.name}
20
+ {prop.required ? <span className="text-destructive">*</span> : null}
21
+ </td>
22
+ <td className="p-3 font-mono text-xs text-muted-foreground">
23
+ {prop.type}
24
+ </td>
25
+ <td className="p-3 font-mono text-xs text-muted-foreground">
26
+ {prop.default ?? "—"}
27
+ </td>
28
+ <td className="p-3 text-muted-foreground">{prop.description}</td>
29
+ </tr>
30
+ ))}
31
+ </tbody>
32
+ </table>
33
+ </div>
34
+ )
35
+ }
@@ -0,0 +1,230 @@
1
+ /* eslint-disable react-refresh/only-export-components */
2
+ import * as React from "react"
3
+
4
+ type Theme = "dark" | "light" | "system"
5
+ type ResolvedTheme = "dark" | "light"
6
+
7
+ type ThemeProviderProps = {
8
+ children: React.ReactNode
9
+ defaultTheme?: Theme
10
+ storageKey?: string
11
+ disableTransitionOnChange?: boolean
12
+ }
13
+
14
+ type ThemeProviderState = {
15
+ theme: Theme
16
+ setTheme: (theme: Theme) => void
17
+ }
18
+
19
+ const COLOR_SCHEME_QUERY = "(prefers-color-scheme: dark)"
20
+ const THEME_VALUES: Theme[] = ["dark", "light", "system"]
21
+
22
+ const ThemeProviderContext = React.createContext<
23
+ ThemeProviderState | undefined
24
+ >(undefined)
25
+
26
+ function isTheme(value: string | null): value is Theme {
27
+ if (value === null) {
28
+ return false
29
+ }
30
+
31
+ return THEME_VALUES.includes(value as Theme)
32
+ }
33
+
34
+ function getSystemTheme(): ResolvedTheme {
35
+ if (window.matchMedia(COLOR_SCHEME_QUERY).matches) {
36
+ return "dark"
37
+ }
38
+
39
+ return "light"
40
+ }
41
+
42
+ function disableTransitionsTemporarily() {
43
+ const style = document.createElement("style")
44
+ style.appendChild(
45
+ document.createTextNode(
46
+ "*,*::before,*::after{-webkit-transition:none!important;transition:none!important}"
47
+ )
48
+ )
49
+ document.head.appendChild(style)
50
+
51
+ return () => {
52
+ window.getComputedStyle(document.body)
53
+ requestAnimationFrame(() => {
54
+ requestAnimationFrame(() => {
55
+ style.remove()
56
+ })
57
+ })
58
+ }
59
+ }
60
+
61
+ function isEditableTarget(target: EventTarget | null) {
62
+ if (!(target instanceof HTMLElement)) {
63
+ return false
64
+ }
65
+
66
+ if (target.isContentEditable) {
67
+ return true
68
+ }
69
+
70
+ const editableParent = target.closest(
71
+ "input, textarea, select, [contenteditable='true']"
72
+ )
73
+ if (editableParent) {
74
+ return true
75
+ }
76
+
77
+ return false
78
+ }
79
+
80
+ export function ThemeProvider({
81
+ children,
82
+ defaultTheme = "system",
83
+ storageKey = "theme",
84
+ disableTransitionOnChange = true,
85
+ ...props
86
+ }: ThemeProviderProps) {
87
+ const [theme, setThemeState] = React.useState<Theme>(() => {
88
+ const storedTheme = localStorage.getItem(storageKey)
89
+ if (isTheme(storedTheme)) {
90
+ return storedTheme
91
+ }
92
+
93
+ return defaultTheme
94
+ })
95
+
96
+ const setTheme = React.useCallback(
97
+ (nextTheme: Theme) => {
98
+ localStorage.setItem(storageKey, nextTheme)
99
+ setThemeState(nextTheme)
100
+ },
101
+ [storageKey]
102
+ )
103
+
104
+ const applyTheme = React.useCallback(
105
+ (nextTheme: Theme) => {
106
+ const root = document.documentElement
107
+ const resolvedTheme =
108
+ nextTheme === "system" ? getSystemTheme() : nextTheme
109
+ const restoreTransitions = disableTransitionOnChange
110
+ ? disableTransitionsTemporarily()
111
+ : null
112
+
113
+ root.classList.remove("light", "dark")
114
+ root.classList.add(resolvedTheme)
115
+
116
+ if (restoreTransitions) {
117
+ restoreTransitions()
118
+ }
119
+ },
120
+ [disableTransitionOnChange]
121
+ )
122
+
123
+ React.useEffect(() => {
124
+ applyTheme(theme)
125
+
126
+ if (theme !== "system") {
127
+ return undefined
128
+ }
129
+
130
+ const mediaQuery = window.matchMedia(COLOR_SCHEME_QUERY)
131
+ const handleChange = () => {
132
+ applyTheme("system")
133
+ }
134
+
135
+ mediaQuery.addEventListener("change", handleChange)
136
+
137
+ return () => {
138
+ mediaQuery.removeEventListener("change", handleChange)
139
+ }
140
+ }, [theme, applyTheme])
141
+
142
+ React.useEffect(() => {
143
+ const handleKeyDown = (event: KeyboardEvent) => {
144
+ if (event.repeat) {
145
+ return
146
+ }
147
+
148
+ if (event.metaKey || event.ctrlKey || event.altKey) {
149
+ return
150
+ }
151
+
152
+ if (isEditableTarget(event.target)) {
153
+ return
154
+ }
155
+
156
+ if (event.key.toLowerCase() !== "d") {
157
+ return
158
+ }
159
+
160
+ setThemeState((currentTheme) => {
161
+ const nextTheme =
162
+ currentTheme === "dark"
163
+ ? "light"
164
+ : currentTheme === "light"
165
+ ? "dark"
166
+ : getSystemTheme() === "dark"
167
+ ? "light"
168
+ : "dark"
169
+
170
+ localStorage.setItem(storageKey, nextTheme)
171
+ return nextTheme
172
+ })
173
+ }
174
+
175
+ window.addEventListener("keydown", handleKeyDown)
176
+
177
+ return () => {
178
+ window.removeEventListener("keydown", handleKeyDown)
179
+ }
180
+ }, [storageKey])
181
+
182
+ React.useEffect(() => {
183
+ const handleStorageChange = (event: StorageEvent) => {
184
+ if (event.storageArea !== localStorage) {
185
+ return
186
+ }
187
+
188
+ if (event.key !== storageKey) {
189
+ return
190
+ }
191
+
192
+ if (isTheme(event.newValue)) {
193
+ setThemeState(event.newValue)
194
+ return
195
+ }
196
+
197
+ setThemeState(defaultTheme)
198
+ }
199
+
200
+ window.addEventListener("storage", handleStorageChange)
201
+
202
+ return () => {
203
+ window.removeEventListener("storage", handleStorageChange)
204
+ }
205
+ }, [defaultTheme, storageKey])
206
+
207
+ const value = React.useMemo(
208
+ () => ({
209
+ theme,
210
+ setTheme,
211
+ }),
212
+ [theme, setTheme]
213
+ )
214
+
215
+ return (
216
+ <ThemeProviderContext.Provider {...props} value={value}>
217
+ {children}
218
+ </ThemeProviderContext.Provider>
219
+ )
220
+ }
221
+
222
+ export const useTheme = () => {
223
+ const context = React.useContext(ThemeProviderContext)
224
+
225
+ if (context === undefined) {
226
+ throw new Error("useTheme must be used within a ThemeProvider")
227
+ }
228
+
229
+ return context
230
+ }