@boyernick/standard-ui-react 0.1.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 (67) hide show
  1. package/README.md +9 -0
  2. package/package.json +46 -0
  3. package/src/accordion.tsx +84 -0
  4. package/src/alert-dialog.tsx +101 -0
  5. package/src/attachment.tsx +138 -0
  6. package/src/autocomplete.tsx +281 -0
  7. package/src/avatar.tsx +56 -0
  8. package/src/badge.tsx +28 -0
  9. package/src/brand.tsx +70 -0
  10. package/src/breadcrumb.tsx +81 -0
  11. package/src/button.tsx +129 -0
  12. package/src/calendar.tsx +84 -0
  13. package/src/card.tsx +54 -0
  14. package/src/carousel.tsx +253 -0
  15. package/src/chart.tsx +185 -0
  16. package/src/checkbox-group.tsx +14 -0
  17. package/src/checkbox.tsx +31 -0
  18. package/src/code-block.tsx +124 -0
  19. package/src/collapsible.tsx +61 -0
  20. package/src/combobox.tsx +324 -0
  21. package/src/command.tsx +377 -0
  22. package/src/context-menu.tsx +250 -0
  23. package/src/dialog.tsx +78 -0
  24. package/src/drawer.tsx +156 -0
  25. package/src/empty.tsx +52 -0
  26. package/src/field.tsx +75 -0
  27. package/src/fieldset.tsx +25 -0
  28. package/src/form.tsx +14 -0
  29. package/src/icons.tsx +91 -0
  30. package/src/illustrations.tsx +167 -0
  31. package/src/image-modal.tsx +110 -0
  32. package/src/index.ts +833 -0
  33. package/src/input.tsx +68 -0
  34. package/src/lib/cn.ts +3 -0
  35. package/src/lib/motion.ts +23 -0
  36. package/src/markdown-editor.tsx +302 -0
  37. package/src/menu.tsx +205 -0
  38. package/src/menubar.tsx +22 -0
  39. package/src/meter.tsx +61 -0
  40. package/src/navigation-menu.tsx +217 -0
  41. package/src/number-field.tsx +119 -0
  42. package/src/orb.tsx +33 -0
  43. package/src/otp-field.tsx +45 -0
  44. package/src/pagination.tsx +91 -0
  45. package/src/popover.tsx +92 -0
  46. package/src/preview-card.tsx +103 -0
  47. package/src/progress.tsx +60 -0
  48. package/src/radio.tsx +43 -0
  49. package/src/scroll-area.tsx +67 -0
  50. package/src/select.tsx +161 -0
  51. package/src/separator.tsx +17 -0
  52. package/src/sidebar.tsx +82 -0
  53. package/src/skeleton.tsx +32 -0
  54. package/src/slider.tsx +56 -0
  55. package/src/sounds.tsx +270 -0
  56. package/src/spinner.tsx +45 -0
  57. package/src/switch.tsx +19 -0
  58. package/src/table.tsx +81 -0
  59. package/src/tabs.tsx +62 -0
  60. package/src/text-animate.tsx +155 -0
  61. package/src/textarea.tsx +58 -0
  62. package/src/ticker.tsx +74 -0
  63. package/src/toast.tsx +142 -0
  64. package/src/toggle.tsx +32 -0
  65. package/src/toolbar.tsx +75 -0
  66. package/src/tooltip.tsx +55 -0
  67. package/src/video-player.tsx +241 -0
package/src/avatar.tsx ADDED
@@ -0,0 +1,56 @@
1
+ "use client"
2
+
3
+ import { Avatar as BaseAvatar } from "@base-ui/react/avatar"
4
+ import { cva, type VariantProps } from "class-variance-authority"
5
+ import type { ComponentProps } from "react"
6
+ import { cn } from "./lib/cn"
7
+
8
+ const avatarVariants = cva(
9
+ "relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-background-tertiary text-fg-primary align-middle shadow-hairline select-none",
10
+ {
11
+ variants: {
12
+ size: {
13
+ sm: "size-8 text-xs",
14
+ md: "size-10 text-xs-strong",
15
+ lg: "size-12 text-sm",
16
+ },
17
+ },
18
+ defaultVariants: {
19
+ size: "md",
20
+ },
21
+ },
22
+ )
23
+
24
+ export type AvatarProps = ComponentProps<typeof BaseAvatar.Root> &
25
+ VariantProps<typeof avatarVariants>
26
+ export type AvatarImageProps = ComponentProps<typeof BaseAvatar.Image>
27
+ export type AvatarFallbackProps = ComponentProps<typeof BaseAvatar.Fallback>
28
+
29
+ export const Avatar = ({ className, size, ...props }: AvatarProps) => (
30
+ <BaseAvatar.Root
31
+ className={cn(avatarVariants({ size }), className)}
32
+ {...props}
33
+ />
34
+ )
35
+
36
+ export const AvatarImage = ({ className, ...props }: AvatarImageProps) => (
37
+ <BaseAvatar.Image
38
+ className={cn("size-full object-cover", className)}
39
+ {...props}
40
+ />
41
+ )
42
+
43
+ export const AvatarFallback = ({
44
+ className,
45
+ ...props
46
+ }: AvatarFallbackProps) => (
47
+ <BaseAvatar.Fallback
48
+ className={cn(
49
+ "flex size-full items-center justify-center bg-background-tertiary tracking-tight text-inherit",
50
+ className,
51
+ )}
52
+ {...props}
53
+ />
54
+ )
55
+
56
+ export { avatarVariants }
package/src/badge.tsx ADDED
@@ -0,0 +1,28 @@
1
+ import { cva, type VariantProps } from "class-variance-authority"
2
+ import type { HTMLAttributes } from "react"
3
+ import { cn } from "./lib/cn"
4
+
5
+ const badgeVariants = cva(
6
+ "text-xs-strong inline-flex items-center rounded-full py-0.5",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: "bg-background-tertiary px-3 text-fg-primary",
11
+ outline: "border border-border-primary px-2 text-fg-secondary",
12
+ destructive: "bg-destructive/10 px-2 text-destructive",
13
+ },
14
+ },
15
+ defaultVariants: {
16
+ variant: "default",
17
+ },
18
+ },
19
+ )
20
+
21
+ export type BadgeProps = HTMLAttributes<HTMLSpanElement> &
22
+ VariantProps<typeof badgeVariants>
23
+
24
+ export const Badge = ({ className, variant, ...props }: BadgeProps) => (
25
+ <span className={cn(badgeVariants({ variant }), className)} {...props} />
26
+ )
27
+
28
+ export { badgeVariants }
package/src/brand.tsx ADDED
@@ -0,0 +1,70 @@
1
+ import type { SVGProps } from "react"
2
+ import { cn } from "./lib/cn"
3
+
4
+ export type BrandMarkProps = SVGProps<SVGSVGElement> & {
5
+ size?: number
6
+ title?: string
7
+ }
8
+
9
+ /** Solid disk — canonical StandardUI mark. Color via currentColor / parent text. */
10
+ export const BrandMark = ({
11
+ size = 24,
12
+ className,
13
+ title = "StandardUI",
14
+ ...props
15
+ }: BrandMarkProps) => (
16
+ <svg
17
+ width={size}
18
+ height={size}
19
+ viewBox="0 0 128 128"
20
+ fill="none"
21
+ xmlns="http://www.w3.org/2000/svg"
22
+ className={cn("shrink-0 text-current", className)}
23
+ aria-hidden={title ? undefined : true}
24
+ role={title ? "img" : undefined}
25
+ {...props}
26
+ >
27
+ {title ? <title>{title}</title> : null}
28
+ <circle cx="64" cy="64" r="64" fill="currentColor" />
29
+ </svg>
30
+ )
31
+
32
+ export type BrandWordmarkProps = {
33
+ className?: string
34
+ markSize?: number
35
+ /** Dense chrome: mark + “UI” only. Default lockup is mark + “StandardUI”. */
36
+ compact?: boolean
37
+ /** Chrome lockups use sm; marketing uses md */
38
+ size?: "sm" | "md"
39
+ }
40
+
41
+ /** Disk + StandardUI — matches the canonical lockup. Inherits color for light/dark. */
42
+ export const BrandWordmark = ({
43
+ className,
44
+ markSize,
45
+ compact = false,
46
+ size = "md",
47
+ }: BrandWordmarkProps) => {
48
+ const resolvedMarkSize = markSize ?? (size === "sm" ? 16 : 28)
49
+
50
+ return (
51
+ <span
52
+ className={cn(
53
+ "inline-flex items-center",
54
+ size === "sm" ? "gap-1" : "gap-1.5",
55
+ className,
56
+ )}
57
+ aria-label="StandardUI"
58
+ >
59
+ <BrandMark size={resolvedMarkSize} title="" aria-hidden />
60
+ <span
61
+ className={cn(
62
+ "tracking-tight",
63
+ size === "sm" ? "text-sm-strong" : "heading-lg-sans",
64
+ )}
65
+ >
66
+ {compact ? "UI" : "StandardUI"}
67
+ </span>
68
+ </span>
69
+ )
70
+ }
@@ -0,0 +1,81 @@
1
+ import type { ComponentProps, ReactNode } from "react";
2
+ import { IconChevronRightSmall } from "./icons";
3
+ import { cn } from "./lib/cn";
4
+ import { motion } from "./lib/motion";
5
+
6
+ export type BreadcrumbProps = ComponentProps<"nav">;
7
+ export type BreadcrumbListProps = ComponentProps<"ol">;
8
+ export type BreadcrumbItemProps = ComponentProps<"li">;
9
+ export type BreadcrumbLinkProps = ComponentProps<"a">;
10
+ export type BreadcrumbPageProps = ComponentProps<"span">;
11
+ export type BreadcrumbSeparatorProps = ComponentProps<"li"> & {
12
+ children?: ReactNode;
13
+ };
14
+
15
+ export const Breadcrumb = ({ className, ...props }: BreadcrumbProps) => (
16
+ <nav
17
+ aria-label="Breadcrumb"
18
+ className={cn("text-sm text-fg-secondary", className)}
19
+ {...props}
20
+ />
21
+ );
22
+
23
+ export const BreadcrumbList = ({
24
+ className,
25
+ ...props
26
+ }: BreadcrumbListProps) => (
27
+ <ol
28
+ className={cn("flex flex-wrap items-center gap-1.5", className)}
29
+ {...props}
30
+ />
31
+ );
32
+
33
+ export const BreadcrumbItem = ({
34
+ className,
35
+ ...props
36
+ }: BreadcrumbItemProps) => (
37
+ <li
38
+ className={cn("inline-flex items-center gap-1.5", className)}
39
+ {...props}
40
+ />
41
+ );
42
+
43
+ export const BreadcrumbLink = ({
44
+ className,
45
+ ...props
46
+ }: BreadcrumbLinkProps) => (
47
+ <a
48
+ className={cn(
49
+ "cursor-pointer rounded-sm text-fg-secondary outline-none hover:text-fg-primary focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20",
50
+ motion.colors,
51
+ className,
52
+ )}
53
+ {...props}
54
+ />
55
+ );
56
+
57
+ export const BreadcrumbPage = ({
58
+ className,
59
+ ...props
60
+ }: BreadcrumbPageProps) => (
61
+ <span
62
+ aria-current="page"
63
+ className={cn("font-medium text-fg-primary", className)}
64
+ {...props}
65
+ />
66
+ );
67
+
68
+ export const BreadcrumbSeparator = ({
69
+ className,
70
+ children,
71
+ ...props
72
+ }: BreadcrumbSeparatorProps) => (
73
+ <li
74
+ role="presentation"
75
+ aria-hidden
76
+ className={cn("text-fg-tertiary [&_svg]:size-3.5", className)}
77
+ {...props}
78
+ >
79
+ {children ?? <IconChevronRightSmall />}
80
+ </li>
81
+ );
package/src/button.tsx ADDED
@@ -0,0 +1,129 @@
1
+ import { cva, type VariantProps } from "class-variance-authority"
2
+ import type { ButtonHTMLAttributes, ReactNode } from "react"
3
+ import { cn } from "./lib/cn"
4
+
5
+ const buttonVariants = cva(
6
+ "text-sm inline-flex cursor-pointer items-center justify-center gap-1.5 whitespace-nowrap transition-all duration-150 ease-out outline-none focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary disabled:cursor-not-allowed disabled:opacity-50 disabled:active:translate-y-0 active:translate-y-px [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ primary:
11
+ "border border-brand-primary-border bg-brand-primary text-brand-foreground inset-shadow-solid-top hover:bg-brand-primary-hover active:bg-brand-primary-active focus-visible:border-ring focus-visible:ring-ring/20",
12
+ secondary:
13
+ "border border-transparent bg-background-tertiary text-fg-primary hover:bg-background-quaternary active:bg-background-active focus-visible:border-ring focus-visible:ring-ring/20 dark:bg-gray-250 dark:hover:bg-gray-350 dark:active:bg-gray-200",
14
+ outline:
15
+ "border border-border-secondary bg-surface text-fg-primary inset-shadow-outline-top hover:bg-background-tertiary focus-visible:border-ring focus-visible:ring-ring/20",
16
+ ghost:
17
+ "border border-transparent text-fg-secondary hover:bg-background-tertiary hover:text-fg-primary focus-visible:border-ring focus-visible:ring-ring/20",
18
+ destructive:
19
+ "border border-destructive-active bg-destructive text-white inset-shadow-solid-top hover:bg-destructive-active focus-visible:border-destructive focus-visible:ring-destructive/20",
20
+ },
21
+ size: {
22
+ sm: "h-8 px-3",
23
+ md: "h-9 px-3.5",
24
+ lg: "h-10 px-4",
25
+ },
26
+ rounded: {
27
+ true: "rounded-full",
28
+ false: "rounded-md",
29
+ },
30
+ iconOnly: {
31
+ true: "px-0",
32
+ false: "",
33
+ },
34
+ },
35
+ compoundVariants: [
36
+ { iconOnly: true, size: "sm", class: "size-8" },
37
+ { iconOnly: true, size: "md", class: "size-9" },
38
+ { iconOnly: true, size: "lg", class: "size-10" },
39
+ ],
40
+ defaultVariants: {
41
+ variant: "primary",
42
+ size: "md",
43
+ rounded: false,
44
+ iconOnly: false,
45
+ },
46
+ },
47
+ )
48
+
49
+ const ButtonSpinner = ({ className }: { className?: string }) => (
50
+ <svg
51
+ className={cn("size-4 animate-spin motion-reduce:animate-none", className)}
52
+ viewBox="0 0 24 24"
53
+ fill="none"
54
+ aria-hidden
55
+ >
56
+ <path
57
+ d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z"
58
+ stroke="currentColor"
59
+ strokeOpacity="0.3"
60
+ strokeWidth="2"
61
+ />
62
+ <path
63
+ d="M21 12C21 16.9706 16.9706 21 12 21"
64
+ stroke="currentColor"
65
+ strokeWidth="2"
66
+ />
67
+ </svg>
68
+ )
69
+
70
+ export type ButtonProps = Omit<
71
+ ButtonHTMLAttributes<HTMLButtonElement>,
72
+ "prefix"
73
+ > &
74
+ VariantProps<typeof buttonVariants> & {
75
+ /** Shows a spinner, sets aria-busy, and disables the button */
76
+ loading?: boolean
77
+ /** Square control sized to the height of `size` — pass an icon as children */
78
+ iconOnly?: boolean
79
+ /** Pill shape (`rounded-full`) */
80
+ rounded?: boolean
81
+ /** Icon before the label (replaced by spinner when loading) */
82
+ prefix?: ReactNode
83
+ /** Icon after the label */
84
+ suffix?: ReactNode
85
+ }
86
+
87
+ export const Button = ({
88
+ className,
89
+ variant,
90
+ size,
91
+ rounded,
92
+ iconOnly,
93
+ loading = false,
94
+ disabled,
95
+ prefix,
96
+ suffix,
97
+ children,
98
+ type = "button",
99
+ ...props
100
+ }: ButtonProps) => {
101
+ const isDisabled = Boolean(disabled || loading)
102
+ const showPrefix = loading ? <ButtonSpinner /> : prefix
103
+
104
+ return (
105
+ <button
106
+ type={type}
107
+ disabled={isDisabled}
108
+ aria-busy={loading || undefined}
109
+ data-loading={loading || undefined}
110
+ className={cn(
111
+ buttonVariants({ variant, size, rounded, iconOnly }),
112
+ className,
113
+ )}
114
+ {...props}
115
+ >
116
+ {loading && iconOnly ? (
117
+ <ButtonSpinner />
118
+ ) : (
119
+ <>
120
+ {showPrefix}
121
+ {children}
122
+ {suffix}
123
+ </>
124
+ )}
125
+ </button>
126
+ )
127
+ }
128
+
129
+ export { buttonVariants }
@@ -0,0 +1,84 @@
1
+ "use client"
2
+
3
+ import { DayPicker, type ChevronProps, type DayPickerProps } from "react-day-picker"
4
+ import { IconChevronBottom } from "./icons"
5
+ import { cn } from "./lib/cn"
6
+ import { motion } from "./lib/motion"
7
+
8
+ export type CalendarProps = DayPickerProps
9
+
10
+ const navButtonClassName = cn(
11
+ "inline-flex size-8 items-center justify-center rounded-md text-fg-tertiary",
12
+ motion.colors,
13
+ "hover:bg-background-tertiary hover:text-fg-primary outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20 disabled:cursor-not-allowed disabled:opacity-50",
14
+ )
15
+
16
+ const CalendarChevron = ({
17
+ className,
18
+ orientation,
19
+ size = 16,
20
+ }: ChevronProps) => {
21
+ const rotation =
22
+ orientation === "left"
23
+ ? "rotate-90"
24
+ : orientation === "right"
25
+ ? "-rotate-90"
26
+ : orientation === "up"
27
+ ? "rotate-180"
28
+ : undefined
29
+
30
+ return (
31
+ <IconChevronBottom
32
+ size={size}
33
+ className={cn("text-fg-tertiary", rotation, className)}
34
+ aria-hidden
35
+ />
36
+ )
37
+ }
38
+
39
+ export const Calendar = ({
40
+ className,
41
+ classNames,
42
+ showOutsideDays = true,
43
+ components,
44
+ ...props
45
+ }: CalendarProps) => (
46
+ <DayPicker
47
+ showOutsideDays={showOutsideDays}
48
+ className={cn("w-fit p-3", className)}
49
+ classNames={{
50
+ months: "relative flex flex-col gap-4 sm:flex-row",
51
+ month: "flex w-full flex-col gap-3",
52
+ month_caption: "relative flex h-8 items-center justify-center px-8",
53
+ caption_label: "text-sm-strong text-fg-primary",
54
+ nav: "absolute inset-x-0 top-0 flex items-center justify-between",
55
+ button_previous: navButtonClassName,
56
+ button_next: navButtonClassName,
57
+ month_grid: "w-full border-collapse",
58
+ weekdays: "flex",
59
+ weekday:
60
+ "flex size-8 items-center justify-center text-xs font-normal text-fg-tertiary",
61
+ weeks: "flex flex-col",
62
+ week: "mt-0.5 flex w-full",
63
+ day: "relative p-0 text-center",
64
+ day_button: cn(
65
+ "inline-flex size-8 items-center justify-center rounded-md text-sm text-fg-primary",
66
+ motion.colors,
67
+ "hover:bg-background-tertiary outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20 disabled:cursor-not-allowed disabled:opacity-50 aria-selected:opacity-100",
68
+ ),
69
+ selected:
70
+ "[&>button]:bg-brand-primary [&>button]:text-brand-foreground [&>button]:ring-0 [&>button]:hover:bg-brand-primary-hover [&>button]:hover:text-brand-foreground",
71
+ today:
72
+ "[&>button]:font-medium [&>button]:ring-1 [&>button]:ring-inset [&>button]:ring-border-secondary",
73
+ outside: "[&>button]:text-fg-quaternary",
74
+ disabled: "[&>button]:opacity-40",
75
+ hidden: "invisible",
76
+ ...classNames,
77
+ }}
78
+ components={{
79
+ Chevron: CalendarChevron,
80
+ ...components,
81
+ }}
82
+ {...({ mode: "single", ...props } as DayPickerProps)}
83
+ />
84
+ )
package/src/card.tsx ADDED
@@ -0,0 +1,54 @@
1
+ import type { HTMLAttributes } from "react"
2
+ import { cn } from "./lib/cn"
3
+
4
+ export type CardProps = HTMLAttributes<HTMLDivElement>
5
+ export type CardHeaderProps = HTMLAttributes<HTMLDivElement>
6
+ export type CardTitleProps = HTMLAttributes<HTMLHeadingElement>
7
+ export type CardDescriptionProps = HTMLAttributes<HTMLParagraphElement>
8
+ export type CardContentProps = HTMLAttributes<HTMLDivElement>
9
+ export type CardFooterProps = HTMLAttributes<HTMLDivElement>
10
+
11
+ export const Card = ({ className, ...props }: CardProps) => (
12
+ <div
13
+ className={cn(
14
+ "flex flex-col overflow-hidden rounded-xl border border-border-primary bg-surface",
15
+ className,
16
+ )}
17
+ {...props}
18
+ />
19
+ )
20
+
21
+ export const CardHeader = ({ className, ...props }: CardHeaderProps) => (
22
+ <div
23
+ className={cn("flex flex-col gap-1 px-5 pt-5 pb-3", className)}
24
+ {...props}
25
+ />
26
+ )
27
+
28
+ export const CardTitle = ({ className, ...props }: CardTitleProps) => (
29
+ <h3 className={cn("text-sm-strong text-fg-primary", className)} {...props} />
30
+ )
31
+
32
+ export const CardDescription = ({
33
+ className,
34
+ ...props
35
+ }: CardDescriptionProps) => (
36
+ <p
37
+ className={cn("text-sm leading-relaxed text-fg-secondary", className)}
38
+ {...props}
39
+ />
40
+ )
41
+
42
+ export const CardContent = ({ className, ...props }: CardContentProps) => (
43
+ <div className={cn("px-5 pb-5", className)} {...props} />
44
+ )
45
+
46
+ export const CardFooter = ({ className, ...props }: CardFooterProps) => (
47
+ <div
48
+ className={cn(
49
+ "flex items-center gap-2 border-t border-border-primary px-5 py-4",
50
+ className,
51
+ )}
52
+ {...props}
53
+ />
54
+ )