@xjur-ui/react 1.0.2 → 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 +26 -16
- package/CHANGELOG.md +20 -0
- package/dist/components/avatar-group.d.ts +34 -0
- package/dist/components/avatar-group.js +179 -0
- package/dist/components/avatar.js +1 -0
- package/dist/components/chip.js +1 -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/table.d.ts +17 -0
- package/dist/components/table.js +196 -0
- package/dist/components/tooltip.d.ts +11 -0
- package/dist/components/tooltip.js +58 -0
- package/dist/components/typography.d.ts +1 -1
- package/dist/index.css +401 -23
- package/package.json +14 -2
- package/src/components/avatar-group.tsx +76 -0
- package/src/components/avatar.tsx +1 -0
- 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/table.tsx +160 -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,76 @@
|
|
|
1
|
+
import { cx } from '@xjur-ui/util'
|
|
2
|
+
import { AvatarRoot, AvatarImage, AvatarFallback } from './avatar'
|
|
3
|
+
import { Typography } from './typography'
|
|
4
|
+
|
|
5
|
+
export interface AvatarGroupProps {
|
|
6
|
+
/**
|
|
7
|
+
* Lista de avatares para exibir
|
|
8
|
+
*/
|
|
9
|
+
avatars: Array<{
|
|
10
|
+
src?: string
|
|
11
|
+
alt?: string
|
|
12
|
+
fallback?: string
|
|
13
|
+
}>
|
|
14
|
+
/**
|
|
15
|
+
* Número máximo de avatares a exibir antes de mostrar o indicador de overflow
|
|
16
|
+
* @default 3
|
|
17
|
+
*/
|
|
18
|
+
max?: number
|
|
19
|
+
/**
|
|
20
|
+
* Tamanho dos avatares
|
|
21
|
+
* @default 'md'
|
|
22
|
+
*/
|
|
23
|
+
size?: 'xs' | 'sm' | 'md' | 'lg'
|
|
24
|
+
/**
|
|
25
|
+
* Classe CSS adicional
|
|
26
|
+
*/
|
|
27
|
+
className?: string
|
|
28
|
+
/**
|
|
29
|
+
* Espaçamento entre os avatares (em pixels)
|
|
30
|
+
* @default -8
|
|
31
|
+
*/
|
|
32
|
+
spacing?: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function AvatarGroup({
|
|
36
|
+
avatars,
|
|
37
|
+
max = 3,
|
|
38
|
+
size = 'md',
|
|
39
|
+
className
|
|
40
|
+
}: AvatarGroupProps) {
|
|
41
|
+
const visibleAvatars = avatars.slice(0, max)
|
|
42
|
+
const remainingCount = avatars.length - max
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div
|
|
46
|
+
className={cx('-space-x-small-1x flex items-center', className)}
|
|
47
|
+
data-slot="avatar-group"
|
|
48
|
+
>
|
|
49
|
+
{visibleAvatars.map((avatar, index) => (
|
|
50
|
+
<AvatarRoot
|
|
51
|
+
className="border-icon-neutral-5 border-2"
|
|
52
|
+
key={index}
|
|
53
|
+
size={size}
|
|
54
|
+
>
|
|
55
|
+
{avatar.src && (
|
|
56
|
+
<AvatarImage
|
|
57
|
+
alt={avatar.alt || `Avatar ${index + 1}`}
|
|
58
|
+
src={avatar.src}
|
|
59
|
+
/>
|
|
60
|
+
)}
|
|
61
|
+
<AvatarFallback>{avatar.fallback || `A${index + 1}`}</AvatarFallback>
|
|
62
|
+
</AvatarRoot>
|
|
63
|
+
))}
|
|
64
|
+
|
|
65
|
+
{remainingCount > 0 && (
|
|
66
|
+
<AvatarRoot className="bg-layer-1-hover" size={size}>
|
|
67
|
+
<AvatarFallback className="bg-layer-1-hover font-medium">
|
|
68
|
+
<Typography className="text-neutral-dark" weight="medium">
|
|
69
|
+
+{remainingCount}
|
|
70
|
+
</Typography>
|
|
71
|
+
</AvatarFallback>
|
|
72
|
+
</AvatarRoot>
|
|
73
|
+
)}
|
|
74
|
+
</div>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
@@ -14,6 +14,7 @@ export function AvatarRoot({
|
|
|
14
14
|
className={cx(
|
|
15
15
|
'rounded-circle relative flex shrink-0 overflow-hidden',
|
|
16
16
|
'data-[size=lg]:size-large-8x data-[size=md]:size-large-4x data-[size=sm]:size-large-2x data-[size=xs]:size-large-1x',
|
|
17
|
+
'data-[size=lg]:text-size-headline-md data-[size=md]:text-size-label-lg data-[size=sm]:text-size-label-sm data-[size=xs]:text-size-label-sm',
|
|
17
18
|
className
|
|
18
19
|
)}
|
|
19
20
|
data-size={size}
|
|
@@ -19,7 +19,7 @@ export function DatePickerTrigger({
|
|
|
19
19
|
className,
|
|
20
20
|
'group h-large-4x rounded-small-1x gap-small py-small_1x-alt px-small relative flex w-full cursor-pointer items-center',
|
|
21
21
|
'bg-layer-2-idle border-border-layer-2 hover:border-border-layer-3 border',
|
|
22
|
-
'data-[state=open]:border-primary-idle
|
|
22
|
+
'data-[state=open]:border-primary-idle',
|
|
23
23
|
'has-[input:not(:placeholder-shown)]:text-primary-idle',
|
|
24
24
|
'has-[input:not(:placeholder-shown)]:border-primary-idle'
|
|
25
25
|
)}
|
|
@@ -11,7 +11,8 @@ export function Divider({
|
|
|
11
11
|
return (
|
|
12
12
|
<SeparatorPrimitive.Root
|
|
13
13
|
className={cx(
|
|
14
|
-
'bg-border-layer-2 shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full
|
|
14
|
+
'bg-border-layer-2 shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full',
|
|
15
|
+
'data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px',
|
|
15
16
|
className
|
|
16
17
|
)}
|
|
17
18
|
data-slot="separator"
|
|
@@ -49,7 +49,7 @@ export function DropdownMenuContent({
|
|
|
49
49
|
<DropdownMenuPrimitive.Content
|
|
50
50
|
align={align}
|
|
51
51
|
className={cx(
|
|
52
|
-
'group z-10 max-h-[var(--radix-popover-content-available-height)] max-w-[var(--radix-popover-trigger-width)] min-w-[
|
|
52
|
+
'group z-10 max-h-[var(--radix-popover-content-available-height)] max-w-[var(--radix-popover-trigger-width)] min-w-[var(--radix-popover-trigger-width)]',
|
|
53
53
|
className
|
|
54
54
|
)}
|
|
55
55
|
data-slot="dropdown-menu-content"
|
package/src/components/input.tsx
CHANGED
|
@@ -43,7 +43,7 @@ export function InputContent({ className, ...props }: ComponentProps<'div'>) {
|
|
|
43
43
|
const variants = cva(
|
|
44
44
|
[
|
|
45
45
|
'relative h-large-4x rounded-small-1x gap-small py-small_1x-alt px-small flex items-center w-full',
|
|
46
|
-
'focus-within:border-primary-idle focus-within:
|
|
46
|
+
'focus-within:border-primary-idle focus-within:border',
|
|
47
47
|
'group-has-[div[data-slot=input-right-action-slot]]:rounded-r-none',
|
|
48
48
|
'group-has-[div[data-slot=input-left-action-slot]]:rounded-l-none'
|
|
49
49
|
],
|
|
@@ -25,7 +25,7 @@ function Input({
|
|
|
25
25
|
className={cx(
|
|
26
26
|
className,
|
|
27
27
|
'text-neutral-dark h-large-6x bg-layer-2-hover rounded-small-1x w-full text-center outline-0',
|
|
28
|
-
'focus-within:border-primary-idle focus-within:
|
|
28
|
+
'focus-within:border-primary-idle focus-within:border'
|
|
29
29
|
)}
|
|
30
30
|
{...props}
|
|
31
31
|
/>
|
|
@@ -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,160 @@
|
|
|
1
|
+
import { cx } from '@xjur-ui/util'
|
|
2
|
+
import { Icon } from './icon'
|
|
3
|
+
|
|
4
|
+
export function Table({ className, ...props }: React.ComponentProps<'table'>) {
|
|
5
|
+
return (
|
|
6
|
+
<div
|
|
7
|
+
className="text-size-body-md scrollbar-thin relative w-full overflow-hidden transition-all hover:overflow-x-auto"
|
|
8
|
+
data-slot="table-container"
|
|
9
|
+
>
|
|
10
|
+
<table
|
|
11
|
+
className={cx('w-full caption-bottom', className)}
|
|
12
|
+
data-slot="table"
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
</div>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function TableHeader({
|
|
20
|
+
className,
|
|
21
|
+
...props
|
|
22
|
+
}: React.ComponentProps<'thead'>) {
|
|
23
|
+
return (
|
|
24
|
+
<thead
|
|
25
|
+
className={cx(
|
|
26
|
+
'group/table-header [&_tr]:border-border-layer-2 hover:bg-layer-2-hover [&_tr]:border-b',
|
|
27
|
+
className
|
|
28
|
+
)}
|
|
29
|
+
data-slot="table-header"
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function TableBody({
|
|
36
|
+
className,
|
|
37
|
+
...props
|
|
38
|
+
}: React.ComponentProps<'tbody'>) {
|
|
39
|
+
return (
|
|
40
|
+
<tbody
|
|
41
|
+
className={cx(
|
|
42
|
+
'[&_tr]:border-border-layer-2 [&_tr]:border-b [&_tr:last-child]:border-0',
|
|
43
|
+
className
|
|
44
|
+
)}
|
|
45
|
+
data-slot="table-body"
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function TableFooter({
|
|
52
|
+
className,
|
|
53
|
+
...props
|
|
54
|
+
}: React.ComponentProps<'tfoot'>) {
|
|
55
|
+
return (
|
|
56
|
+
<tfoot
|
|
57
|
+
className={cx(
|
|
58
|
+
'border-border-layer-2 border-t font-medium [&>tr]:last:border-b-0',
|
|
59
|
+
className
|
|
60
|
+
)}
|
|
61
|
+
data-slot="table-footer"
|
|
62
|
+
{...props}
|
|
63
|
+
/>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function TableRow({
|
|
68
|
+
className,
|
|
69
|
+
isSelected = false,
|
|
70
|
+
...props
|
|
71
|
+
}: React.ComponentProps<'tr'> & { isSelected?: boolean }) {
|
|
72
|
+
return (
|
|
73
|
+
<tr
|
|
74
|
+
className={cx(
|
|
75
|
+
'bg-layer-2-idle hover:bg-layer-2-hover ease border-b transition-colors duration-100',
|
|
76
|
+
'active:bg-layer-2-pressed',
|
|
77
|
+
'data-[selected=true]:bg-primary-alt-active',
|
|
78
|
+
className
|
|
79
|
+
)}
|
|
80
|
+
data-selected={isSelected}
|
|
81
|
+
data-slot="table-row"
|
|
82
|
+
{...props}
|
|
83
|
+
/>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function TableHead({ className, ...props }: React.ComponentProps<'th'>) {
|
|
88
|
+
return (
|
|
89
|
+
<th
|
|
90
|
+
className={cx(
|
|
91
|
+
'group/th text-neutral-placeholder text-size-label-sm h-large-4x py-small-1x px-small text-left align-middle font-medium whitespace-nowrap',
|
|
92
|
+
'pr-large-2x relative',
|
|
93
|
+
'animate-in fade-in duration-200 ease-in',
|
|
94
|
+
className
|
|
95
|
+
)}
|
|
96
|
+
data-slot="table-head"
|
|
97
|
+
{...props}
|
|
98
|
+
/>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function TableHeadContent({
|
|
103
|
+
className,
|
|
104
|
+
...props
|
|
105
|
+
}: React.ComponentProps<'div'>) {
|
|
106
|
+
return (
|
|
107
|
+
<div
|
|
108
|
+
className={cx(
|
|
109
|
+
'gap-small pr-large-3x relative flex w-fit items-center',
|
|
110
|
+
className
|
|
111
|
+
)}
|
|
112
|
+
{...props}
|
|
113
|
+
/>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function TableSort({
|
|
118
|
+
className,
|
|
119
|
+
sortedDirection,
|
|
120
|
+
...props
|
|
121
|
+
}: React.ComponentProps<'button'> & { sortedDirection?: 'asc' | 'desc' }) {
|
|
122
|
+
return (
|
|
123
|
+
<button
|
|
124
|
+
className={cx(
|
|
125
|
+
'size-large-1x rounded-small-1x',
|
|
126
|
+
'flex cursor-pointer items-center justify-center',
|
|
127
|
+
'hidden group-hover/th:flex',
|
|
128
|
+
'data-[sorted=true]:flex',
|
|
129
|
+
'transition-all duration-200 ease-out',
|
|
130
|
+
'data-[sorted=true]:bg-primary-alt-active data-[sorted=true]:text-primary-label-active',
|
|
131
|
+
'absolute top-1/2 right-0 -translate-y-1/2',
|
|
132
|
+
className
|
|
133
|
+
)}
|
|
134
|
+
data-sorted={sortedDirection !== undefined}
|
|
135
|
+
type="button"
|
|
136
|
+
{...props}
|
|
137
|
+
>
|
|
138
|
+
{sortedDirection !== undefined ? (
|
|
139
|
+
<Icon
|
|
140
|
+
name={sortedDirection === 'asc' ? 'arrow_downward' : 'arrow_upward'}
|
|
141
|
+
/>
|
|
142
|
+
) : (
|
|
143
|
+
<Icon name="swap_vert" />
|
|
144
|
+
)}
|
|
145
|
+
</button>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function TableCell({ className, ...props }: React.ComponentProps<'td'>) {
|
|
150
|
+
return (
|
|
151
|
+
<td
|
|
152
|
+
className={cx(
|
|
153
|
+
'px-small py-none h-large-8x text-left align-middle whitespace-nowrap',
|
|
154
|
+
className
|
|
155
|
+
)}
|
|
156
|
+
data-slot="table-cell"
|
|
157
|
+
{...props}
|
|
158
|
+
/>
|
|
159
|
+
)
|
|
160
|
+
}
|