@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.
@@ -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