@xjur-ui/react 1.0.3 → 1.0.4
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.
- package/.turbo/turbo-build.log +20 -14
- package/CHANGELOG.md +13 -0
- package/dist/components/date-picker.js +1 -1
- package/dist/components/divider.js +2 -1
- package/dist/components/dropdown-menu.js +3 -2
- package/dist/components/icon.d.ts +1 -1
- package/dist/components/icon.js +5 -1
- package/dist/components/input.js +1 -1
- package/dist/components/otp-input.js +1 -1
- package/dist/components/rich-editor.d.ts +25 -0
- package/dist/components/rich-editor.js +723 -0
- package/dist/components/search-input.js +1 -1
- package/dist/components/select.js +2 -2
- package/dist/components/skeleton.d.ts +6 -0
- package/dist/components/skeleton.js +16 -0
- package/dist/components/tooltip.d.ts +11 -0
- package/dist/components/tooltip.js +58 -0
- package/dist/index.css +247 -25
- package/package.json +12 -2
- package/src/components/date-picker.tsx +1 -1
- package/src/components/divider.tsx +2 -1
- package/src/components/dropdown-menu.tsx +1 -1
- package/src/components/input.tsx +1 -1
- package/src/components/otp-input.tsx +1 -1
- package/src/components/rich-editor.tsx +371 -0
- package/src/components/search-input.tsx +1 -2
- package/src/components/select.tsx +1 -1
- package/src/components/skeleton.tsx +12 -0
- package/src/components/tooltip.tsx +63 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-os.ts +32 -0
- package/src/resources/icons.ts +5 -1
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { cx } from '@xjur-ui/util'
|
|
2
|
+
import { useCallback, useMemo, useState, type ComponentProps } from 'react'
|
|
3
|
+
import {
|
|
4
|
+
EditorContent,
|
|
5
|
+
EditorContext,
|
|
6
|
+
useCurrentEditor as useRichTextEditor,
|
|
7
|
+
useEditorState as useRichTextEditorState,
|
|
8
|
+
useEditor,
|
|
9
|
+
type EditorOptions,
|
|
10
|
+
Extension
|
|
11
|
+
} from '@tiptap/react'
|
|
12
|
+
import { StarterKit } from '@tiptap/starter-kit'
|
|
13
|
+
import { Heading, type Level } from '@tiptap/extension-heading'
|
|
14
|
+
import { Placeholder } from '@tiptap/extensions'
|
|
15
|
+
import { useOS } from '../hooks'
|
|
16
|
+
import { Icon, type IconType } from './icon'
|
|
17
|
+
import { Button, type ButtonProps } from './button'
|
|
18
|
+
import {
|
|
19
|
+
DropdownMenuContent,
|
|
20
|
+
DropdownMenuItem,
|
|
21
|
+
DropdownMenuItemText,
|
|
22
|
+
DropdownMenuList,
|
|
23
|
+
DropdownMenuRoot,
|
|
24
|
+
DropdownMenuTrigger
|
|
25
|
+
} from './dropdown-menu'
|
|
26
|
+
import { Tooltip, TooltipTrigger, TooltipContent } from './tooltip'
|
|
27
|
+
import { Typography } from './typography'
|
|
28
|
+
|
|
29
|
+
const headingClasses: Partial<Record<Level, string>> = {
|
|
30
|
+
1: 'text-size-body-md',
|
|
31
|
+
2: 'text-size-title-lg',
|
|
32
|
+
3: 'text-size-headline-lg'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function RichEditorRoot({
|
|
36
|
+
className,
|
|
37
|
+
editorOptions,
|
|
38
|
+
...props
|
|
39
|
+
}: ComponentProps<'div'> & {
|
|
40
|
+
editorOptions?: Partial<
|
|
41
|
+
Omit<EditorOptions, 'extensions'> & {
|
|
42
|
+
extensions?: Extension[]
|
|
43
|
+
}
|
|
44
|
+
>
|
|
45
|
+
}) {
|
|
46
|
+
const editor = useEditor({
|
|
47
|
+
...editorOptions,
|
|
48
|
+
extensions: [
|
|
49
|
+
StarterKit.configure({
|
|
50
|
+
bulletList: {
|
|
51
|
+
HTMLAttributes: {
|
|
52
|
+
class: 'list-disc list-outside pl-large-1x'
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
orderedList: {
|
|
56
|
+
HTMLAttributes: {
|
|
57
|
+
class: 'list-decimal list-outside pl-large-1x'
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
heading: false
|
|
61
|
+
}),
|
|
62
|
+
Placeholder.configure({
|
|
63
|
+
placeholder: ({ node, editor }) =>
|
|
64
|
+
node.type.name === 'paragraph' && editor.isEmpty
|
|
65
|
+
? 'Digite aqui…'
|
|
66
|
+
: '',
|
|
67
|
+
showOnlyWhenEditable: true,
|
|
68
|
+
includeChildren: false,
|
|
69
|
+
emptyNodeClass:
|
|
70
|
+
'before:content-[attr(data-placeholder)] before:absolute before:top-0 before:left-0 before:text-neutral-placeholder first:before:float-left first:before:content-[attr(data-placeholder)] first:before:pointer-events-none'
|
|
71
|
+
}),
|
|
72
|
+
Heading.configure({
|
|
73
|
+
levels: [1, 2, 3]
|
|
74
|
+
}).extend({
|
|
75
|
+
renderHTML({ node, HTMLAttributes }) {
|
|
76
|
+
const level: Level = node.attrs.level ?? 1
|
|
77
|
+
|
|
78
|
+
return [
|
|
79
|
+
'h',
|
|
80
|
+
{
|
|
81
|
+
...HTMLAttributes,
|
|
82
|
+
class: headingClasses[level]
|
|
83
|
+
},
|
|
84
|
+
0
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
}),
|
|
88
|
+
...(editorOptions?.extensions ?? [])
|
|
89
|
+
]
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
const value = useMemo(() => ({ editor }), [editor])
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<EditorContext.Provider value={value}>
|
|
96
|
+
<div
|
|
97
|
+
className={cx('gap-small flex w-full flex-col', className)}
|
|
98
|
+
data-slot="text-area-root"
|
|
99
|
+
{...props}
|
|
100
|
+
/>
|
|
101
|
+
</EditorContext.Provider>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function RichEditorContent({
|
|
106
|
+
className,
|
|
107
|
+
...props
|
|
108
|
+
}: ComponentProps<'div'>) {
|
|
109
|
+
return (
|
|
110
|
+
<div
|
|
111
|
+
className={cx(
|
|
112
|
+
'bg-layer-2-hover py-small_1x-alt px-small rounded-small-1x min-h-[116px] w-full border border-transparent',
|
|
113
|
+
'hover:border-border-layer-2 transition-colors duration-200',
|
|
114
|
+
'[&_ul_ul]:list-[circle]',
|
|
115
|
+
'[&_ul_ul_ul]:list-[square]',
|
|
116
|
+
'[&_ol_ol]:list-[lower-alpha]',
|
|
117
|
+
'[&_ol_ol_ol]:list-[lower-roman]',
|
|
118
|
+
'gap-small flex flex-col overflow-x-hidden overflow-y-auto',
|
|
119
|
+
className
|
|
120
|
+
)}
|
|
121
|
+
data-slot="text-area-content"
|
|
122
|
+
{...props}
|
|
123
|
+
/>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function RichEditorCaption({
|
|
128
|
+
className,
|
|
129
|
+
...props
|
|
130
|
+
}: ComponentProps<'p'>) {
|
|
131
|
+
return (
|
|
132
|
+
<p
|
|
133
|
+
{...props}
|
|
134
|
+
className={cx(
|
|
135
|
+
'text-size-body-sm text-neutral-dark font-normal',
|
|
136
|
+
className
|
|
137
|
+
)}
|
|
138
|
+
data-slot="text-area-caption"
|
|
139
|
+
/>
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function RichEditorAreaText({
|
|
144
|
+
className,
|
|
145
|
+
...props
|
|
146
|
+
}: Omit<ComponentProps<typeof EditorContent>, 'editor'>) {
|
|
147
|
+
const { editor } = useRichTextEditor()
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<EditorContent
|
|
151
|
+
className={cx(
|
|
152
|
+
'text-size-body-md text-neutral-dark max-h-[116px] *:h-full *:w-full *:outline-0',
|
|
153
|
+
className
|
|
154
|
+
)}
|
|
155
|
+
editor={editor}
|
|
156
|
+
{...props}
|
|
157
|
+
/>
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function RichEditorToolbar({
|
|
162
|
+
className,
|
|
163
|
+
...props
|
|
164
|
+
}: ComponentProps<'div'>) {
|
|
165
|
+
return (
|
|
166
|
+
<div
|
|
167
|
+
className={cx(
|
|
168
|
+
'h-large-4x gap-small-1x h-large-6px scrollbar-hide flex flex-nowrap items-center overflow-x-auto overflow-y-hidden',
|
|
169
|
+
className
|
|
170
|
+
)}
|
|
171
|
+
data-slot="text-area-toolbar"
|
|
172
|
+
{...props}
|
|
173
|
+
/>
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function ToolbarButton({
|
|
178
|
+
appearance = 'ghost',
|
|
179
|
+
variant = 'secondary',
|
|
180
|
+
onMouseDown,
|
|
181
|
+
onPointerDown,
|
|
182
|
+
...props
|
|
183
|
+
}: ButtonProps) {
|
|
184
|
+
return (
|
|
185
|
+
<Button
|
|
186
|
+
appearance={appearance}
|
|
187
|
+
size="icon_md"
|
|
188
|
+
tabIndex={-1}
|
|
189
|
+
type="button"
|
|
190
|
+
variant={variant}
|
|
191
|
+
onMouseDown={e => {
|
|
192
|
+
e.preventDefault()
|
|
193
|
+
onMouseDown?.(e)
|
|
194
|
+
}}
|
|
195
|
+
onPointerDown={e => {
|
|
196
|
+
e.preventDefault()
|
|
197
|
+
onPointerDown?.(e)
|
|
198
|
+
}}
|
|
199
|
+
{...props}
|
|
200
|
+
/>
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
type ToggleButtonName =
|
|
205
|
+
| 'bold'
|
|
206
|
+
| 'italic'
|
|
207
|
+
| 'underline'
|
|
208
|
+
| 'strike'
|
|
209
|
+
| 'bulletList'
|
|
210
|
+
| 'orderedList'
|
|
211
|
+
| 'undo'
|
|
212
|
+
| 'redo'
|
|
213
|
+
|
|
214
|
+
export function ToggleButton({
|
|
215
|
+
name,
|
|
216
|
+
...props
|
|
217
|
+
}: ButtonProps & { name: ToggleButtonName }) {
|
|
218
|
+
const { editor } = useRichTextEditor()
|
|
219
|
+
const { shortcut } = useOS()
|
|
220
|
+
|
|
221
|
+
const isActive = useRichTextEditorState({
|
|
222
|
+
editor,
|
|
223
|
+
selector: ctx => ctx.editor?.isActive(name) ?? false
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
const icon = useMemo(() => {
|
|
227
|
+
const iconsMap = new Map<ToggleButtonName, IconType>([
|
|
228
|
+
['bold', 'format_bold'],
|
|
229
|
+
['italic', 'format_italic'],
|
|
230
|
+
['underline', 'format_underlined'],
|
|
231
|
+
['strike', 'format_strikethrough'],
|
|
232
|
+
['bulletList', 'format_list_bulleted'],
|
|
233
|
+
['orderedList', 'format_list_numbered'],
|
|
234
|
+
['undo', 'undo'],
|
|
235
|
+
['redo', 'redo']
|
|
236
|
+
])
|
|
237
|
+
|
|
238
|
+
return iconsMap.get(name)
|
|
239
|
+
}, [name])
|
|
240
|
+
|
|
241
|
+
const description = useMemo(() => {
|
|
242
|
+
const descriptionsMap = new Map<ToggleButtonName, string>([
|
|
243
|
+
['bold', `Negrito (${shortcut} + B)`],
|
|
244
|
+
['italic', `Itálico (${shortcut} + I)`],
|
|
245
|
+
['underline', `Sublinhado (${shortcut} + U)`],
|
|
246
|
+
['strike', `Tachado (${shortcut} + S)`],
|
|
247
|
+
['bulletList', `Lista (${shortcut} + Shift + 7)`],
|
|
248
|
+
['orderedList', `Lista numerada (${shortcut} + Shift + 8)`],
|
|
249
|
+
['undo', `Desfazer (${shortcut} + Z)`],
|
|
250
|
+
['redo', `Refazer (${shortcut} + Y)`]
|
|
251
|
+
])
|
|
252
|
+
|
|
253
|
+
return descriptionsMap.get(name)
|
|
254
|
+
}, [name, shortcut])
|
|
255
|
+
|
|
256
|
+
const handleToggle = useCallback(() => {
|
|
257
|
+
if (!editor) throw new Error('Editor not found')
|
|
258
|
+
|
|
259
|
+
const commandsMap = new Map<ToggleButtonName, () => void>([
|
|
260
|
+
['bold', () => editor.chain().focus().toggleBold().run()],
|
|
261
|
+
['italic', () => editor.chain().focus().toggleItalic().run()],
|
|
262
|
+
['underline', () => editor.chain().focus().toggleUnderline().run()],
|
|
263
|
+
['strike', () => editor.chain().focus().toggleStrike().run()],
|
|
264
|
+
['bulletList', () => editor.chain().focus().toggleBulletList().run()],
|
|
265
|
+
['orderedList', () => editor.chain().focus().toggleOrderedList().run()],
|
|
266
|
+
['undo', () => editor.chain().focus().undo().run()],
|
|
267
|
+
['redo', () => editor.chain().focus().redo().run()]
|
|
268
|
+
])
|
|
269
|
+
|
|
270
|
+
commandsMap.get(name)?.()
|
|
271
|
+
}, [editor, name])
|
|
272
|
+
|
|
273
|
+
return (
|
|
274
|
+
<Tooltip>
|
|
275
|
+
<TooltipTrigger asChild={true}>
|
|
276
|
+
<ToolbarButton
|
|
277
|
+
onClick={handleToggle}
|
|
278
|
+
{...(isActive && { appearance: 'solid' })}
|
|
279
|
+
{...props}
|
|
280
|
+
>
|
|
281
|
+
{icon && <Icon className="text-icon-neutral-3" name={icon} />}
|
|
282
|
+
</ToolbarButton>
|
|
283
|
+
</TooltipTrigger>
|
|
284
|
+
|
|
285
|
+
<TooltipContent>
|
|
286
|
+
{description && <Typography size="sm">{description}</Typography>}
|
|
287
|
+
</TooltipContent>
|
|
288
|
+
</Tooltip>
|
|
289
|
+
)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function HeadingButton({
|
|
293
|
+
appearance = 'ghost',
|
|
294
|
+
variant = 'secondary',
|
|
295
|
+
...props
|
|
296
|
+
}: ButtonProps) {
|
|
297
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
298
|
+
const { editor } = useRichTextEditor()
|
|
299
|
+
|
|
300
|
+
const isActive = useRichTextEditorState({
|
|
301
|
+
editor,
|
|
302
|
+
selector: ctx => ctx.editor?.isActive('heading') ?? false
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
const isLevelActive = useCallback(
|
|
306
|
+
(level: Level) => {
|
|
307
|
+
if (!editor) throw new Error('Editor not found')
|
|
308
|
+
return editor.isActive('heading', { level })
|
|
309
|
+
},
|
|
310
|
+
[editor]
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
const toggleHeading = useCallback(
|
|
314
|
+
(level: Level) => {
|
|
315
|
+
if (!editor) throw new Error('Editor not found')
|
|
316
|
+
setIsOpen(false)
|
|
317
|
+
setTimeout(() => {
|
|
318
|
+
editor.chain().focus().toggleHeading({ level }).run()
|
|
319
|
+
}, 0)
|
|
320
|
+
},
|
|
321
|
+
[editor]
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
const levels: { level: Level; label: string; className?: string }[] = [
|
|
325
|
+
{ level: 1, label: 'Normal', className: headingClasses[1] },
|
|
326
|
+
{ level: 2, label: 'Grande', className: headingClasses[2] },
|
|
327
|
+
{ level: 3, label: 'Enorme', className: headingClasses[3] }
|
|
328
|
+
]
|
|
329
|
+
|
|
330
|
+
return (
|
|
331
|
+
<DropdownMenuRoot open={isOpen} onOpenChange={open => setIsOpen(open)}>
|
|
332
|
+
<DropdownMenuTrigger asChild={true}>
|
|
333
|
+
<Button
|
|
334
|
+
appearance={isActive ? 'solid' : appearance}
|
|
335
|
+
size="icon_md"
|
|
336
|
+
tabIndex={-1}
|
|
337
|
+
type="button"
|
|
338
|
+
variant={variant}
|
|
339
|
+
{...props}
|
|
340
|
+
>
|
|
341
|
+
<Tooltip>
|
|
342
|
+
<TooltipTrigger asChild={true}>
|
|
343
|
+
<Icon className="text-icon-neutral-3" name="format_size" />
|
|
344
|
+
</TooltipTrigger>
|
|
345
|
+
|
|
346
|
+
<TooltipContent>
|
|
347
|
+
<Typography size="sm">Tamanho</Typography>
|
|
348
|
+
</TooltipContent>
|
|
349
|
+
</Tooltip>
|
|
350
|
+
</Button>
|
|
351
|
+
</DropdownMenuTrigger>
|
|
352
|
+
<DropdownMenuContent>
|
|
353
|
+
<DropdownMenuList>
|
|
354
|
+
{levels.map(level => (
|
|
355
|
+
<DropdownMenuItem
|
|
356
|
+
isActive={isLevelActive(level.level)}
|
|
357
|
+
key={level.level}
|
|
358
|
+
onPointerDown={() => toggleHeading(level.level)}
|
|
359
|
+
>
|
|
360
|
+
<DropdownMenuItemText className={level.className}>
|
|
361
|
+
{level.label}
|
|
362
|
+
</DropdownMenuItemText>
|
|
363
|
+
</DropdownMenuItem>
|
|
364
|
+
))}
|
|
365
|
+
</DropdownMenuList>
|
|
366
|
+
</DropdownMenuContent>
|
|
367
|
+
</DropdownMenuRoot>
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export { useRichTextEditorState, useRichTextEditor }
|
|
@@ -39,8 +39,7 @@ export function SearchInputContent({
|
|
|
39
39
|
{
|
|
40
40
|
variants: {
|
|
41
41
|
variant: {
|
|
42
|
-
outlined:
|
|
43
|
-
'rounded-circle focus-within:border-primary-idle focus-within:shadow-focus-primary',
|
|
42
|
+
outlined: 'rounded-circle focus-within:border-primary-idle ',
|
|
44
43
|
line: 'rounded-none border-t-0 border-b-1 border-x-0',
|
|
45
44
|
action: 'rounded-tr-none rounded-br-none border-r-0'
|
|
46
45
|
}
|
|
@@ -31,7 +31,7 @@ export function SelectTrigger({
|
|
|
31
31
|
className,
|
|
32
32
|
'group h-large-4x rounded-small-1x gap-small py-small_1x-alt px-small relative flex w-full cursor-pointer items-center',
|
|
33
33
|
'bg-layer-2-idle border-border-layer-2 hover:border-border-layer-3 border',
|
|
34
|
-
'data-[state=open]:border-primary-idle
|
|
34
|
+
'data-[state=open]:border-primary-idle',
|
|
35
35
|
'has-[input:not(:placeholder-shown)]:text-primary-idle',
|
|
36
36
|
'has-[input:not(:placeholder-shown)]:border-primary-idle'
|
|
37
37
|
)}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react'
|
|
2
|
+
import { cx } from '@xjur-ui/util'
|
|
3
|
+
|
|
4
|
+
export function Skeleton({ className, ...props }: ComponentProps<'div'>) {
|
|
5
|
+
return (
|
|
6
|
+
<div
|
|
7
|
+
className={cx('bg-layer-skeleton animate-pulse', className)}
|
|
8
|
+
data-slot="skeleton"
|
|
9
|
+
{...props}
|
|
10
|
+
/>
|
|
11
|
+
)
|
|
12
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as TooltipPrimitive from '@radix-ui/react-tooltip'
|
|
2
|
+
import { cx } from '@xjur-ui/util'
|
|
3
|
+
|
|
4
|
+
function TooltipProvider({
|
|
5
|
+
delayDuration = 0,
|
|
6
|
+
...props
|
|
7
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
|
8
|
+
return (
|
|
9
|
+
<TooltipPrimitive.Provider
|
|
10
|
+
data-slot="tooltip-provider"
|
|
11
|
+
delayDuration={delayDuration}
|
|
12
|
+
{...props}
|
|
13
|
+
/>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function Tooltip({
|
|
18
|
+
...props
|
|
19
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
|
20
|
+
return (
|
|
21
|
+
<TooltipProvider>
|
|
22
|
+
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
|
23
|
+
</TooltipProvider>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function TooltipTrigger({
|
|
28
|
+
...props
|
|
29
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
30
|
+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function TooltipContent({
|
|
34
|
+
className,
|
|
35
|
+
sideOffset = 1,
|
|
36
|
+
variant = 'simple',
|
|
37
|
+
children,
|
|
38
|
+
...props
|
|
39
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Content> & {
|
|
40
|
+
variant?: 'simple' | 'rich'
|
|
41
|
+
}) {
|
|
42
|
+
return (
|
|
43
|
+
<TooltipPrimitive.Portal>
|
|
44
|
+
<TooltipPrimitive.Content
|
|
45
|
+
className={cx(
|
|
46
|
+
'text-size-body-sm text-balance',
|
|
47
|
+
'data-[variant=simple]:bg-alt-2 data-[variant=simple]:text-neutral-label data-[variant=simple]:rounded-small-1x data-[variant=simple]:px-small data-[variant=simple]:py-small-1x',
|
|
48
|
+
'data-[variant=rich]:bg-layer-2-idle data-[variant=rich]:text-neutral-dark data-[variant=rich]:rounded-small data-[variant=rich]:border-border-layer-2 data-[variant=rich]:p-large data-[variant=rich]:border',
|
|
49
|
+
'animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin)',
|
|
50
|
+
className
|
|
51
|
+
)}
|
|
52
|
+
data-slot="tooltip-content"
|
|
53
|
+
data-variant={variant}
|
|
54
|
+
sideOffset={sideOffset}
|
|
55
|
+
{...props}
|
|
56
|
+
>
|
|
57
|
+
{children}
|
|
58
|
+
</TooltipPrimitive.Content>
|
|
59
|
+
</TooltipPrimitive.Portal>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useOS } from './use-os'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
export type OS = 'ios' | 'macos' | 'windows' | 'android' | 'linux' | 'unknown'
|
|
4
|
+
|
|
5
|
+
export function useOS() {
|
|
6
|
+
const [os, setOs] = useState<OS>('unknown')
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (typeof window === 'undefined') return
|
|
10
|
+
|
|
11
|
+
const userAgent = window.navigator.userAgent
|
|
12
|
+
|
|
13
|
+
if (/iPad|iPhone|iPod/.test(userAgent)) {
|
|
14
|
+
setOs('ios')
|
|
15
|
+
} else if (/Mac/.test(userAgent)) {
|
|
16
|
+
setOs('macos')
|
|
17
|
+
} else if (/Win/.test(userAgent)) {
|
|
18
|
+
setOs('windows')
|
|
19
|
+
} else if (/Android/.test(userAgent)) {
|
|
20
|
+
setOs('android')
|
|
21
|
+
} else if (/Linux/.test(userAgent)) {
|
|
22
|
+
setOs('linux')
|
|
23
|
+
} else {
|
|
24
|
+
setOs('unknown')
|
|
25
|
+
}
|
|
26
|
+
}, [])
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
os,
|
|
30
|
+
shortcut: ['macos', 'ios'].includes(os) ? '⌘' : 'Ctrl'
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/resources/icons.ts
CHANGED
|
@@ -140,7 +140,11 @@ export const icons = [
|
|
|
140
140
|
'history',
|
|
141
141
|
'security',
|
|
142
142
|
'bar_chart',
|
|
143
|
-
'category'
|
|
143
|
+
'category',
|
|
144
|
+
'format_underlined',
|
|
145
|
+
'format_list_bulleted',
|
|
146
|
+
'format_list_numbered',
|
|
147
|
+
'redo'
|
|
144
148
|
] as const
|
|
145
149
|
|
|
146
150
|
export type IconType = (typeof icons)[number]
|