@spunto/design-system 0.16.0 → 0.18.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 +40 -3
- package/package.json +1 -1
- package/src/components/auth/auth-brand-panel.tsx +163 -0
- package/src/components/auth/auth-form.tsx +263 -0
- package/src/components/auth/auth-layout.tsx +79 -0
- package/src/components/auth/auth-separator.tsx +24 -0
- package/src/components/auth/index.ts +38 -0
- package/src/components/auth/oauth-button.tsx +81 -0
- package/src/components/auth/oauth-icons.tsx +53 -0
- package/src/components/auth/types.ts +29 -0
- package/src/components/command-menu.tsx +630 -0
- package/src/components/command-palette.tsx +7 -50
- package/src/components/command-shared.tsx +59 -0
- package/src/components/sheet.tsx +173 -0
- package/src/components/table.tsx +98 -0
- package/src/index.ts +87 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { ReactNode } from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../utils"
|
|
4
|
+
import { buttonVariants } from "../button-variants"
|
|
5
|
+
import { GitHubIcon, GoogleIcon } from "./oauth-icons"
|
|
6
|
+
import type { AuthLinkRender } from "./types"
|
|
7
|
+
|
|
8
|
+
/* ─── OAuthButton ──────────────────────────────────────────────────────────
|
|
9
|
+
"Continue with <provider>", full width, above the separator.
|
|
10
|
+
|
|
11
|
+
It's the design system's `Button` in its `outline` register, only taller and
|
|
12
|
+
rounder than any of its sizes — an auth screen's two big targets (this one and
|
|
13
|
+
the submit) are 44 px pills, not 32 px toolbar buttons. That's a `className`
|
|
14
|
+
over `buttonVariants`, deliberately, rather than a new `size` on the primitive:
|
|
15
|
+
the shape belongs to this screen, not to buttons in general.
|
|
16
|
+
|
|
17
|
+
Going through `buttonVariants` (and not the `Button` component) is also what
|
|
18
|
+
makes the link case honest: an OAuth "button" is nearly always an `<a href>`
|
|
19
|
+
to the API's redirect route, and both cases must come out pixel-identical. */
|
|
20
|
+
|
|
21
|
+
export type OAuthProvider = "google" | "github"
|
|
22
|
+
|
|
23
|
+
const providers: Record<OAuthProvider, { label: string; icon: ReactNode }> = {
|
|
24
|
+
google: { label: "Continue with Google", icon: <GoogleIcon /> },
|
|
25
|
+
github: { label: "Continue with GitHub", icon: <GitHubIcon /> },
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface OAuthButtonProps {
|
|
29
|
+
/** Picks the shipped icon and the default label. */
|
|
30
|
+
provider?: OAuthProvider
|
|
31
|
+
/** Overrides the provider's icon (or supplies one for a provider we don't ship). */
|
|
32
|
+
icon?: ReactNode
|
|
33
|
+
/** Overrides the label. Defaults to "Continue with <provider>". */
|
|
34
|
+
children?: ReactNode
|
|
35
|
+
/**
|
|
36
|
+
* Where the provider dance starts — an API route, not a client-side one
|
|
37
|
+
* (`/api/auth/login`). Set it and the button renders as an `<a>`.
|
|
38
|
+
*/
|
|
39
|
+
href?: string
|
|
40
|
+
/** Alternative to `href` when the app opens the flow itself (a popup, a SDK call). */
|
|
41
|
+
onClick?: () => void
|
|
42
|
+
disabled?: boolean
|
|
43
|
+
/** `link` renders the anchor (a router link…); without it, a plain `<a>`. */
|
|
44
|
+
render?: { link?: AuthLinkRender }
|
|
45
|
+
className?: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function OAuthButton({
|
|
49
|
+
provider = "google",
|
|
50
|
+
icon,
|
|
51
|
+
children,
|
|
52
|
+
href,
|
|
53
|
+
onClick,
|
|
54
|
+
disabled,
|
|
55
|
+
render,
|
|
56
|
+
className,
|
|
57
|
+
}: OAuthButtonProps) {
|
|
58
|
+
const preset = providers[provider]
|
|
59
|
+
const classes = cn(
|
|
60
|
+
buttonVariants({ variant: "outline" }),
|
|
61
|
+
"h-auto w-full gap-3 rounded-xl bg-card px-4 py-3 text-sm font-medium text-foreground",
|
|
62
|
+
className
|
|
63
|
+
)
|
|
64
|
+
const content = (
|
|
65
|
+
<>
|
|
66
|
+
{icon ?? preset?.icon}
|
|
67
|
+
{children ?? preset?.label}
|
|
68
|
+
</>
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
if (href && !disabled) {
|
|
72
|
+
const link = render?.link ?? ((props) => <a {...props} />)
|
|
73
|
+
return link({ href, className: classes, children: content })
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<button type="button" className={classes} onClick={onClick} disabled={disabled}>
|
|
78
|
+
{content}
|
|
79
|
+
</button>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ComponentProps } from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../utils"
|
|
4
|
+
|
|
5
|
+
/* The two marks that actually appear on an auth screen, shipped with the package
|
|
6
|
+
for the same reason `/devcontainer` ships its stack marks: a login button that
|
|
7
|
+
has to fetch its provider logo from a CDN is a login button that renders wrong
|
|
8
|
+
on a cold cache. Google's is drawn in its brand colors (its guidelines don't
|
|
9
|
+
allow recoloring it); GitHub's inherits `currentColor` like every other glyph. */
|
|
10
|
+
|
|
11
|
+
export function GoogleIcon({ className, ...props }: ComponentProps<"svg">) {
|
|
12
|
+
return (
|
|
13
|
+
<svg
|
|
14
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
15
|
+
viewBox="0 0 24 24"
|
|
16
|
+
aria-hidden="true"
|
|
17
|
+
className={cn("h-4 w-4 shrink-0", className)}
|
|
18
|
+
{...props}
|
|
19
|
+
>
|
|
20
|
+
<path
|
|
21
|
+
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
|
|
22
|
+
fill="#4285F4"
|
|
23
|
+
/>
|
|
24
|
+
<path
|
|
25
|
+
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
|
|
26
|
+
fill="#34A853"
|
|
27
|
+
/>
|
|
28
|
+
<path
|
|
29
|
+
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
|
|
30
|
+
fill="#FBBC05"
|
|
31
|
+
/>
|
|
32
|
+
<path
|
|
33
|
+
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
|
|
34
|
+
fill="#EA4335"
|
|
35
|
+
/>
|
|
36
|
+
</svg>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function GitHubIcon({ className, ...props }: ComponentProps<"svg">) {
|
|
41
|
+
return (
|
|
42
|
+
<svg
|
|
43
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
44
|
+
viewBox="0 0 24 24"
|
|
45
|
+
fill="currentColor"
|
|
46
|
+
aria-hidden="true"
|
|
47
|
+
className={cn("h-4 w-4 shrink-0", className)}
|
|
48
|
+
{...props}
|
|
49
|
+
>
|
|
50
|
+
<path d="M12 .5C5.73.5.5 5.73.5 12a11.5 11.5 0 0 0 7.86 10.92c.58.1.79-.25.79-.56v-2.17c-3.2.7-3.88-1.37-3.88-1.37-.53-1.34-1.29-1.7-1.29-1.7-1.05-.72.08-.7.08-.7 1.16.08 1.77 1.2 1.77 1.2 1.03 1.77 2.7 1.26 3.36.96.1-.75.4-1.26.73-1.55-2.55-.29-5.24-1.28-5.24-5.7 0-1.26.45-2.29 1.19-3.1-.12-.29-.52-1.46.11-3.05 0 0 .97-.31 3.18 1.18a11 11 0 0 1 5.79 0c2.2-1.5 3.17-1.18 3.17-1.18.63 1.59.24 2.76.12 3.05.74.81 1.19 1.84 1.19 3.1 0 4.43-2.7 5.4-5.26 5.69.41.36.78 1.06.78 2.14v3.17c0 .31.2.67.8.56A11.5 11.5 0 0 0 23.5 12C23.5 5.73 18.27.5 12 .5z" />
|
|
51
|
+
</svg>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ComponentProps, ReactElement } from "react"
|
|
2
|
+
|
|
3
|
+
/** The two faces of one screen: signing in, or opening an account. */
|
|
4
|
+
export type AuthMode = "login" | "register"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* What `AuthForm` hands back on submit. `name` only exists in `register` mode
|
|
8
|
+
* (and only when the display-name field is shown and filled).
|
|
9
|
+
*
|
|
10
|
+
* The package **never calls an API**: it collects, validates natively (`required`,
|
|
11
|
+
* `type="email"`, `minLength`) and gives the values back. Which endpoint they go
|
|
12
|
+
* to, what a session cookie is, and where to redirect afterwards are the app's.
|
|
13
|
+
*/
|
|
14
|
+
export interface AuthSubmitValues {
|
|
15
|
+
mode: AuthMode
|
|
16
|
+
email: string
|
|
17
|
+
password: string
|
|
18
|
+
name?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Props handed to the app's link component — spread them all.
|
|
23
|
+
*
|
|
24
|
+
* Same slot motif as `CommandPaletteLinkRender` and `WorkerCard`'s `render.link`:
|
|
25
|
+
* the package never imports `next/link`, so anything that navigates takes an
|
|
26
|
+
* `href` plus an optional renderer. Without one, it falls back to a plain `<a>`.
|
|
27
|
+
*/
|
|
28
|
+
export type AuthLinkProps = ComponentProps<"a"> & { href: string }
|
|
29
|
+
export type AuthLinkRender = (props: AuthLinkProps) => ReactElement
|