@maestro-js/components 1.0.0-alpha.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 (89) hide show
  1. package/commands/add.ts +41 -0
  2. package/commands/index.ts +7 -0
  3. package/commands/list.ts +9 -0
  4. package/dist/components.json +1 -0
  5. package/dist/index.d.ts +14 -0
  6. package/dist/index.js +48 -0
  7. package/package.json +49 -0
  8. package/registry.json +1445 -0
  9. package/scripts/build.ts +44 -0
  10. package/src/components/alert-dialog.tsx +150 -0
  11. package/src/components/autocomplete.tsx +288 -0
  12. package/src/components/autosuggest.tsx +314 -0
  13. package/src/components/avatar.tsx +54 -0
  14. package/src/components/boolean-select.tsx +48 -0
  15. package/src/components/button-group.tsx +75 -0
  16. package/src/components/button-link.tsx +71 -0
  17. package/src/components/button.tsx +132 -0
  18. package/src/components/checkbox-group.tsx +172 -0
  19. package/src/components/checkbox.tsx +207 -0
  20. package/src/components/chip.tsx +158 -0
  21. package/src/components/container.tsx +82 -0
  22. package/src/components/currency-field.tsx +183 -0
  23. package/src/components/date-input.tsx +189 -0
  24. package/src/components/date-picker.tsx +211 -0
  25. package/src/components/date-range-picker.tsx +290 -0
  26. package/src/components/date-time-picker.tsx +196 -0
  27. package/src/components/dialog.tsx +97 -0
  28. package/src/components/disclosure.tsx +114 -0
  29. package/src/components/drawer.tsx +78 -0
  30. package/src/components/enum-chip.tsx +30 -0
  31. package/src/components/file-input.tsx +245 -0
  32. package/src/components/form.tsx +82 -0
  33. package/src/components/headless-file-input.tsx +362 -0
  34. package/src/components/helpers/animated-popover.tsx +24 -0
  35. package/src/components/helpers/button-context.ts +10 -0
  36. package/src/components/helpers/calendar-month-year-picker.tsx +280 -0
  37. package/src/components/helpers/form-field.tsx +229 -0
  38. package/src/components/helpers/get-button-classes.ts +138 -0
  39. package/src/components/helpers/headless-button.tsx +36 -0
  40. package/src/components/helpers/pdf-dist.client.ts +6 -0
  41. package/src/components/icon.tsx +26 -0
  42. package/src/components/image-input.tsx +265 -0
  43. package/src/components/img.tsx +46 -0
  44. package/src/components/inline-alert.tsx +54 -0
  45. package/src/components/labeled-value.tsx +480 -0
  46. package/src/components/link-tabs.tsx +118 -0
  47. package/src/components/menu.tsx +152 -0
  48. package/src/components/month-day-input.tsx +176 -0
  49. package/src/components/multi-file-input.tsx +244 -0
  50. package/src/components/multi-image-input.tsx +389 -0
  51. package/src/components/multicomplete.tsx +322 -0
  52. package/src/components/multiselect.tsx +325 -0
  53. package/src/components/multisuggest.tsx +357 -0
  54. package/src/components/number-field.tsx +143 -0
  55. package/src/components/numeric-tag-field.tsx +271 -0
  56. package/src/components/pdf-input.tsx +249 -0
  57. package/src/components/pdf.tsx +86 -0
  58. package/src/components/percentage-field.tsx +187 -0
  59. package/src/components/phone-number-field.tsx +166 -0
  60. package/src/components/radio-group.tsx +112 -0
  61. package/src/components/radio.tsx +91 -0
  62. package/src/components/select.tsx +215 -0
  63. package/src/components/spinner.tsx +16 -0
  64. package/src/components/stepper.tsx +181 -0
  65. package/src/components/switch.tsx +186 -0
  66. package/src/components/tabs.tsx +151 -0
  67. package/src/components/tag-field.tsx +250 -0
  68. package/src/components/text-area.tsx +148 -0
  69. package/src/components/text-field.tsx +144 -0
  70. package/src/components/time-input.tsx +198 -0
  71. package/src/components/toast.tsx +176 -0
  72. package/src/components/toggle-button-group.tsx +94 -0
  73. package/src/components/year-month-input.tsx +187 -0
  74. package/src/utils/colors.ts +44 -0
  75. package/src/utils/compose-refs.ts +32 -0
  76. package/src/utils/enum-colors.ts +1 -0
  77. package/src/utils/file-input.ts +49 -0
  78. package/src/utils/icons.d.ts +20 -0
  79. package/src/utils/tw.ts +13 -0
  80. package/src/utils/use-element-size.ts +35 -0
  81. package/src/utils/use-number-input.ts +143 -0
  82. package/src/utils/use-pagination.ts +38 -0
  83. package/src/utils/use-prevent-default.ts +27 -0
  84. package/src/utils/use-render-props.ts +39 -0
  85. package/src/utils/use-spin-delay.ts +24 -0
  86. package/src/utils/use-stable-accessor.ts +11 -0
  87. package/src/utils/use-tab-indicator.ts +106 -0
  88. package/tests/commands.test.ts +81 -0
  89. package/tsconfig.json +27 -0
@@ -0,0 +1,229 @@
1
+ import * as React from 'react'
2
+ import {
3
+ Button,
4
+ Collection,
5
+ Group,
6
+ Header,
7
+ Input,
8
+ ListBox,
9
+ ListBoxItem,
10
+ ListBoxSection,
11
+ type InputProps
12
+ } from 'react-aria-components'
13
+ import { composeTailwindRenderProps, tw } from '../../utils/tw'
14
+ import { Icon } from '../icon'
15
+
16
+ const colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const
17
+
18
+ type LabelProps = {
19
+ as?: React.ElementType<React.ComponentPropsWithRef<'label'>>
20
+ } & React.ComponentPropsWithRef<'label'>
21
+
22
+ const Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {
23
+ return React.createElement(as, {
24
+ ...props,
25
+ ref,
26
+ className: tw('block text-sm font-medium text-neutral-700', props.className)
27
+ })
28
+ })
29
+ Label.displayName = 'Label'
30
+
31
+ function FormFieldHelpText(props: {
32
+ id: string
33
+ isInvalid: boolean
34
+ validationMessage: React.ReactNode
35
+ hasWarning: boolean
36
+ warningMessage?: React.ReactNode
37
+ helpText?: React.ReactNode
38
+ UNSAFE_className?: string
39
+ }) {
40
+ const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText
41
+ const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'
42
+ return text === null ? null : (
43
+ <div
44
+ id={props.id}
45
+ className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}
46
+ >
47
+ {text ?? ''}
48
+ </div>
49
+ )
50
+ }
51
+
52
+ function LabelRow(
53
+ props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })
54
+ ): React.JSX.Element | null {
55
+ if (!props.label) return null
56
+ return (
57
+ <div className="flex justify-between items-center mb-1">
58
+ {'labelId' in props ? (
59
+ <Label id={props.labelId}>{props.label}</Label>
60
+ ) : (
61
+ <Label htmlFor={props.htmlFor}>{props.label}</Label>
62
+ )}
63
+ {props.cornerHint && <div className="text-sm text-neutral-500">{props.cornerHint}</div>}
64
+ </div>
65
+ )
66
+ }
67
+
68
+ type FieldGroupProps = {
69
+ isDisabled?: boolean
70
+ isReadonly?: boolean
71
+ isInvalid?: boolean
72
+ hasWarning?: boolean
73
+ shape?: 'rectangle' | 'oval'
74
+ className?: string
75
+ children: React.ReactNode
76
+ onClick?: React.MouseEventHandler<HTMLDivElement>
77
+ }
78
+
79
+ const FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(
80
+ ({ children, className, onClick, ...opts }, ref) => {
81
+ const disabled = opts.isDisabled || opts.isReadonly
82
+ const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'
83
+
84
+ return (
85
+ <Group
86
+ ref={ref}
87
+ className={tw(
88
+ 'relative overflow-hidden border transition-all duration-200 max-w-96',
89
+ 'focus-within:ring-1',
90
+ shape,
91
+ disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',
92
+ opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',
93
+ opts.hasWarning &&
94
+ 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',
95
+ !opts.isInvalid &&
96
+ !opts.hasWarning &&
97
+ !disabled &&
98
+ 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',
99
+ className
100
+ )}
101
+ onClick={onClick}
102
+ >
103
+ {children}
104
+ </Group>
105
+ )
106
+ }
107
+ )
108
+
109
+ const FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {
110
+ return (
111
+ <Input
112
+ {...props}
113
+ ref={ref}
114
+ className={composeTailwindRenderProps(
115
+ props.className,
116
+ tw(
117
+ 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',
118
+ (props.disabled || props.readOnly) && 'cursor-not-allowed'
119
+ )
120
+ )}
121
+ />
122
+ )
123
+ })
124
+
125
+ function DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {
126
+ return (
127
+ <ListBox<T>
128
+ {...props}
129
+ className={composeTailwindRenderProps(
130
+ props.className,
131
+ 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'
132
+ )}
133
+ />
134
+ )
135
+ }
136
+
137
+ function DropdownItem(props: {
138
+ id: string
139
+ textValue: string
140
+ label: React.ReactNode
141
+ secondary?: React.ReactNode
142
+ isDisabled?: boolean
143
+ color?: string | null
144
+ showCheckIcon?: boolean
145
+ customValuePrefix?: string
146
+ className?: string
147
+ }) {
148
+ const isCustom = Boolean(props.customValuePrefix)
149
+ const showCheck = props.showCheckIcon !== false && !isCustom
150
+
151
+ return (
152
+ <ListBoxItem
153
+ id={props.id}
154
+ textValue={props.textValue}
155
+ isDisabled={props.isDisabled}
156
+ className={({ isFocused, isSelected }) =>
157
+ tw(
158
+ isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',
159
+ 'relative cursor-pointer text-sm transition-colors duration-150',
160
+ 'pr-2 pl-3 py-1.5 outline-none',
161
+ props.isDisabled && 'opacity-50 cursor-not-allowed',
162
+ isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',
163
+ isCustom && 'italic',
164
+ props.className
165
+ )
166
+ }
167
+ >
168
+ {({ isSelected }) => (
169
+ <>
170
+ {props.color && !isCustom ? (
171
+ <div className="absolute left-0 top-0 w-[3px] h-full" style={{ backgroundColor: props.color }} />
172
+ ) : null}
173
+ <div className="flex items-center justify-between w-full">
174
+ <div className="flex-1 min-w-0">
175
+ <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>
176
+ {isCustom ? `${props.customValuePrefix} "${props.label}"` : props.label}
177
+ </div>
178
+ {props.secondary && !isCustom && (
179
+ <div className="text-xs mt-px truncate text-neutral-500">{props.secondary}</div>
180
+ )}
181
+ </div>
182
+ {showCheck && isSelected && (
183
+ <div className="ml-2 w-fit shrink-0 flex items-center text-primary-500">
184
+ <Icon name="check" className="h-4 w-4" />
185
+ </div>
186
+ )}
187
+ </div>
188
+ </>
189
+ )}
190
+ </ListBoxItem>
191
+ )
192
+ }
193
+
194
+ function DropdownSection<T extends object>(props: {
195
+ title?: string
196
+ items?: Iterable<T>
197
+ children: React.ReactNode | ((item: T) => React.ReactNode)
198
+ }) {
199
+ return (
200
+ <ListBoxSection>
201
+ {props.title && (
202
+ <Header className="sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100">
203
+ {props.title}
204
+ </Header>
205
+ )}
206
+ <Collection items={props.items}>{props.children}</Collection>
207
+ </ListBoxSection>
208
+ )
209
+ }
210
+
211
+ function ChevronButton(props?: { className?: string }) {
212
+ return (
213
+ <Button className={tw('flex items-center px-2 py-2', props?.className)}>
214
+ <Icon name="chevron-up-down" className="h-4 w-4 text-neutral-400 transition-colors" />
215
+ </Button>
216
+ )
217
+ }
218
+
219
+ export const FormFieldComponents = {
220
+ Label,
221
+ FormFieldHelpText,
222
+ LabelRow,
223
+ FieldGroup,
224
+ FieldInput,
225
+ DropdownListBox,
226
+ DropdownSection,
227
+ DropdownItem,
228
+ ChevronButton
229
+ }
@@ -0,0 +1,138 @@
1
+ import { tw } from '../../utils/tw'
2
+
3
+ export type ButtonVariant = 'contained' | 'outlined' | 'soft' | 'plain'
4
+ export type ButtonColor = 'primary' | 'negative' | 'positive' | 'notice' | 'neutral'
5
+ export type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
6
+ export type ButtonShape = 'rectangle' | 'circle' | 'oval'
7
+
8
+ const base =
9
+ 'relative inline-flex items-center justify-center gap-2 font-medium whitespace-nowrap transition-all cursor-default select-none [&_svg]:pointer-events-none [&_svg:not([class*=size-])]:size-4 [&_svg]:shrink-0 data-[disabled]:cursor-not-allowed'
10
+
11
+ const sizeClasses: Record<ButtonSize, string> = {
12
+ xs: 'h-7 min-w-14 px-2.5 text-xs',
13
+ sm: 'h-8 min-w-16 px-3 text-sm',
14
+ md: 'h-9 min-w-20 px-4 text-sm',
15
+ lg: 'h-10 min-w-24 px-5 text-base',
16
+ xl: 'h-12 min-w-28 px-6 text-base'
17
+ }
18
+
19
+ const shapeClasses: Record<ButtonShape, string> = {
20
+ rectangle: 'rounded',
21
+ circle: 'rounded-full aspect-square px-0',
22
+ oval: 'rounded-full'
23
+ }
24
+
25
+ const variantColorClasses: Record<ButtonVariant, Record<ButtonColor, string>> = {
26
+ contained: {
27
+ primary:
28
+ 'bg-primary-600 text-white ring-1 ring-inset ring-primary-700 data-[hovered]:bg-primary-700 data-[hovered]:ring-primary-800 data-[pressed]:bg-primary-800 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none',
29
+ negative:
30
+ 'bg-negative-600 text-white ring-1 ring-inset ring-negative-700 data-[hovered]:bg-negative-700 data-[hovered]:ring-negative-800 data-[pressed]:bg-negative-800 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none',
31
+ positive:
32
+ 'bg-positive-600 text-white ring-1 ring-inset ring-positive-700 data-[hovered]:bg-positive-700 data-[hovered]:ring-positive-800 data-[pressed]:bg-positive-800 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none',
33
+ notice:
34
+ 'bg-notice-600 text-white ring-1 ring-inset ring-notice-700 data-[hovered]:bg-notice-700 data-[hovered]:ring-notice-800 data-[pressed]:bg-notice-800 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none',
35
+ neutral:
36
+ 'bg-neutral-800 text-white ring-1 ring-inset ring-neutral-900 data-[hovered]:bg-neutral-900 data-[pressed]:bg-neutral-950 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none'
37
+ },
38
+ outlined: {
39
+ primary:
40
+ 'bg-white ring-1 ring-inset ring-primary-300 text-primary-700 data-[hovered]:bg-primary-50 data-[hovered]:ring-primary-400 data-[pressed]:bg-primary-100 data-[pressed]:ring-primary-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none',
41
+ negative:
42
+ 'bg-white ring-1 ring-inset ring-negative-300 text-negative-700 data-[hovered]:bg-negative-50 data-[hovered]:ring-negative-400 data-[pressed]:bg-negative-100 data-[pressed]:ring-negative-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none',
43
+ positive:
44
+ 'bg-white ring-1 ring-inset ring-positive-300 text-positive-700 data-[hovered]:bg-positive-50 data-[hovered]:ring-positive-400 data-[pressed]:bg-positive-100 data-[pressed]:ring-positive-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none',
45
+ notice:
46
+ 'bg-white ring-1 ring-inset ring-notice-300 text-notice-700 data-[hovered]:bg-notice-50 data-[hovered]:ring-notice-400 data-[pressed]:bg-notice-100 data-[pressed]:ring-notice-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none',
47
+ neutral:
48
+ 'bg-white ring-1 ring-inset ring-neutral-300 text-neutral-700 data-[hovered]:bg-neutral-50 data-[hovered]:ring-neutral-400 data-[pressed]:bg-neutral-100 data-[pressed]:ring-neutral-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none'
49
+ },
50
+ soft: {
51
+ primary:
52
+ 'bg-primary-100 text-primary-800 data-[hovered]:bg-primary-200 data-[pressed]:bg-primary-300 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400',
53
+ negative:
54
+ 'bg-negative-100 text-negative-800 data-[hovered]:bg-negative-200 data-[pressed]:bg-negative-300 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400',
55
+ positive:
56
+ 'bg-positive-100 text-positive-800 data-[hovered]:bg-positive-200 data-[pressed]:bg-positive-300 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400',
57
+ notice:
58
+ 'bg-notice-100 text-notice-800 data-[hovered]:bg-notice-200 data-[pressed]:bg-notice-300 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400',
59
+ neutral:
60
+ 'bg-neutral-200 text-neutral-800 data-[hovered]:bg-neutral-300 data-[pressed]:bg-neutral-400 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400'
61
+ },
62
+ plain: {
63
+ primary:
64
+ 'bg-transparent text-primary-700 data-[hovered]:bg-primary-50 data-[pressed]:bg-primary-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400',
65
+ negative:
66
+ 'bg-transparent text-negative-700 data-[hovered]:bg-negative-50 data-[pressed]:bg-negative-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400',
67
+ positive:
68
+ 'bg-transparent text-positive-700 data-[hovered]:bg-positive-50 data-[pressed]:bg-positive-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400',
69
+ notice:
70
+ 'bg-transparent text-notice-700 data-[hovered]:bg-notice-50 data-[pressed]:bg-notice-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400',
71
+ neutral:
72
+ 'bg-transparent text-neutral-700 data-[hovered]:bg-neutral-50 data-[pressed]:bg-neutral-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400'
73
+ }
74
+ }
75
+
76
+ const toggledVariantColorClasses: Record<ButtonVariant, Record<ButtonColor, string>> = {
77
+ contained: {
78
+ primary: 'bg-primary-800 ring-primary-900',
79
+ negative: 'bg-negative-800 ring-negative-900',
80
+ positive: 'bg-positive-800 ring-positive-900',
81
+ notice: 'bg-notice-800 ring-notice-900',
82
+ neutral: 'bg-neutral-950'
83
+ },
84
+ outlined: {
85
+ primary: 'bg-primary-100 ring-primary-500',
86
+ negative: 'bg-negative-100 ring-negative-500',
87
+ positive: 'bg-positive-100 ring-positive-500',
88
+ notice: 'bg-notice-100 ring-notice-500',
89
+ neutral: 'bg-neutral-100 ring-neutral-500'
90
+ },
91
+ soft: {
92
+ primary: 'bg-primary-200',
93
+ negative: 'bg-negative-200',
94
+ positive: 'bg-positive-200',
95
+ notice: 'bg-notice-200',
96
+ neutral: 'bg-neutral-300'
97
+ },
98
+ plain: {
99
+ primary: 'bg-primary-100',
100
+ negative: 'bg-negative-100',
101
+ positive: 'bg-positive-100',
102
+ notice: 'bg-notice-100',
103
+ neutral: 'bg-neutral-100'
104
+ }
105
+ }
106
+
107
+ const focusClasses =
108
+ 'data-[focus-visible]:outline-2 data-[focus-visible]:outline-offset-2 data-[focus-visible]:outline-primary-600'
109
+
110
+ export function getButtonClasses({
111
+ variant = 'contained',
112
+ color = 'primary',
113
+ size = 'md',
114
+ shape = 'oval',
115
+ showSpinner,
116
+ isToggled,
117
+ className
118
+ }: {
119
+ variant?: ButtonVariant
120
+ color?: ButtonColor
121
+ size?: ButtonSize
122
+ shape?: ButtonShape
123
+ showSpinner?: boolean
124
+ isToggled?: boolean
125
+ className?: string
126
+ } = {}) {
127
+ return tw(
128
+ base,
129
+ 'data-[pressed]:scale-[.98]',
130
+ showSpinner ? '[&>:not([data-loader])]:opacity-0 !text-transparent' : '',
131
+ sizeClasses[size],
132
+ shapeClasses[shape],
133
+ variantColorClasses[variant][color],
134
+ isToggled && toggledVariantColorClasses[variant][color],
135
+ focusClasses,
136
+ className
137
+ )
138
+ }
@@ -0,0 +1,36 @@
1
+ import { composeRefs } from '../../utils/compose-refs'
2
+ import { type WithRenderProps, useRenderProps } from '../../utils/use-render-props'
3
+
4
+ import React from 'react'
5
+ import { usePress } from '@react-aria/interactions'
6
+ import { mergeProps } from '@react-aria/utils'
7
+
8
+ export const HeadlessButton = React.forwardRef<
9
+ HTMLButtonElement,
10
+ WithRenderProps<React.ComponentProps<'button'>, { isPressed: boolean }>
11
+ >((rawProps, externalRef) => {
12
+ const myRef = React.useRef<HTMLButtonElement>(null)
13
+ const { isPressed, pressProps } = usePress({ ref: myRef, isDisabled: rawProps.disabled })
14
+ const renderProps = React.useMemo(
15
+ () => ({
16
+ isPressed
17
+ }),
18
+ [isPressed]
19
+ )
20
+
21
+ const props = useRenderProps(mergeProps(pressProps, rawProps), renderProps, ['children', 'style', 'className'])
22
+ const ref = React.useMemo(() => composeRefs(myRef, externalRef), [myRef, externalRef])
23
+
24
+ return (
25
+ <button
26
+ type="button"
27
+ {...props}
28
+ {...(props.disabled || props['data-disabled'] ? { ['data-disabled']: true } : {})}
29
+ {...((isPressed && !props.disabled) || props['data-pressed'] ? { ['data-pressed']: true } : {})}
30
+ aria-pressed={isPressed && !props.disabled}
31
+ ref={ref}
32
+ />
33
+ )
34
+ })
35
+
36
+ HeadlessButton.displayName = 'HeadlessButton'
@@ -0,0 +1,6 @@
1
+ // @ts-ignore
2
+ import pdfjs from '@bundled-es-modules/pdfjs-dist'
3
+
4
+ pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`
5
+
6
+ export default pdfjs
@@ -0,0 +1,26 @@
1
+ import * as React from 'react'
2
+ // @ts-ignore
3
+ import href from '../icons/sprite.svg'
4
+ import type { IconName } from '~/icons/types'
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Props
8
+ // ---------------------------------------------------------------------------
9
+
10
+ export interface IconProps extends React.ComponentProps<'svg'> {
11
+ name: IconName
12
+ }
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Component
16
+ // ---------------------------------------------------------------------------
17
+
18
+ export const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {
19
+ return (
20
+ <svg ref={ref} viewBox="0 0 24 24" className={className} {...props}>
21
+ <use href={`${href}#${name}`} />
22
+ </svg>
23
+ )
24
+ })
25
+
26
+ Icon.displayName = 'Icon'