@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.
- package/README.md +21 -0
- package/dist-lib/components/blocks/auth/auth-shell.d.ts +56 -0
- package/dist-lib/components/blocks/auth/code-auth-block.d.ts +22 -0
- package/dist-lib/components/blocks/auth/forgot-password-block.d.ts +15 -0
- package/dist-lib/components/blocks/auth/login-block.d.ts +25 -0
- package/dist-lib/components/blocks/auth/signup-block.d.ts +26 -0
- package/dist-lib/components/composed/password-input.d.ts +30 -0
- package/dist-lib/components/composed/social-providers.d.ts +22 -0
- package/dist-lib/components/docs/code-block.d.ts +4 -0
- package/dist-lib/components/docs/component-preview.d.ts +10 -0
- package/dist-lib/components/docs/demo-controls.d.ts +28 -0
- package/dist-lib/components/docs/props-table.d.ts +4 -0
- package/dist-lib/components/theme-provider.d.ts +15 -0
- package/dist-lib/components/ui/button.d.ts +28 -0
- package/dist-lib/components/ui/card.d.ts +16 -0
- package/dist-lib/components/ui/checkbox.d.ts +7 -0
- package/dist-lib/components/ui/field.d.ts +10 -0
- package/dist-lib/components/ui/frame.d.ts +7 -0
- package/dist-lib/components/ui/glasscn/glass-button.d.ts +6 -0
- package/dist-lib/components/ui/glasscn/liquid-glass.d.ts +31 -0
- package/dist-lib/components/ui/input-group.d.ts +13 -0
- package/dist-lib/components/ui/input.d.ts +9 -0
- package/dist-lib/components/ui/label.d.ts +11 -0
- package/dist-lib/components/ui/otp-field.d.ts +11 -0
- package/dist-lib/components/ui/separator.d.ts +4 -0
- package/dist-lib/components/ui/spinner.d.ts +3 -0
- package/dist-lib/components/ui/textarea.d.ts +8 -0
- package/dist-lib/index.d.ts +29 -0
- package/dist-lib/index.js +1617 -0
- package/dist-lib/index.js.map +1 -0
- package/dist-lib/lib/auth-accent.d.ts +33 -0
- package/dist-lib/lib/glass-variants.d.ts +6 -0
- package/dist-lib/lib/sound.d.ts +13 -0
- package/dist-lib/lib/utils.d.ts +2 -0
- package/dist-lib/lib/utils.test.d.ts +1 -0
- package/package.json +80 -0
- package/src/components/blocks/.gitkeep +0 -0
- package/src/components/blocks/auth/auth-shell.tsx +266 -0
- package/src/components/blocks/auth/code-auth-block.tsx +190 -0
- package/src/components/blocks/auth/forgot-password-block.tsx +123 -0
- package/src/components/blocks/auth/login-block.tsx +239 -0
- package/src/components/blocks/auth/signup-block.tsx +260 -0
- package/src/components/composed/.gitkeep +0 -0
- package/src/components/composed/password-input.tsx +179 -0
- package/src/components/composed/social-providers.tsx +89 -0
- package/src/components/docs/code-block.tsx +8 -0
- package/src/components/docs/component-preview.tsx +28 -0
- package/src/components/docs/demo-controls.tsx +118 -0
- package/src/components/docs/props-table.tsx +35 -0
- package/src/components/theme-provider.tsx +230 -0
- package/src/components/ui/button.tsx +199 -0
- package/src/components/ui/card.tsx +253 -0
- package/src/components/ui/checkbox.tsx +83 -0
- package/src/components/ui/field.tsx +89 -0
- package/src/components/ui/frame.tsx +87 -0
- package/src/components/ui/glasscn/glass-button.tsx +25 -0
- package/src/components/ui/glasscn/liquid-glass.tsx +276 -0
- package/src/components/ui/input-group.tsx +105 -0
- package/src/components/ui/input.tsx +70 -0
- package/src/components/ui/label.tsx +36 -0
- package/src/components/ui/otp-field.tsx +88 -0
- package/src/components/ui/separator.tsx +23 -0
- package/src/components/ui/spinner.tsx +17 -0
- package/src/components/ui/textarea.tsx +58 -0
- package/src/index.css +171 -0
- package/src/index.ts +37 -0
- package/src/lib/auth-accent.ts +49 -0
- package/src/lib/glass-variants.ts +57 -0
- package/src/lib/sound.ts +39 -0
- package/src/lib/utils.test.ts +22 -0
- package/src/lib/utils.ts +6 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { OTPField as OTPFieldPrimitive } from "@base-ui/react/otp-field";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
import { playCue } from "@/lib/sound";
|
|
7
|
+
import { Separator } from "@/components/ui/separator";
|
|
8
|
+
|
|
9
|
+
export function OTPField({
|
|
10
|
+
className,
|
|
11
|
+
size = "default",
|
|
12
|
+
sound = true,
|
|
13
|
+
onValueChange,
|
|
14
|
+
...props
|
|
15
|
+
}: React.ComponentProps<typeof OTPFieldPrimitive.Root> & {
|
|
16
|
+
size?: "default" | "lg";
|
|
17
|
+
/** Play a tick cue per entered character. Inert until bindSounds(). */
|
|
18
|
+
sound?: boolean;
|
|
19
|
+
}): React.ReactElement {
|
|
20
|
+
// Typing is keyboard-driven, so the cue is imperative (playCue is gated on
|
|
21
|
+
// the app having called bindSounds). Tick only when characters are added.
|
|
22
|
+
const lastLength = React.useRef(
|
|
23
|
+
String(props.value ?? props.defaultValue ?? "").length,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const handleValueChange: NonNullable<typeof onValueChange> = (
|
|
27
|
+
value,
|
|
28
|
+
...rest
|
|
29
|
+
) => {
|
|
30
|
+
if (sound && value.length > lastLength.current) {
|
|
31
|
+
playCue("tick");
|
|
32
|
+
}
|
|
33
|
+
lastLength.current = value.length;
|
|
34
|
+
onValueChange?.(value, ...rest);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<OTPFieldPrimitive.Root
|
|
39
|
+
className={cn(
|
|
40
|
+
"flex items-center gap-2 has-disabled:opacity-64 has-disabled:**:data-[slot=otp-field-input]:shadow-none has-disabled:**:data-[slot=otp-field-input]:before:shadow-none!",
|
|
41
|
+
className,
|
|
42
|
+
)}
|
|
43
|
+
data-size={size}
|
|
44
|
+
data-slot="otp-field"
|
|
45
|
+
onValueChange={handleValueChange}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function OTPFieldInput({
|
|
52
|
+
className,
|
|
53
|
+
...props
|
|
54
|
+
}: React.ComponentProps<typeof OTPFieldPrimitive.Input>): React.ReactElement {
|
|
55
|
+
return (
|
|
56
|
+
<OTPFieldPrimitive.Input
|
|
57
|
+
className={cn(
|
|
58
|
+
"relative in-[[data-slot=otp-field][data-size=lg]]:size-10 size-9 min-w-0 rounded-lg border border-input bg-background not-dark:bg-clip-padding text-center in-[[data-slot=otp-field][data-size=lg]]:text-lg text-base text-foreground in-[[data-slot=otp-field][data-size=lg]]:leading-10 leading-9 shadow-xs/5 outline-none ring-ring/24 transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-focus-visible:not-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] focus-visible:z-10 focus-visible:border-ring focus-visible:shadow-none focus-visible:ring-[3px] focus-visible:ring-ring/24 aria-invalid:border-destructive/36 aria-invalid:shadow-none aria-invalid:focus-visible:border-destructive/64 aria-invalid:focus-visible:ring-destructive/16 sm:in-[[data-slot=otp-field][data-size=lg]]:size-9 sm:size-8 sm:in-[[data-slot=otp-field][data-size=lg]]:text-base sm:text-sm sm:in-[[data-slot=otp-field][data-size=lg]]:leading-9 sm:leading-8 dark:bg-input/32 dark:aria-invalid:focus-visible:ring-destructive/24 dark:not-focus-visible:not-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)]",
|
|
59
|
+
className,
|
|
60
|
+
)}
|
|
61
|
+
data-slot="otp-field-input"
|
|
62
|
+
spellCheck={false}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function OTPFieldSeparator({
|
|
69
|
+
className,
|
|
70
|
+
...props
|
|
71
|
+
}: React.ComponentProps<typeof Separator>): React.ReactElement {
|
|
72
|
+
return (
|
|
73
|
+
<OTPFieldPrimitive.Separator
|
|
74
|
+
render={
|
|
75
|
+
<Separator
|
|
76
|
+
className={cn(
|
|
77
|
+
"rounded-full bg-input data-[orientation=horizontal]:h-0.5 data-[orientation=horizontal]:w-3",
|
|
78
|
+
className,
|
|
79
|
+
)}
|
|
80
|
+
orientation="horizontal"
|
|
81
|
+
{...props}
|
|
82
|
+
/>
|
|
83
|
+
}
|
|
84
|
+
/>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { OTPFieldPrimitive };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Separator as SeparatorPrimitive } from "@base-ui/react/separator";
|
|
2
|
+
import type React from "react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
export function Separator({
|
|
6
|
+
className,
|
|
7
|
+
orientation = "horizontal",
|
|
8
|
+
...props
|
|
9
|
+
}: SeparatorPrimitive.Props): React.ReactElement {
|
|
10
|
+
return (
|
|
11
|
+
<SeparatorPrimitive
|
|
12
|
+
className={cn(
|
|
13
|
+
"shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:not-[[class^='h-']]:not-[[class*='_h-']]:self-stretch",
|
|
14
|
+
className,
|
|
15
|
+
)}
|
|
16
|
+
data-slot="separator"
|
|
17
|
+
orientation={orientation}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { SeparatorPrimitive };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Loader2Icon } from "lucide-react";
|
|
2
|
+
import type React from "react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
export function Spinner({
|
|
6
|
+
className,
|
|
7
|
+
...props
|
|
8
|
+
}: React.ComponentProps<typeof Loader2Icon>): React.ReactElement {
|
|
9
|
+
return (
|
|
10
|
+
<Loader2Icon
|
|
11
|
+
aria-label="Loading"
|
|
12
|
+
className={cn("animate-spin", className)}
|
|
13
|
+
role="status"
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Field as FieldPrimitive } from "@base-ui/react/field";
|
|
4
|
+
import { mergeProps } from "@base-ui/react/merge-props";
|
|
5
|
+
import type * as React from "react";
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
|
|
8
|
+
export type TextareaProps = React.ComponentPropsWithoutRef<"textarea"> &
|
|
9
|
+
React.RefAttributes<HTMLTextAreaElement> & {
|
|
10
|
+
size?: "sm" | "default" | "lg" | number;
|
|
11
|
+
unstyled?: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function Textarea({
|
|
15
|
+
className,
|
|
16
|
+
size = "default",
|
|
17
|
+
unstyled = false,
|
|
18
|
+
ref,
|
|
19
|
+
...props
|
|
20
|
+
}: TextareaProps): React.ReactElement {
|
|
21
|
+
return (
|
|
22
|
+
<span
|
|
23
|
+
className={
|
|
24
|
+
cn(
|
|
25
|
+
!unstyled &&
|
|
26
|
+
"relative inline-flex w-full rounded-lg border border-input bg-background not-dark:bg-clip-padding text-base text-foreground shadow-xs/5 ring-ring/24 transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] has-focus-visible:has-aria-invalid:border-destructive/64 has-focus-visible:has-aria-invalid:ring-destructive/16 has-aria-invalid:border-destructive/36 has-focus-visible:border-ring has-disabled:opacity-64 has-[:disabled,:focus-visible,[aria-invalid]]:shadow-none has-focus-visible:ring-[3px] not-has-disabled:has-not-focus-visible:not-has-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] sm:text-sm dark:bg-input/32 dark:has-aria-invalid:ring-destructive/24 dark:not-has-disabled:has-not-focus-visible:not-has-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)]",
|
|
27
|
+
className,
|
|
28
|
+
) || undefined
|
|
29
|
+
}
|
|
30
|
+
data-size={size}
|
|
31
|
+
data-slot="textarea-control"
|
|
32
|
+
>
|
|
33
|
+
<FieldPrimitive.Control
|
|
34
|
+
ref={ref}
|
|
35
|
+
value={props.value}
|
|
36
|
+
defaultValue={props.defaultValue}
|
|
37
|
+
disabled={props.disabled}
|
|
38
|
+
id={props.id}
|
|
39
|
+
name={props.name}
|
|
40
|
+
render={(defaultProps: React.ComponentProps<"textarea">) => (
|
|
41
|
+
<textarea
|
|
42
|
+
className={cn(
|
|
43
|
+
"field-sizing-content min-h-17.5 w-full rounded-[inherit] px-[calc(--spacing(3)-1px)] py-[calc(--spacing(1.5)-1px)] outline-none max-sm:min-h-20.5",
|
|
44
|
+
size === "sm" &&
|
|
45
|
+
"min-h-16.5 px-[calc(--spacing(2.5)-1px)] py-[calc(--spacing(1)-1px)] max-sm:min-h-19.5",
|
|
46
|
+
size === "lg" &&
|
|
47
|
+
"min-h-18.5 py-[calc(--spacing(2)-1px)] max-sm:min-h-21.5",
|
|
48
|
+
)}
|
|
49
|
+
data-slot="textarea"
|
|
50
|
+
{...mergeProps(defaultProps, props)}
|
|
51
|
+
/>
|
|
52
|
+
)}
|
|
53
|
+
/>
|
|
54
|
+
</span>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export { FieldPrimitive };
|
package/src/index.css
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "tw-animate-css";
|
|
3
|
+
@import "shadcn/tailwind.css";
|
|
4
|
+
@import "@fontsource-variable/inter";
|
|
5
|
+
@import "@fontsource-variable/figtree";
|
|
6
|
+
|
|
7
|
+
@custom-variant dark (&:is(.dark *));
|
|
8
|
+
|
|
9
|
+
:root {
|
|
10
|
+
/* Let width/height tween between auto and fixed values (e.g. a button
|
|
11
|
+
toggling to an icon-only size). Progressive enhancement: browsers
|
|
12
|
+
without support keep the instant jump. */
|
|
13
|
+
interpolate-size: allow-keywords;
|
|
14
|
+
--background: oklch(1 0 0);
|
|
15
|
+
--foreground: oklch(0.145 0 0);
|
|
16
|
+
--card: oklch(1 0 0);
|
|
17
|
+
--card-foreground: oklch(0.145 0 0);
|
|
18
|
+
--popover: oklch(1 0 0);
|
|
19
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
20
|
+
--primary: oklch(0.457 0.24 277.023);
|
|
21
|
+
--primary-foreground: oklch(0.962 0.018 272.314);
|
|
22
|
+
--secondary: oklch(0.967 0.001 286.375);
|
|
23
|
+
--secondary-foreground: oklch(0.21 0.006 285.885);
|
|
24
|
+
--muted: oklch(0.97 0 0);
|
|
25
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
26
|
+
--accent: oklch(0.97 0 0);
|
|
27
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
28
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
29
|
+
--border: oklch(0.922 0 0);
|
|
30
|
+
--input: oklch(0.922 0 0);
|
|
31
|
+
--ring: oklch(0.708 0 0);
|
|
32
|
+
--chart-1: oklch(0.811 0.111 293.571);
|
|
33
|
+
--chart-2: oklch(0.606 0.25 292.717);
|
|
34
|
+
--chart-3: oklch(0.541 0.281 293.009);
|
|
35
|
+
--chart-4: oklch(0.491 0.27 292.581);
|
|
36
|
+
--chart-5: oklch(0.432 0.232 292.759);
|
|
37
|
+
--radius: 0.875rem;
|
|
38
|
+
--sidebar: oklch(0.985 0 0);
|
|
39
|
+
--sidebar-foreground: oklch(0.145 0 0);
|
|
40
|
+
--sidebar-primary: oklch(0.511 0.262 276.966);
|
|
41
|
+
--sidebar-primary-foreground: oklch(0.962 0.018 272.314);
|
|
42
|
+
--sidebar-accent: oklch(0.97 0 0);
|
|
43
|
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
44
|
+
--sidebar-border: oklch(0.922 0 0);
|
|
45
|
+
--sidebar-ring: oklch(0.708 0 0);
|
|
46
|
+
--destructive-foreground: var(--color-red-700);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.dark {
|
|
50
|
+
--background: oklch(0.145 0 0);
|
|
51
|
+
--foreground: oklch(0.985 0 0);
|
|
52
|
+
--card: oklch(0.205 0 0);
|
|
53
|
+
--card-foreground: oklch(0.985 0 0);
|
|
54
|
+
--popover: oklch(0.205 0 0);
|
|
55
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
56
|
+
--primary: oklch(0.398 0.195 277.366);
|
|
57
|
+
--primary-foreground: oklch(0.962 0.018 272.314);
|
|
58
|
+
--secondary: oklch(0.274 0.006 286.033);
|
|
59
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
60
|
+
--muted: oklch(0.269 0 0);
|
|
61
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
62
|
+
--accent: oklch(0.269 0 0);
|
|
63
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
64
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
65
|
+
--border: oklch(1 0 0 / 10%);
|
|
66
|
+
--input: oklch(1 0 0 / 15%);
|
|
67
|
+
--ring: oklch(0.556 0 0);
|
|
68
|
+
--chart-1: oklch(0.811 0.111 293.571);
|
|
69
|
+
--chart-2: oklch(0.606 0.25 292.717);
|
|
70
|
+
--chart-3: oklch(0.541 0.281 293.009);
|
|
71
|
+
--chart-4: oklch(0.491 0.27 292.581);
|
|
72
|
+
--chart-5: oklch(0.432 0.232 292.759);
|
|
73
|
+
--sidebar: oklch(0.205 0 0);
|
|
74
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
75
|
+
--sidebar-primary: oklch(0.585 0.233 277.117);
|
|
76
|
+
--sidebar-primary-foreground: oklch(0.962 0.018 272.314);
|
|
77
|
+
--sidebar-accent: oklch(0.269 0 0);
|
|
78
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
79
|
+
--sidebar-border: oklch(1 0 0 / 10%);
|
|
80
|
+
--sidebar-ring: oklch(0.556 0 0);
|
|
81
|
+
--destructive-foreground: var(--color-red-400);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@theme inline {
|
|
85
|
+
/* motion tokens (transitions.dev scale) */
|
|
86
|
+
--ease-smooth-out: cubic-bezier(0.22, 1, 0.36, 1);
|
|
87
|
+
--font-sans: 'Inter Variable', sans-serif;
|
|
88
|
+
--font-heading: 'Figtree Variable', sans-serif;
|
|
89
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
90
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
91
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
92
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
93
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
94
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
95
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
96
|
+
--color-sidebar: var(--sidebar);
|
|
97
|
+
--color-chart-5: var(--chart-5);
|
|
98
|
+
--color-chart-4: var(--chart-4);
|
|
99
|
+
--color-chart-3: var(--chart-3);
|
|
100
|
+
--color-chart-2: var(--chart-2);
|
|
101
|
+
--color-chart-1: var(--chart-1);
|
|
102
|
+
--color-ring: var(--ring);
|
|
103
|
+
--color-input: var(--input);
|
|
104
|
+
--color-border: var(--border);
|
|
105
|
+
--color-destructive: var(--destructive);
|
|
106
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
107
|
+
--color-accent: var(--accent);
|
|
108
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
109
|
+
--color-muted: var(--muted);
|
|
110
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
111
|
+
--color-secondary: var(--secondary);
|
|
112
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
113
|
+
--color-primary: var(--primary);
|
|
114
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
115
|
+
--color-popover: var(--popover);
|
|
116
|
+
--color-card-foreground: var(--card-foreground);
|
|
117
|
+
--color-card: var(--card);
|
|
118
|
+
--color-foreground: var(--foreground);
|
|
119
|
+
--color-background: var(--background);
|
|
120
|
+
--radius-sm: calc(var(--radius) * 0.6);
|
|
121
|
+
--radius-md: calc(var(--radius) * 0.8);
|
|
122
|
+
--radius-lg: var(--radius);
|
|
123
|
+
--radius-xl: calc(var(--radius) * 1.4);
|
|
124
|
+
--radius-2xl: calc(var(--radius) * 1.8);
|
|
125
|
+
--radius-3xl: calc(var(--radius) * 2.2);
|
|
126
|
+
--radius-4xl: calc(var(--radius) * 2.6);
|
|
127
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
128
|
+
@keyframes liquid-drift {
|
|
129
|
+
0% {
|
|
130
|
+
background-position: 0% 0%, 100% 100%, 0% 0%;
|
|
131
|
+
}
|
|
132
|
+
50% {
|
|
133
|
+
background-position: 30% 20%, 70% 80%, 0% 0%;
|
|
134
|
+
}
|
|
135
|
+
100% {
|
|
136
|
+
background-position: 0% 0%, 100% 100%, 0% 0%;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
--animate-caret-blink: 1s ease-out infinite caret-blink
|
|
140
|
+
;
|
|
141
|
+
--animate-input-shake: input-shake 0.4s ease-in-out;
|
|
142
|
+
@keyframes input-shake {
|
|
143
|
+
0%, 100% { translate: 0; }
|
|
144
|
+
20% { translate: -6px 0; }
|
|
145
|
+
40% { translate: 6px 0; }
|
|
146
|
+
60% { translate: -4px 0; }
|
|
147
|
+
80% { translate: 4px 0; }
|
|
148
|
+
}
|
|
149
|
+
@keyframes caret-blink {
|
|
150
|
+
0%, 70%, to {
|
|
151
|
+
opacity: 1;
|
|
152
|
+
}
|
|
153
|
+
20%, 50% {
|
|
154
|
+
opacity: 0;
|
|
155
|
+
}
|
|
156
|
+
}}
|
|
157
|
+
|
|
158
|
+
@layer base {
|
|
159
|
+
* {
|
|
160
|
+
@apply border-border outline-ring/50;
|
|
161
|
+
}
|
|
162
|
+
body {
|
|
163
|
+
@apply bg-background text-foreground;
|
|
164
|
+
}
|
|
165
|
+
button:not(:disabled), [role="button"]:not(:disabled) {
|
|
166
|
+
cursor: pointer;
|
|
167
|
+
}
|
|
168
|
+
html {
|
|
169
|
+
@apply font-sans;
|
|
170
|
+
}
|
|
171
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* npm library entry point (`@corpora/ui`).
|
|
3
|
+
*
|
|
4
|
+
* Export every published component here — atoms from `components/ui`,
|
|
5
|
+
* compositions from `components/composed`, blocks from `components/blocks`.
|
|
6
|
+
* The docs site (main.tsx + routes.tsx + pages/) is NOT part of the package.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// atoms
|
|
10
|
+
export * from "./components/ui/button"
|
|
11
|
+
export * from "./components/ui/card"
|
|
12
|
+
export * from "./components/ui/checkbox"
|
|
13
|
+
export * from "./components/ui/field"
|
|
14
|
+
export * from "./components/ui/frame"
|
|
15
|
+
export * from "./components/ui/input"
|
|
16
|
+
export * from "./components/ui/input-group"
|
|
17
|
+
export * from "./components/ui/label"
|
|
18
|
+
export * from "./components/ui/otp-field"
|
|
19
|
+
export * from "./components/ui/separator"
|
|
20
|
+
export * from "./components/ui/spinner"
|
|
21
|
+
export * from "./components/ui/textarea"
|
|
22
|
+
|
|
23
|
+
// components
|
|
24
|
+
export * from "./components/composed/password-input"
|
|
25
|
+
export * from "./components/composed/social-providers"
|
|
26
|
+
|
|
27
|
+
// blocks
|
|
28
|
+
export * from "./components/blocks/auth/auth-shell"
|
|
29
|
+
export * from "./components/blocks/auth/code-auth-block"
|
|
30
|
+
export * from "./components/blocks/auth/forgot-password-block"
|
|
31
|
+
export * from "./components/blocks/auth/login-block"
|
|
32
|
+
export * from "./components/blocks/auth/signup-block"
|
|
33
|
+
|
|
34
|
+
// lib
|
|
35
|
+
export * from "./lib/auth-accent"
|
|
36
|
+
export * from "./lib/sound"
|
|
37
|
+
export * from "./lib/utils"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brand accents for the auth blocks.
|
|
3
|
+
*
|
|
4
|
+
* An accent is a set of custom properties set on the AuthCard root plus one
|
|
5
|
+
* class string applied to the card's primary action. Splitting it this way
|
|
6
|
+
* keeps the accent inheritable — anything else inside the card can opt into
|
|
7
|
+
* `var(--auth-accent)` without the shell knowing about it.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Brand accents available to the auth blocks. */
|
|
11
|
+
export type AuthAccent = "corpora" | "exegia";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Custom properties an accent contributes, set on the AuthCard root.
|
|
15
|
+
*
|
|
16
|
+
* The gradient lives in `--auth-accent-image` because `background-color` can't
|
|
17
|
+
* hold one; `--auth-accent` sits underneath as the solid that paints wherever
|
|
18
|
+
* the image is `none`. Foregrounds are fixed rather than themed — the accent
|
|
19
|
+
* itself is a constant, so its contrast pair has to be too (white on the
|
|
20
|
+
* corpora yellow fails badly).
|
|
21
|
+
*/
|
|
22
|
+
export const authAccentVars: Record<AuthAccent, Record<string, string>> = {
|
|
23
|
+
corpora: {
|
|
24
|
+
"--auth-accent": "#E8B124",
|
|
25
|
+
"--auth-accent-image": "none",
|
|
26
|
+
"--auth-accent-foreground": "oklch(0.145 0 0)",
|
|
27
|
+
},
|
|
28
|
+
exegia: {
|
|
29
|
+
"--auth-accent": "#3B0080",
|
|
30
|
+
"--auth-accent-image":
|
|
31
|
+
"linear-gradient(44deg in oklab, rgba(29,0,202,0.66) 3%, #3B0080 37%, rgba(36,0,72,0.86) 98%)",
|
|
32
|
+
"--auth-accent-foreground": "oklch(0.985 0 0)",
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Applied to the primary action inside an accented AuthCard, after the button
|
|
38
|
+
* cva output so tailwind-merge lets it win over `bg-primary`. The hover and
|
|
39
|
+
* pressed background entries are required: `hover:bg-primary/90` lives in a
|
|
40
|
+
* different modifier group from `bg-*` and would otherwise survive and repaint
|
|
41
|
+
* the button on hover.
|
|
42
|
+
*
|
|
43
|
+
* Hover uses `brightness` rather than a background tween because the button's
|
|
44
|
+
* `transition-property` list is shared with its press/size animations — adding
|
|
45
|
+
* `filter` to it means restating the whole list and its per-property
|
|
46
|
+
* durations. The trade is an instant hover change instead of a 150ms one.
|
|
47
|
+
*/
|
|
48
|
+
export const authAccentActionStyles =
|
|
49
|
+
"border-transparent bg-[var(--auth-accent)] bg-[image:var(--auth-accent-image)] text-[var(--auth-accent-foreground)] shadow-none hover:bg-[var(--auth-accent)] hover:brightness-110 data-pressed:bg-[var(--auth-accent)] data-pressed:brightness-110 *:data-[slot=button-loading-indicator]:text-[var(--auth-accent-foreground)]";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export type FrostGlassVariant = "clear" | "frosted" | "subtle" | "liquid" | "liquid-refract";
|
|
2
|
+
export type FrostGlassVariantProp = { glassVariant?: FrostGlassVariant };
|
|
3
|
+
|
|
4
|
+
export const liquidRefractStyles = "bg-transparent border-0 shadow-none";
|
|
5
|
+
|
|
6
|
+
export const glassVariantStyles: Record<FrostGlassVariant, string> = {
|
|
7
|
+
clear: [
|
|
8
|
+
"backdrop-blur-[2px] backdrop-saturate-[1.9]",
|
|
9
|
+
"bg-white/[0.25] dark:bg-black/[0.25]",
|
|
10
|
+
"border border-white/[0.5] dark:border-white/[0.12]",
|
|
11
|
+
"shadow-[0_1px_12px_rgba(0,0,0,0.05)] dark:shadow-[0_1px_12px_rgba(0,0,0,0.2)]",
|
|
12
|
+
].join(" "),
|
|
13
|
+
frosted: [
|
|
14
|
+
"backdrop-blur-[16px] backdrop-saturate-[1.6]",
|
|
15
|
+
"bg-white/[0.55] dark:bg-black/[0.35]",
|
|
16
|
+
"border border-white/[0.4] dark:border-white/10",
|
|
17
|
+
"shadow-[0_2px_20px_rgba(0,0,0,0.06)] dark:shadow-[0_2px_20px_rgba(0,0,0,0.3)]",
|
|
18
|
+
].join(" "),
|
|
19
|
+
subtle: [
|
|
20
|
+
"backdrop-blur-[4px] backdrop-saturate-[1.5]",
|
|
21
|
+
"bg-white/[0.3] dark:bg-white/[0.06]",
|
|
22
|
+
"border border-black/[0.05] dark:border-white/[0.08]",
|
|
23
|
+
"shadow-sm",
|
|
24
|
+
].join(" "),
|
|
25
|
+
liquid: [
|
|
26
|
+
// 1) Refractive backdrop — lighter than frosted so the background reads
|
|
27
|
+
// clearly through the lens; saturation + brightness lift makes it bloom.
|
|
28
|
+
"backdrop-blur-[12px] backdrop-saturate-[1.8] backdrop-brightness-[1.05]",
|
|
29
|
+
"dark:backdrop-saturate-[1.6] dark:backdrop-brightness-[0.95]",
|
|
30
|
+
|
|
31
|
+
// 2) Base translucent fill (acts as the panel's "tint").
|
|
32
|
+
"bg-white/[0.10] dark:bg-white/[0.04]",
|
|
33
|
+
|
|
34
|
+
// 3) Layered radial-gradient sheen + warm bottom tint + vertical wash.
|
|
35
|
+
// Underscores represent spaces inside Tailwind arbitrary values.
|
|
36
|
+
"[background-image:radial-gradient(120%_85%_at_15%_8%,rgba(255,255,255,0.55)_0%,rgba(255,255,255,0.18)_38%,rgba(255,255,255,0)_70%),radial-gradient(110%_80%_at_85%_100%,rgba(255,210,230,0.22)_0%,rgba(255,255,255,0)_60%),linear-gradient(180deg,rgba(255,255,255,0.28)_0%,rgba(255,255,255,0.12)_100%)]",
|
|
37
|
+
"dark:[background-image:radial-gradient(120%_85%_at_15%_8%,rgba(255,255,255,0.18)_0%,rgba(255,255,255,0.05)_40%,rgba(255,255,255,0)_70%),radial-gradient(110%_80%_at_85%_100%,rgba(120,170,255,0.12)_0%,rgba(0,0,0,0)_60%),linear-gradient(180deg,rgba(255,255,255,0.06)_0%,rgba(0,0,0,0.18)_100%)]",
|
|
38
|
+
"[background-size:200%_200%,180%_180%,100%_100%]",
|
|
39
|
+
"[background-repeat:no-repeat]",
|
|
40
|
+
|
|
41
|
+
// 4) Multi-layer bevel:
|
|
42
|
+
// - inset top hairline highlight (light-from-above)
|
|
43
|
+
// - inset fish-eye glow at the bottom (lensing artifact)
|
|
44
|
+
// - thin inset side rails
|
|
45
|
+
// - outer contact shadow + broader halo (panel feels floating)
|
|
46
|
+
"shadow-[inset_0_1px_0_0_rgba(255,255,255,0.65),inset_0_-12px_24px_-10px_rgba(255,255,255,0.45),inset_1px_0_0_0_rgba(255,255,255,0.22),inset_-1px_0_0_0_rgba(255,255,255,0.18),0_24px_60px_-18px_rgba(15,23,42,0.30),0_8px_24px_-8px_rgba(15,23,42,0.18)]",
|
|
47
|
+
"dark:shadow-[inset_0_1px_0_0_rgba(255,255,255,0.30),inset_0_-14px_28px_-10px_rgba(180,210,255,0.18),inset_1px_0_0_0_rgba(255,255,255,0.10),inset_-1px_0_0_0_rgba(255,255,255,0.08),0_28px_70px_-18px_rgba(0,0,0,0.55),0_10px_28px_-10px_rgba(0,0,0,0.40)]",
|
|
48
|
+
|
|
49
|
+
// 5) Hairline border for the chamfer's outer edge.
|
|
50
|
+
"border border-white/[0.45] dark:border-white/[0.10]",
|
|
51
|
+
|
|
52
|
+
// 6) Slow ambient drift; respects prefers-reduced-motion.
|
|
53
|
+
"animate-[liquid-drift_18s_ease-in-out_infinite] motion-reduce:animate-none",
|
|
54
|
+
"[will-change:background-position]",
|
|
55
|
+
].join(" "),
|
|
56
|
+
"liquid-refract": "",
|
|
57
|
+
};
|
package/src/lib/sound.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interaction sound for the library, powered by cuelume (Web Audio synthesis,
|
|
3
|
+
* zero runtime deps, SSR-safe).
|
|
4
|
+
*
|
|
5
|
+
* Components only emit inert `data-cuelume-*` attributes — nothing plays
|
|
6
|
+
* until the consuming app opts in once:
|
|
7
|
+
*
|
|
8
|
+
* import { bindSounds } from "@corpora/ui"
|
|
9
|
+
* bindSounds()
|
|
10
|
+
*
|
|
11
|
+
* The app owns preferences via setSoundEnabled / setSoundVolume.
|
|
12
|
+
*/
|
|
13
|
+
import { bind, play } from "cuelume";
|
|
14
|
+
import type { SoundName } from "cuelume";
|
|
15
|
+
|
|
16
|
+
let bound = false;
|
|
17
|
+
|
|
18
|
+
/** Opt into interaction sound. Delegated and idempotent (see cuelume bind). */
|
|
19
|
+
export function bindSounds(root?: ParentNode): void {
|
|
20
|
+
bound = true;
|
|
21
|
+
bind(root);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Imperative cue used by library components for keyboard-driven interactions
|
|
26
|
+
* the data attributes can't cover (OTP typing, visibility toggles, …).
|
|
27
|
+
* No-op until the app has opted in via bindSounds().
|
|
28
|
+
*/
|
|
29
|
+
export function playCue(name: SoundName, options?: { volume?: number }): void {
|
|
30
|
+
if (bound) play(name, options);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
play as playSound,
|
|
35
|
+
setEnabled as setSoundEnabled,
|
|
36
|
+
setVolume as setSoundVolume,
|
|
37
|
+
sounds,
|
|
38
|
+
} from "cuelume";
|
|
39
|
+
export type { SoundName };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
|
|
3
|
+
import { glassVariantStyles } from "./glass-variants"
|
|
4
|
+
import { cn } from "./utils"
|
|
5
|
+
|
|
6
|
+
describe("cn", () => {
|
|
7
|
+
test("merges conflicting tailwind classes", () => {
|
|
8
|
+
expect(cn("p-2", "p-4")).toBe("p-4")
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test("drops falsy values", () => {
|
|
12
|
+
expect(cn("a", undefined, null, "c")).toBe("a c")
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
describe("glassVariantStyles", () => {
|
|
17
|
+
test("has a style entry for every glass finish", () => {
|
|
18
|
+
expect(Object.keys(glassVariantStyles).sort()).toEqual(
|
|
19
|
+
["clear", "frosted", "liquid", "liquid-refract", "subtle"].sort(),
|
|
20
|
+
)
|
|
21
|
+
})
|
|
22
|
+
})
|