@boyernick/standard-ui-react 0.1.1-canary.9 → 0.2.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 (82) hide show
  1. package/package.json +5 -3
  2. package/src/accordion.tsx +5 -2
  3. package/src/alert-dialog.tsx +1 -1
  4. package/src/attachment.tsx +5 -2
  5. package/src/autocomplete.tsx +4 -3
  6. package/src/block-editor.tsx +1747 -0
  7. package/src/brand.tsx +2 -2
  8. package/src/breadcrumb.tsx +14 -7
  9. package/src/button.tsx +22 -11
  10. package/src/calendar.tsx +6 -3
  11. package/src/carousel.tsx +220 -53
  12. package/src/checkbox.tsx +3 -2
  13. package/src/code-block.tsx +66 -5
  14. package/src/collapsible.tsx +4 -2
  15. package/src/combobox.tsx +4 -3
  16. package/src/command.tsx +27 -12
  17. package/src/dialog.tsx +33 -10
  18. package/src/empty.tsx +4 -4
  19. package/src/field.tsx +4 -3
  20. package/src/filter-group.tsx +143 -0
  21. package/src/icons.tsx +28 -1
  22. package/src/illustrations.tsx +219 -141
  23. package/src/index.ts +238 -18
  24. package/src/input.tsx +8 -5
  25. package/src/kbd.tsx +10 -4
  26. package/src/lib/focus.ts +41 -0
  27. package/src/lib/popup.ts +1 -1
  28. package/src/lifeline/company-icon.tsx +71 -0
  29. package/src/lifeline/index.ts +42 -0
  30. package/src/lifeline/lifeline-data.ts +97 -0
  31. package/src/lifeline/lifeline-desktop.tsx +204 -0
  32. package/src/lifeline/lifeline-event.tsx +108 -0
  33. package/src/lifeline/lifeline-fireworks.tsx +302 -0
  34. package/src/lifeline/lifeline-hover-image.tsx +269 -0
  35. package/src/lifeline/lifeline-icons.tsx +39 -0
  36. package/src/lifeline/lifeline-intro-timing.ts +105 -0
  37. package/src/lifeline/lifeline-labels.tsx +24 -0
  38. package/src/lifeline/lifeline-layout.ts +1 -0
  39. package/src/lifeline/lifeline-legend.tsx +31 -0
  40. package/src/lifeline/lifeline-lightbox.tsx +309 -0
  41. package/src/lifeline/lifeline-marker.tsx +183 -0
  42. package/src/lifeline/lifeline-people.tsx +99 -0
  43. package/src/lifeline/lifeline-photos.tsx +366 -0
  44. package/src/lifeline/lifeline-shell.tsx +135 -0
  45. package/src/lifeline/lifeline-utils.ts +83 -0
  46. package/src/lifeline/lifeline-vertical.tsx +482 -0
  47. package/src/lifeline/lifeline.tsx +69 -0
  48. package/src/lifeline/types.ts +112 -0
  49. package/src/lifeline/use-lifeline-intro.ts +130 -0
  50. package/src/lifeline/use-lifeline-scroll.ts +1011 -0
  51. package/src/lifeline/use-lifeline-vertical-scroll.ts +271 -0
  52. package/src/menubar.tsx +9 -2
  53. package/src/minimap.tsx +296 -0
  54. package/src/modal.tsx +608 -0
  55. package/src/navigation-menu.tsx +338 -67
  56. package/src/number-field.tsx +336 -61
  57. package/src/otp-field.tsx +4 -3
  58. package/src/pagination.tsx +262 -42
  59. package/src/password-protection.tsx +345 -0
  60. package/src/popover.tsx +1 -1
  61. package/src/progress.tsx +5 -0
  62. package/src/questionnaire.tsx +284 -0
  63. package/src/radio.tsx +4 -2
  64. package/src/scroll-area.tsx +4 -1
  65. package/src/select.tsx +5 -2
  66. package/src/sidebar.tsx +113 -28
  67. package/src/slider.tsx +66 -6
  68. package/src/sounds.tsx +6 -10
  69. package/src/spinner.tsx +105 -32
  70. package/src/switch.tsx +4 -1
  71. package/src/table.tsx +82 -15
  72. package/src/tabs.tsx +189 -38
  73. package/src/text-animate.tsx +71 -28
  74. package/src/textarea.tsx +6 -1
  75. package/src/timeline.tsx +378 -0
  76. package/src/toast.tsx +214 -35
  77. package/src/toggle.tsx +4 -2
  78. package/src/toolbar.tsx +12 -7
  79. package/src/tooltip.tsx +43 -3
  80. package/src/video-player.tsx +396 -127
  81. package/src/image-modal.tsx +0 -110
  82. package/src/markdown-editor.tsx +0 -302
@@ -1,110 +0,0 @@
1
- "use client"
2
-
3
- import type { ComponentProps, ImgHTMLAttributes } from "react"
4
- import { Button } from "./button"
5
- import {
6
- Dialog,
7
- DialogBackdrop,
8
- DialogClose,
9
- DialogPopup,
10
- DialogPortal,
11
- DialogTitle,
12
- DialogTrigger,
13
- } from "./dialog"
14
- import { cn } from "./lib/cn"
15
- import { motion } from "./lib/motion"
16
-
17
- export type ImageModalProps = ComponentProps<typeof Dialog>
18
- export type ImageModalTriggerProps = ComponentProps<typeof DialogTrigger>
19
- export type ImageModalContentProps = ComponentProps<typeof DialogPopup> & {
20
- src: string
21
- alt: string
22
- caption?: string
23
- imgProps?: Omit<ImgHTMLAttributes<HTMLImageElement>, "src" | "alt">
24
- }
25
-
26
- export const ImageModal = (props: ImageModalProps) => <Dialog {...props} />
27
-
28
- export const ImageModalTrigger = ({
29
- className,
30
- ...props
31
- }: ImageModalTriggerProps) => (
32
- <DialogTrigger
33
- className={cn(
34
- "cursor-pointer rounded-xl outline-none focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20",
35
- className,
36
- )}
37
- {...props}
38
- />
39
- )
40
-
41
- export const ImageModalContent = ({
42
- src,
43
- alt,
44
- caption,
45
- className,
46
- imgProps,
47
- children,
48
- ...props
49
- }: ImageModalContentProps) => (
50
- <DialogPortal>
51
- <DialogBackdrop className="bg-black/70 dark:bg-black/80" />
52
- <DialogPopup
53
- data-slot="image-modal-content"
54
- className={cn(
55
- "max-h-[90vh] w-full max-w-4xl gap-0 overflow-hidden border-border-primary bg-surface p-0 shadow-lg max-sm:max-w-[calc(100vw-1.5rem)]",
56
- motion.popupCenter,
57
- className,
58
- )}
59
- {...props}
60
- >
61
- <div className="relative">
62
- <DialogTitle className="sr-only">{alt}</DialogTitle>
63
- {/* eslint-disable-next-line @next/next/no-img-element */}
64
- <img
65
- src={src}
66
- alt={alt}
67
- className={cn(
68
- "max-h-[min(80vh,56rem)] w-full object-contain bg-background-secondary",
69
- imgProps?.className,
70
- )}
71
- {...imgProps}
72
- />
73
- <DialogClose
74
- render={
75
- <Button
76
- type="button"
77
- variant="secondary"
78
- size="sm"
79
- iconOnly
80
- rounded
81
- aria-label="Close"
82
- className="absolute top-3 right-3"
83
- />
84
- }
85
- >
86
- <CloseIcon />
87
- </DialogClose>
88
- </div>
89
- {caption || children ? (
90
- <div className="border-t border-border-primary px-4 py-3">
91
- {caption ? (
92
- <p className="text-sm text-fg-secondary">{caption}</p>
93
- ) : null}
94
- {children}
95
- </div>
96
- ) : null}
97
- </DialogPopup>
98
- </DialogPortal>
99
- )
100
-
101
- const CloseIcon = () => (
102
- <svg viewBox="0 0 24 24" fill="none" className="size-4" aria-hidden>
103
- <path
104
- d="M7 7l10 10M17 7 7 17"
105
- stroke="currentColor"
106
- strokeWidth="2"
107
- strokeLinecap="round"
108
- />
109
- </svg>
110
- )
@@ -1,302 +0,0 @@
1
- "use client"
2
-
3
- import {
4
- createContext,
5
- forwardRef,
6
- useContext,
7
- useRef,
8
- useState,
9
- type ChangeEvent,
10
- type ComponentPropsWithoutRef,
11
- type HTMLAttributes,
12
- type ReactNode,
13
- type Ref,
14
- type TextareaHTMLAttributes,
15
- } from "react"
16
- import { cn } from "./lib/cn"
17
- import { textareaVariants } from "./textarea"
18
- import { Toggle } from "./toggle"
19
- import { Toolbar } from "./toolbar"
20
-
21
- type MarkdownEditorContextValue = {
22
- value: string
23
- setValue: (value: string) => void
24
- inputRef: React.RefObject<HTMLTextAreaElement | null>
25
- selection: { start: number; end: number }
26
- setSelection: (selection: { start: number; end: number }) => void
27
- }
28
-
29
- const MarkdownEditorContext = createContext<MarkdownEditorContextValue | null>(
30
- null,
31
- )
32
-
33
- const useMarkdownEditor = () => {
34
- const context = useContext(MarkdownEditorContext)
35
-
36
- if (!context) {
37
- throw new Error(
38
- "MarkdownEditor parts must be rendered inside MarkdownEditor",
39
- )
40
- }
41
-
42
- return context
43
- }
44
-
45
- const assignRef = <Value,>(ref: Ref<Value> | undefined, value: Value) => {
46
- if (typeof ref === "function") {
47
- ref(value)
48
- return
49
- }
50
-
51
- if (ref) ref.current = value
52
- }
53
-
54
- export type MarkdownEditorProps = Omit<
55
- HTMLAttributes<HTMLDivElement>,
56
- "onChange"
57
- > & {
58
- value?: string
59
- defaultValue?: string
60
- onValueChange?: (value: string) => void
61
- }
62
-
63
- export const MarkdownEditor = ({
64
- value: controlledValue,
65
- defaultValue = "",
66
- onValueChange,
67
- className,
68
- children,
69
- ...props
70
- }: MarkdownEditorProps) => {
71
- const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue)
72
- const [selection, setSelection] = useState({ start: 0, end: 0 })
73
- const inputRef = useRef<HTMLTextAreaElement>(null)
74
- const value = controlledValue ?? uncontrolledValue
75
-
76
- const setValue = (nextValue: string) => {
77
- if (controlledValue === undefined) setUncontrolledValue(nextValue)
78
- onValueChange?.(nextValue)
79
- }
80
-
81
- return (
82
- <MarkdownEditorContext.Provider
83
- value={{
84
- value,
85
- setValue,
86
- inputRef,
87
- selection,
88
- setSelection,
89
- }}
90
- >
91
- <div
92
- className={cn(
93
- "overflow-hidden rounded-xl border border-border-primary bg-surface",
94
- className,
95
- )}
96
- {...props}
97
- >
98
- {children}
99
- </div>
100
- </MarkdownEditorContext.Provider>
101
- )
102
- }
103
-
104
- type Format = {
105
- label: string
106
- marker: "*" | "**" | "`"
107
- className?: string
108
- }
109
-
110
- const formats: Format[] = [
111
- { label: "Bold", marker: "**", className: "font-medium" },
112
- { label: "Italic", marker: "*", className: "italic" },
113
- { label: "Code", marker: "`", className: "font-mono" },
114
- ]
115
-
116
- export type MarkdownEditorToolbarProps = ComponentPropsWithoutRef<
117
- typeof Toolbar
118
- >
119
-
120
- export const MarkdownEditorToolbar = ({
121
- className,
122
- children,
123
- ...props
124
- }: MarkdownEditorToolbarProps) => {
125
- const { value, setValue, inputRef, selection, setSelection } =
126
- useMarkdownEditor()
127
-
128
- const isFormatActive = (marker: Format["marker"]) => {
129
- const { start, end } = selection
130
- return (
131
- value.slice(start - marker.length, start) === marker &&
132
- value.slice(end, end + marker.length) === marker
133
- )
134
- }
135
-
136
- const handleFormat = (marker: Format["marker"]) => {
137
- const input = inputRef.current
138
-
139
- if (!input) return
140
-
141
- const start = input.selectionStart
142
- const end = input.selectionEnd
143
- const selectedText = value.slice(start, end)
144
- const isWrapped =
145
- value.slice(start - marker.length, start) === marker &&
146
- value.slice(end, end + marker.length) === marker
147
-
148
- if (isWrapped) {
149
- const nextValue =
150
- value.slice(0, start - marker.length) +
151
- selectedText +
152
- value.slice(end + marker.length)
153
-
154
- setValue(nextValue)
155
- requestAnimationFrame(() => {
156
- input.focus()
157
- input.setSelectionRange(start - marker.length, end - marker.length)
158
- setSelection({
159
- start: start - marker.length,
160
- end: end - marker.length,
161
- })
162
- })
163
- return
164
- }
165
-
166
- const nextValue =
167
- value.slice(0, start) +
168
- marker +
169
- selectedText +
170
- marker +
171
- value.slice(end)
172
-
173
- setValue(nextValue)
174
- requestAnimationFrame(() => {
175
- const selectionStart = start + marker.length
176
- input.focus()
177
- input.setSelectionRange(selectionStart, selectionStart + selectedText.length)
178
- setSelection({
179
- start: selectionStart,
180
- end: selectionStart + selectedText.length,
181
- })
182
- })
183
- }
184
-
185
- return (
186
- <Toolbar
187
- aria-label="Text formatting"
188
- className={cn(
189
- "flex w-full rounded-none border-0 border-b border-border-primary px-2 py-1",
190
- className,
191
- )}
192
- {...props}
193
- >
194
- {children ??
195
- formats.map((format) => (
196
- <Toggle
197
- key={format.label}
198
- aria-label={format.label}
199
- pressed={isFormatActive(format.marker)}
200
- onPressedChange={() => handleFormat(format.marker)}
201
- className={format.className}
202
- >
203
- {format.label}
204
- </Toggle>
205
- ))}
206
- </Toolbar>
207
- )
208
- }
209
-
210
- export type MarkdownEditorInputProps = TextareaHTMLAttributes<HTMLTextAreaElement>
211
-
212
- export const MarkdownEditorInput = forwardRef<
213
- HTMLTextAreaElement,
214
- MarkdownEditorInputProps
215
- >(({ className, onChange, onSelect, ...props }, forwardedRef) => {
216
- const { value, setValue, inputRef, setSelection } = useMarkdownEditor()
217
-
218
- const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
219
- setValue(event.target.value)
220
- onChange?.(event)
221
- }
222
-
223
- return (
224
- <textarea
225
- ref={(node) => {
226
- inputRef.current = node
227
- assignRef(forwardedRef, node)
228
- }}
229
- value={value}
230
- className={cn(
231
- textareaVariants({ variant: "ghost" }),
232
- "min-h-56 resize-y rounded-none border-0 p-4 font-mono text-sm focus-visible:ring-0",
233
- className,
234
- )}
235
- onChange={handleChange}
236
- onSelect={(event) => {
237
- setSelection({
238
- start: event.currentTarget.selectionStart,
239
- end: event.currentTarget.selectionEnd,
240
- })
241
- onSelect?.(event)
242
- }}
243
- {...props}
244
- />
245
- )
246
- })
247
-
248
- MarkdownEditorInput.displayName = "MarkdownEditorInput"
249
-
250
- const renderBasicMarkdown = (value: string): ReactNode[] =>
251
- value
252
- .split(/(\*\*[^*\n]+\*\*|`[^`\n]+`|\*[^*\n]+\*)/g)
253
- .map((part, index) => {
254
- if (part.startsWith("**") && part.endsWith("**")) {
255
- return <strong key={index}>{part.slice(2, -2)}</strong>
256
- }
257
-
258
- if (part.startsWith("`") && part.endsWith("`")) {
259
- return (
260
- <code
261
- key={index}
262
- className="rounded-xs bg-background-tertiary px-1 py-0.5 font-mono text-sm"
263
- >
264
- {part.slice(1, -1)}
265
- </code>
266
- )
267
- }
268
-
269
- if (part.startsWith("*") && part.endsWith("*")) {
270
- return <em key={index}>{part.slice(1, -1)}</em>
271
- }
272
-
273
- return part
274
- })
275
-
276
- export type MarkdownEditorPreviewProps = HTMLAttributes<HTMLDivElement> & {
277
- emptyText?: string
278
- }
279
-
280
- export const MarkdownEditorPreview = ({
281
- className,
282
- emptyText = "Nothing to preview",
283
- ...props
284
- }: MarkdownEditorPreviewProps) => {
285
- const { value } = useMarkdownEditor()
286
-
287
- return (
288
- <div
289
- className={cn(
290
- "min-h-56 whitespace-pre-wrap border-t border-border-primary p-4 text-sm text-fg-primary",
291
- className,
292
- )}
293
- {...props}
294
- >
295
- {value ? (
296
- renderBasicMarkdown(value)
297
- ) : (
298
- <span className="text-fg-quaternary">{emptyText}</span>
299
- )}
300
- </div>
301
- )
302
- }