@boyernick/standard-ui-react 0.1.0 → 0.1.1-canary.10

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,174 @@
1
+ "use client"
2
+
3
+ import { useState, type ReactNode } from "react"
4
+ import type { DateRange, PropsBase } from "react-day-picker"
5
+ import { Button } from "./button"
6
+ import { Calendar } from "./calendar"
7
+ import { IconCalendar1 } from "./icons"
8
+ import { cn } from "./lib/cn"
9
+ import {
10
+ Popover,
11
+ PopoverPopup,
12
+ PopoverPortal,
13
+ PopoverPositioner,
14
+ PopoverTrigger,
15
+ } from "./popover"
16
+
17
+ const dateFormatter = new Intl.DateTimeFormat("en-US", {
18
+ month: "short",
19
+ day: "numeric",
20
+ year: "numeric",
21
+ })
22
+
23
+ const formatDate = (date: Date) => dateFormatter.format(date)
24
+
25
+ type DatePickerBaseProps = {
26
+ /** Options forwarded to Calendar, excluding its selection mode. */
27
+ calendarProps?: Omit<PropsBase, "mode" | "required">
28
+ /** Classes applied to the trigger button. */
29
+ className?: string
30
+ /** Disables the trigger button. */
31
+ disabled?: boolean
32
+ /** Placeholder shown when no date is selected. */
33
+ placeholder?: string
34
+ }
35
+
36
+ export type DatePickerSingleProps = DatePickerBaseProps & {
37
+ mode?: "single"
38
+ selected?: Date
39
+ onSelect?: (date: Date | undefined) => void
40
+ }
41
+
42
+ export type DatePickerRangeProps = DatePickerBaseProps & {
43
+ mode: "range"
44
+ selected?: DateRange
45
+ onSelect?: (range: DateRange | undefined) => void
46
+ }
47
+
48
+ export type DatePickerProps = DatePickerSingleProps | DatePickerRangeProps
49
+
50
+ const DatePickerTrigger = ({
51
+ className,
52
+ disabled,
53
+ empty,
54
+ label,
55
+ }: {
56
+ className?: string
57
+ disabled?: boolean
58
+ empty: boolean
59
+ label: string
60
+ }) => (
61
+ <PopoverTrigger
62
+ render={
63
+ <Button
64
+ type="button"
65
+ // Default md height, so the trigger lines up with every other field
66
+ // in a form rather than sitting 4px short of them.
67
+ size="md"
68
+ variant="outline"
69
+ disabled={disabled}
70
+ prefix={<IconCalendar1 aria-hidden />}
71
+ // The outline button fills on hover, which is right for a button and
72
+ // wrong here: this reads as a field, and fields in the system keep
73
+ // their surface. The border and the popover carry the state.
74
+ className={cn("w-56 justify-start hover:bg-surface", className)}
75
+ />
76
+ }
77
+ >
78
+ <span
79
+ className={cn(
80
+ "min-w-0 flex-1 truncate text-left",
81
+ empty && "text-fg-tertiary",
82
+ )}
83
+ >
84
+ {label}
85
+ </span>
86
+ </PopoverTrigger>
87
+ )
88
+
89
+ const DatePickerPopup = ({ children }: { children: ReactNode }) => (
90
+ <PopoverPortal>
91
+ <PopoverPositioner align="start" sideOffset={6}>
92
+ <PopoverPopup className="w-auto gap-0 p-0">{children}</PopoverPopup>
93
+ </PopoverPositioner>
94
+ </PopoverPortal>
95
+ )
96
+
97
+ const SingleDatePicker = ({
98
+ calendarProps,
99
+ className,
100
+ disabled,
101
+ onSelect,
102
+ placeholder = "Pick a date",
103
+ selected,
104
+ }: DatePickerSingleProps) => {
105
+ const [open, setOpen] = useState(false)
106
+
107
+ const handleSelect = (date: Date | undefined) => {
108
+ onSelect?.(date)
109
+ if (date) setOpen(false)
110
+ }
111
+
112
+ return (
113
+ <Popover open={open} onOpenChange={setOpen}>
114
+ <DatePickerTrigger
115
+ className={className}
116
+ disabled={disabled}
117
+ empty={!selected}
118
+ label={selected ? formatDate(selected) : placeholder}
119
+ />
120
+ <DatePickerPopup>
121
+ <Calendar
122
+ {...calendarProps}
123
+ className={cn("border-0", calendarProps?.className)}
124
+ mode="single"
125
+ selected={selected}
126
+ onSelect={handleSelect}
127
+ />
128
+ </DatePickerPopup>
129
+ </Popover>
130
+ )
131
+ }
132
+
133
+ const RangeDatePicker = ({
134
+ calendarProps,
135
+ className,
136
+ disabled,
137
+ onSelect,
138
+ placeholder = "Pick a date range",
139
+ selected,
140
+ }: DatePickerRangeProps) => {
141
+ const [open, setOpen] = useState(false)
142
+ const label = selected?.from
143
+ ? selected.to
144
+ ? `${formatDate(selected.from)} – ${formatDate(selected.to)}`
145
+ : `${formatDate(selected.from)} – Select end date`
146
+ : placeholder
147
+
148
+ return (
149
+ <Popover open={open} onOpenChange={setOpen}>
150
+ <DatePickerTrigger
151
+ className={className}
152
+ disabled={disabled}
153
+ empty={!selected?.from}
154
+ label={label}
155
+ />
156
+ <DatePickerPopup>
157
+ <Calendar
158
+ {...calendarProps}
159
+ className={cn("border-0", calendarProps?.className)}
160
+ mode="range"
161
+ selected={selected}
162
+ onSelect={onSelect}
163
+ />
164
+ </DatePickerPopup>
165
+ </Popover>
166
+ )
167
+ }
168
+
169
+ export const DatePicker = (props: DatePickerProps) =>
170
+ props.mode === "range" ? (
171
+ <RangeDatePicker {...props} />
172
+ ) : (
173
+ <SingleDatePicker {...props} />
174
+ )
package/src/dialog.tsx CHANGED
@@ -44,7 +44,7 @@ export const DialogBackdrop = ({
44
44
  export const DialogPopup = ({ className, ...props }: DialogPopupProps) => (
45
45
  <BaseDialog.Popup
46
46
  className={cn(
47
- "fixed top-1/2 left-1/2 z-50 flex w-full max-w-md -translate-x-1/2 -translate-y-1/2 flex-col gap-6 rounded-xl border border-border-primary bg-surface-raised p-5 shadow-md outline-none max-sm:max-w-[calc(100vw-2rem)]",
47
+ "fixed top-1/2 left-1/2 z-50 flex w-full max-w-md -translate-x-1/2 -translate-y-1/2 flex-col gap-6 rounded-xl border border-border-primary bg-surface-raised p-5 shadow-md outline-none max-h-[calc(100dvh-2rem)] overflow-y-auto max-sm:max-w-[calc(100vw-2rem)]",
48
48
  motion.popupCenter,
49
49
  className,
50
50
  )}
package/src/drawer.tsx CHANGED
@@ -69,7 +69,7 @@ export const DrawerPopup = ({ className, ...props }: DrawerPopupProps) => (
69
69
  <BaseDrawer.Popup
70
70
  className={cn(
71
71
  "fixed z-50 flex flex-col border-border-primary bg-surface shadow-md outline-none",
72
- "transition-[transform,opacity] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none",
72
+ "transition-[transform,opacity] duration-[var(--duration-lg)] ease-move motion-reduce:transition-none",
73
73
  "data-swiping:duration-0",
74
74
  // Right panel (swipeDirection="right")
75
75
  "data-[swipe-direction=right]:top-0 data-[swipe-direction=right]:right-0 data-[swipe-direction=right]:h-full data-[swipe-direction=right]:w-full data-[swipe-direction=right]:max-w-sm data-[swipe-direction=right]:border-l",
package/src/empty.tsx CHANGED
@@ -10,7 +10,7 @@ export type EmptyActionsProps = ComponentProps<"div">;
10
10
  export const Empty = ({ className, ...props }: EmptyProps) => (
11
11
  <div
12
12
  className={cn(
13
- "flex min-h-48 flex-col items-center justify-center gap-3 rounded-xl border border-dashed border-border-primary bg-surface p-8 text-center",
13
+ "flex min-h-48 flex-col items-center justify-center rounded-xl border border-dashed border-border-primary bg-surface p-8 text-center",
14
14
  className,
15
15
  )}
16
16
  {...props}
@@ -20,7 +20,7 @@ export const Empty = ({ className, ...props }: EmptyProps) => (
20
20
  export const EmptyIcon = ({ className, ...props }: EmptyIconProps) => (
21
21
  <div
22
22
  className={cn(
23
- "flex size-10 items-center justify-center rounded-lg bg-background-tertiary text-fg-secondary [&_svg]:size-5",
23
+ "mb-4 flex size-10 items-center justify-center rounded-lg bg-background-tertiary text-fg-secondary [&_svg]:size-5",
24
24
  className,
25
25
  )}
26
26
  {...props}
@@ -36,7 +36,7 @@ export const EmptyDescription = ({
36
36
  ...props
37
37
  }: EmptyDescriptionProps) => (
38
38
  <p
39
- className={cn("max-w-sm text-sm text-fg-secondary", className)}
39
+ className={cn("mt-1 max-w-sm text-sm text-fg-secondary", className)}
40
40
  {...props}
41
41
  />
42
42
  );
@@ -44,7 +44,7 @@ export const EmptyDescription = ({
44
44
  export const EmptyActions = ({ className, ...props }: EmptyActionsProps) => (
45
45
  <div
46
46
  className={cn(
47
- "mt-1 flex flex-wrap items-center justify-center gap-2",
47
+ "mt-4 flex flex-wrap items-center justify-center gap-2",
48
48
  className,
49
49
  )}
50
50
  {...props}
package/src/field.tsx CHANGED
@@ -49,7 +49,7 @@ export const FieldError = ({ className, ...props }: FieldErrorProps) => (
49
49
  export const FieldControl = ({ className, ...props }: FieldControlProps) => (
50
50
  <BaseField.Control
51
51
  className={cn(
52
- "text-sm flex h-9 w-full cursor-text rounded-md border border-border-secondary bg-surface px-3 text-fg-primary inset-shadow-outline-top outline-none transition-[color,box-shadow] duration-150 ease-out placeholder:text-fg-quaternary",
52
+ "text-sm flex h-9 w-full cursor-text rounded-md border border-border-secondary bg-surface px-3 text-fg-primary inset-shadow-outline-top outline-none transition-[color,box-shadow] duration-[var(--duration-sm)] ease-enter placeholder:text-fg-quaternary",
53
53
  "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20",
54
54
  "data-invalid:border-destructive data-invalid:focus-visible:border-destructive data-invalid:focus-visible:ring-destructive/20",
55
55
  "aria-invalid:border-destructive aria-invalid:focus-visible:border-destructive aria-invalid:focus-visible:ring-destructive/20",
package/src/icons.tsx CHANGED
@@ -2,13 +2,19 @@
2
2
 
3
3
  import type { CentralIconBaseProps } from "@central-icons-react/round-outlined-radius-2-stroke-2/CentralIconBase"
4
4
  import { IconBell as IconBellBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconBell"
5
+ import { IconCalendar1 as IconCalendar1Base } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconCalendar1"
5
6
  import { IconChainLink1 as IconChainLink1Base } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconChainLink1"
6
7
  import { IconCheckmark1 as IconCheckmark1Base } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconCheckmark1"
7
8
  import { IconChevronBottom as IconChevronBottomBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconChevronBottom"
8
9
  import { IconChevronDownSmall as IconChevronDownSmallBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconChevronDownSmall"
9
10
  import { IconChevronRightSmall as IconChevronRightSmallBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconChevronRightSmall"
11
+ import { IconCircleCheck as IconCircleCheckBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconCircleCheck"
12
+ import { IconCircleInfo as IconCircleInfoBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconCircleInfo"
10
13
  import { IconClipboard as IconClipboardBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconClipboard"
11
14
  import { IconCrossSmall as IconCrossSmallBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconCrossSmall"
15
+ import { IconDotGrid1x3Horizontal as IconDotGrid1x3HorizontalBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconDotGrid1x3Horizontal"
16
+ import { IconExclamationCircle as IconExclamationCircleBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconExclamationCircle"
17
+ import { IconExclamationTriangle as IconExclamationTriangleBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconExclamationTriangle"
12
18
  import { IconHome as IconHomeBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconHome"
13
19
  import { IconMagnifyingGlass as IconMagnifyingGlassBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconMagnifyingGlass"
14
20
  import { IconMinusMedium as IconMinusBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconMinusMedium"
@@ -17,6 +23,7 @@ import { IconPeople as IconPeopleBase } from "@central-icons-react/round-outline
17
23
  import { IconPlusMedium as IconPlusBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconPlusMedium"
18
24
  import { IconSettingsGear1 as IconSettingsGear1Base } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconSettingsGear1"
19
25
  import { IconSquareBehindSquare6 as IconSquareBehindSquare6Base } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconSquareBehindSquare6"
26
+ import { IconStar as IconStarBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconStar"
20
27
  import { IconSun as IconSunBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconSun"
21
28
  import { IconX as IconXBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconX"
22
29
  import type { ComponentType } from "react"
@@ -43,6 +50,7 @@ export const withCentralIconDefaults = (
43
50
  }
44
51
 
45
52
  export const IconBell = withCentralIconDefaults(IconBellBase)
53
+ export const IconCalendar1 = withCentralIconDefaults(IconCalendar1Base)
46
54
  export const IconChainLink1 = withCentralIconDefaults(IconChainLink1Base)
47
55
  export const IconCheckmark1 = withCentralIconDefaults(IconCheckmark1Base)
48
56
  export const IconChevronBottom = withCentralIconDefaults(IconChevronBottomBase)
@@ -52,8 +60,19 @@ export const IconChevronDownSmall = withCentralIconDefaults(
52
60
  export const IconChevronRightSmall = withCentralIconDefaults(
53
61
  IconChevronRightSmallBase,
54
62
  )
63
+ export const IconCircleCheck = withCentralIconDefaults(IconCircleCheckBase)
64
+ export const IconCircleInfo = withCentralIconDefaults(IconCircleInfoBase)
55
65
  export const IconClipboard = withCentralIconDefaults(IconClipboardBase)
56
66
  export const IconCrossSmall = withCentralIconDefaults(IconCrossSmallBase)
67
+ export const IconDotGrid1x3Horizontal = withCentralIconDefaults(
68
+ IconDotGrid1x3HorizontalBase,
69
+ )
70
+ export const IconExclamationCircle = withCentralIconDefaults(
71
+ IconExclamationCircleBase,
72
+ )
73
+ export const IconExclamationTriangle = withCentralIconDefaults(
74
+ IconExclamationTriangleBase,
75
+ )
57
76
  export const IconHome = withCentralIconDefaults(IconHomeBase)
58
77
  export const IconMagnifyingGlass = withCentralIconDefaults(
59
78
  IconMagnifyingGlassBase,
@@ -66,11 +85,13 @@ export const IconSettingsGear1 = withCentralIconDefaults(IconSettingsGear1Base)
66
85
  export const IconSquareBehindSquare6 = withCentralIconDefaults(
67
86
  IconSquareBehindSquare6Base,
68
87
  )
88
+ export const IconStar = withCentralIconDefaults(IconStarBase)
69
89
  export const IconSun = withCentralIconDefaults(IconSunBase)
70
90
  export const IconX = withCentralIconDefaults(IconXBase)
71
91
 
72
92
  export const iconGallery = [
73
93
  { name: "IconHome", Icon: IconHome },
94
+ { name: "IconCalendar1", Icon: IconCalendar1 },
74
95
  { name: "IconMagnifyingGlass", Icon: IconMagnifyingGlass },
75
96
  { name: "IconSettingsGear1", Icon: IconSettingsGear1 },
76
97
  { name: "IconBell", Icon: IconBell },
@@ -85,7 +106,13 @@ export const iconGallery = [
85
106
  { name: "IconChevronBottom", Icon: IconChevronBottom },
86
107
  { name: "IconChevronDownSmall", Icon: IconChevronDownSmall },
87
108
  { name: "IconChevronRightSmall", Icon: IconChevronRightSmall },
109
+ { name: "IconCircleCheck", Icon: IconCircleCheck },
110
+ { name: "IconCircleInfo", Icon: IconCircleInfo },
88
111
  { name: "IconClipboard", Icon: IconClipboard },
112
+ { name: "IconDotGrid1x3Horizontal", Icon: IconDotGrid1x3Horizontal },
89
113
  { name: "IconSquareBehindSquare6", Icon: IconSquareBehindSquare6 },
90
114
  { name: "IconChainLink1", Icon: IconChainLink1 },
115
+ { name: "IconExclamationCircle", Icon: IconExclamationCircle },
116
+ { name: "IconExclamationTriangle", Icon: IconExclamationTriangle },
117
+ { name: "IconStar", Icon: IconStar },
91
118
  ] as const