lark-docs-variables 1.0.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.
- package/.vscode/launch.json +18 -0
- package/.vscode/settings.json +42 -0
- package/README.md +1 -0
- package/app.json +19 -0
- package/components.json +38 -0
- package/eslint.config.js +123 -0
- package/package.json +55 -0
- package/src/components/Box.tsx +10 -0
- package/src/components/Icon.module.css +3 -0
- package/src/components/Icon.tsx +26 -0
- package/src/components/ValueToggle.module.css +4 -0
- package/src/components/ValueToggle.tsx +25 -0
- package/src/components/base/Button.tsx +46 -0
- package/src/components/base/Card.tsx +74 -0
- package/src/components/base/Combobox.tsx +217 -0
- package/src/components/base/Input.tsx +17 -0
- package/src/components/base/InputGroup.tsx +123 -0
- package/src/components/base/Textarea.tsx +15 -0
- package/src/components/base/Toggle.tsx +24 -0
- package/src/entries/Panel.module.css +33 -0
- package/src/entries/Panel.tsx +67 -0
- package/src/entries/Settings.css +3 -0
- package/src/entries/Settings.module.css +4 -0
- package/src/entries/Settings.tsx +14 -0
- package/src/globals.d.ts +1 -0
- package/src/hooks/useBlockHover.ts +20 -0
- package/src/hooks/useDebounce.ts +16 -0
- package/src/hooks/useDocument.ts +8 -0
- package/src/hooks/useIsMounted.ts +15 -0
- package/src/hooks/useRecord.ts +33 -0
- package/src/hooks/useResizeObserver.ts +90 -0
- package/src/hooks/useSelection.ts +16 -0
- package/src/hooks/useTimeoutFn.ts +40 -0
- package/src/index.css +125 -0
- package/src/lib/utils.ts +6 -0
- package/src/panel.html +11 -0
- package/src/panel.tsx +14 -0
- package/src/providers/BlockProvider.context.ts +10 -0
- package/src/providers/BlockProvider.tsx +30 -0
- package/src/public/icon.png +0 -0
- package/src/settings.html +11 -0
- package/src/settings.tsx +14 -0
- package/src/util/app.ts +3 -0
- package/src/util/box.ts +27 -0
- package/tsconfig.app.json +35 -0
- package/tsconfig.json +13 -0
- package/tsconfig.node.json +26 -0
- package/vite/plugin.ts +131 -0
- package/vite.config.ts +55 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { Button } from '@/components/base/Button'
|
|
2
|
+
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from '@/components/base/InputGroup'
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
import { Combobox as ComboboxPrimitive } from '@base-ui/react'
|
|
5
|
+
import { CheckIcon, ChevronDownIcon, XIcon } from 'lucide-react'
|
|
6
|
+
import { ComponentPropsWithRef, FunctionComponent } from 'react'
|
|
7
|
+
|
|
8
|
+
const Combobox = ComboboxPrimitive.Root
|
|
9
|
+
|
|
10
|
+
export const ComboboxValue: FunctionComponent<ComboboxPrimitive.Value.Props> = ({ ...props }) => {
|
|
11
|
+
return <ComboboxPrimitive.Value data-slot='combobox-value' {...props} />
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const ComboboxTrigger: FunctionComponent<ComboboxPrimitive.Trigger.Props> = ({
|
|
15
|
+
className,
|
|
16
|
+
children,
|
|
17
|
+
...props
|
|
18
|
+
}) => {
|
|
19
|
+
return (
|
|
20
|
+
<ComboboxPrimitive.Trigger
|
|
21
|
+
data-slot='combobox-trigger'
|
|
22
|
+
className={cn('[&_svg:not([class*=\'size-\'])]:size-4', className)}
|
|
23
|
+
{...props}>
|
|
24
|
+
{children}
|
|
25
|
+
<ChevronDownIcon className='text-muted-foreground size-4 pointer-events-none' />
|
|
26
|
+
</ComboboxPrimitive.Trigger>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const ComboboxClear: FunctionComponent<ComboboxPrimitive.Clear.Props> = ({ className, ...props }) => {
|
|
31
|
+
return (
|
|
32
|
+
<ComboboxPrimitive.Clear
|
|
33
|
+
data-slot='combobox-clear'
|
|
34
|
+
render={<InputGroupButton variant='ghost' size='icon-xs' />}
|
|
35
|
+
className={cn(className)}
|
|
36
|
+
{...props}>
|
|
37
|
+
<XIcon className='pointer-events-none' />
|
|
38
|
+
</ComboboxPrimitive.Clear>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const ComboboxInput: FunctionComponent<
|
|
43
|
+
ComboboxPrimitive.Input.Props & { showTrigger?: boolean; showClear?: boolean }
|
|
44
|
+
> = ({ className, children, disabled = false, showTrigger = true, showClear = false, ...props }) => {
|
|
45
|
+
return (
|
|
46
|
+
<InputGroup className={cn('w-auto', className)}>
|
|
47
|
+
<ComboboxPrimitive.Input render={<InputGroupInput disabled={disabled} />} {...props} />
|
|
48
|
+
<InputGroupAddon align='inline-end'>
|
|
49
|
+
{showTrigger && (
|
|
50
|
+
<InputGroupButton
|
|
51
|
+
size='icon-xs'
|
|
52
|
+
variant='ghost'
|
|
53
|
+
render={<ComboboxTrigger />}
|
|
54
|
+
data-slot='input-group-button'
|
|
55
|
+
className='group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent'
|
|
56
|
+
disabled={disabled}
|
|
57
|
+
/>
|
|
58
|
+
)}
|
|
59
|
+
{showClear && <ComboboxClear disabled={disabled} />}
|
|
60
|
+
</InputGroupAddon>
|
|
61
|
+
{children}
|
|
62
|
+
</InputGroup>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const ComboboxContent: FunctionComponent<
|
|
67
|
+
ComboboxPrimitive.Popup.Props &
|
|
68
|
+
Pick<ComboboxPrimitive.Positioner.Props, 'side' | 'align' | 'sideOffset' | 'alignOffset' | 'anchor'>
|
|
69
|
+
> = ({ className, side = 'bottom', sideOffset = 6, align = 'start', alignOffset = 0, anchor, ...props }) => {
|
|
70
|
+
return (
|
|
71
|
+
<ComboboxPrimitive.Portal>
|
|
72
|
+
<ComboboxPrimitive.Positioner
|
|
73
|
+
side={side}
|
|
74
|
+
sideOffset={sideOffset}
|
|
75
|
+
align={align}
|
|
76
|
+
alignOffset={alignOffset}
|
|
77
|
+
anchor={anchor}
|
|
78
|
+
className='isolate z-50'>
|
|
79
|
+
<ComboboxPrimitive.Popup
|
|
80
|
+
data-slot='combobox-content'
|
|
81
|
+
data-chips={!!anchor}
|
|
82
|
+
className={cn(
|
|
83
|
+
'bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-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 ring-foreground/10 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:border-input/30 max-h-72 min-w-36 overflow-hidden rounded-md shadow-md ring-1 duration-100 *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:shadow-none data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 group/combobox-content relative w-(--anchor-width) max-w-(--available-width) origin-(--transform-origin) data-[chips=true]:min-w-(--anchor-width)',
|
|
84
|
+
className
|
|
85
|
+
)}
|
|
86
|
+
{...props}
|
|
87
|
+
/>
|
|
88
|
+
</ComboboxPrimitive.Positioner>
|
|
89
|
+
</ComboboxPrimitive.Portal>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const ComboboxList: FunctionComponent<ComboboxPrimitive.List.Props> = ({ className, ...props }) => {
|
|
94
|
+
return (
|
|
95
|
+
<ComboboxPrimitive.List
|
|
96
|
+
data-slot='combobox-list'
|
|
97
|
+
className={cn(
|
|
98
|
+
'no-scrollbar max-h-[min(calc(--spacing(72)---spacing(9)),calc(var(--available-height)---spacing(9)))] scroll-py-1 overflow-y-auto p-1 data-empty:p-0 overscroll-contain',
|
|
99
|
+
className
|
|
100
|
+
)}
|
|
101
|
+
{...props}
|
|
102
|
+
/>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const ComboboxItem: FunctionComponent<ComboboxPrimitive.Item.Props> = ({ className, children, ...props }) => {
|
|
107
|
+
return (
|
|
108
|
+
<ComboboxPrimitive.Item
|
|
109
|
+
data-slot='combobox-item'
|
|
110
|
+
className={cn(
|
|
111
|
+
'data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm [&_svg:not([class*=\'size-\'])]:size-4 relative flex w-full cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0',
|
|
112
|
+
className
|
|
113
|
+
)}
|
|
114
|
+
{...props}>
|
|
115
|
+
{children}
|
|
116
|
+
<ComboboxPrimitive.ItemIndicator
|
|
117
|
+
render={<span className='pointer-events-none absolute right-2 flex size-4 items-center justify-center' />}>
|
|
118
|
+
<CheckIcon className='pointer-events-none' />
|
|
119
|
+
</ComboboxPrimitive.ItemIndicator>
|
|
120
|
+
</ComboboxPrimitive.Item>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const ComboboxGroup: FunctionComponent<ComboboxPrimitive.Group.Props> = ({ className, ...props }) => {
|
|
125
|
+
return <ComboboxPrimitive.Group data-slot='combobox-group' className={cn(className)} {...props} />
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export const ComboboxLabel: FunctionComponent<ComboboxPrimitive.GroupLabel.Props> = ({ className, ...props }) => {
|
|
129
|
+
return (
|
|
130
|
+
<ComboboxPrimitive.GroupLabel
|
|
131
|
+
data-slot='combobox-label'
|
|
132
|
+
className={cn('text-muted-foreground px-2 py-1.5 text-xs', className)}
|
|
133
|
+
{...props}
|
|
134
|
+
/>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export const ComboboxCollection: FunctionComponent<ComboboxPrimitive.Collection.Props> = ({ ...props }) => {
|
|
139
|
+
return <ComboboxPrimitive.Collection data-slot='combobox-collection' {...props} />
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const ComboboxEmpty: FunctionComponent<ComboboxPrimitive.Empty.Props> = ({ className, ...props }) => {
|
|
143
|
+
return (
|
|
144
|
+
<ComboboxPrimitive.Empty
|
|
145
|
+
data-slot='combobox-empty'
|
|
146
|
+
className={cn(
|
|
147
|
+
'text-muted-foreground hidden w-full justify-center py-2 text-center text-sm group-data-empty/combobox-content:flex',
|
|
148
|
+
className
|
|
149
|
+
)}
|
|
150
|
+
{...props}
|
|
151
|
+
/>
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export const ComboboxSeparator: FunctionComponent<ComboboxPrimitive.Separator.Props> = ({ className, ...props }) => {
|
|
156
|
+
return (
|
|
157
|
+
<ComboboxPrimitive.Separator
|
|
158
|
+
data-slot='combobox-separator'
|
|
159
|
+
className={cn('bg-border -mx-1 my-1 h-px', className)}
|
|
160
|
+
{...props}
|
|
161
|
+
/>
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export const ComboboxChips: FunctionComponent<
|
|
166
|
+
ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> & ComboboxPrimitive.Chips.Props
|
|
167
|
+
> = ({ className, ...props }) => {
|
|
168
|
+
return (
|
|
169
|
+
<ComboboxPrimitive.Chips
|
|
170
|
+
data-slot='combobox-chips'
|
|
171
|
+
className={cn(
|
|
172
|
+
'dark:bg-input/30 border-input focus-within:border-ring focus-within:ring-ring/50 has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40 has-aria-invalid:border-destructive dark:has-aria-invalid:border-destructive/50 flex min-h-9 flex-wrap items-center gap-1.5 rounded-md border bg-transparent bg-clip-padding px-2.5 py-1.5 text-sm shadow-xs transition-[color,box-shadow] focus-within:ring-[3px] has-aria-invalid:ring-[3px] has-data-[slot=combobox-chip]:px-1.5',
|
|
173
|
+
className
|
|
174
|
+
)}
|
|
175
|
+
{...props}
|
|
176
|
+
/>
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export const ComboboxChip: FunctionComponent<ComboboxPrimitive.Chip.Props & { showRemove?: boolean }> = ({
|
|
181
|
+
className,
|
|
182
|
+
children,
|
|
183
|
+
showRemove = true,
|
|
184
|
+
...props
|
|
185
|
+
}) => {
|
|
186
|
+
return (
|
|
187
|
+
<ComboboxPrimitive.Chip
|
|
188
|
+
data-slot='combobox-chip'
|
|
189
|
+
className={cn(
|
|
190
|
+
'bg-muted text-foreground flex h-[calc(--spacing(5.5))] w-fit items-center justify-center gap-1 rounded-sm px-1.5 text-xs font-medium whitespace-nowrap has-data-[slot=combobox-chip-remove]:pr-0 has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50',
|
|
191
|
+
className
|
|
192
|
+
)}
|
|
193
|
+
{...props}>
|
|
194
|
+
{children}
|
|
195
|
+
{showRemove && (
|
|
196
|
+
<ComboboxPrimitive.ChipRemove
|
|
197
|
+
render={<Button variant='ghost' size='icon-xs' />}
|
|
198
|
+
className='-ml-1 opacity-50 hover:opacity-100'
|
|
199
|
+
data-slot='combobox-chip-remove'>
|
|
200
|
+
<XIcon className='pointer-events-none' />
|
|
201
|
+
</ComboboxPrimitive.ChipRemove>
|
|
202
|
+
)}
|
|
203
|
+
</ComboboxPrimitive.Chip>
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export const ComboboxChipsInput: FunctionComponent<ComboboxPrimitive.Input.Props> = ({ className, ...props }) => {
|
|
208
|
+
return (
|
|
209
|
+
<ComboboxPrimitive.Input
|
|
210
|
+
data-slot='combobox-chip-input'
|
|
211
|
+
className={cn('min-w-16 flex-1 outline-none', className)}
|
|
212
|
+
{...props}
|
|
213
|
+
/>
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export { Combobox }
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { cn } from '@/lib/utils'
|
|
2
|
+
import { Input as InputPrimitive } from '@base-ui/react/input'
|
|
3
|
+
import { ComponentProps, FunctionComponent } from 'react'
|
|
4
|
+
|
|
5
|
+
export const Input: FunctionComponent<ComponentProps<'input'>> = ({ className, type, ...props }) => {
|
|
6
|
+
return (
|
|
7
|
+
<InputPrimitive
|
|
8
|
+
type={type}
|
|
9
|
+
data-slot='input'
|
|
10
|
+
className={cn(
|
|
11
|
+
'dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-9 rounded-md border bg-transparent px-2.5 py-1 text-base shadow-xs transition-[color,box-shadow] file:h-7 file:text-sm file:font-medium focus-visible:ring-[3px] aria-invalid:ring-[3px] md:text-sm file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',
|
|
12
|
+
className
|
|
13
|
+
)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Button } from '@/components/base/Button'
|
|
4
|
+
import { Input } from '@/components/base/Input'
|
|
5
|
+
import { Textarea } from '@/components/base/Textarea'
|
|
6
|
+
import { cn } from '@/lib/utils'
|
|
7
|
+
import { VariantProps, cva } from 'class-variance-authority'
|
|
8
|
+
import { ComponentProps, FunctionComponent } from 'react'
|
|
9
|
+
|
|
10
|
+
export const InputGroup: FunctionComponent<ComponentProps<'div'>> = ({ className, ...props }) => {
|
|
11
|
+
return (
|
|
12
|
+
<div
|
|
13
|
+
data-slot='input-group'
|
|
14
|
+
role='group'
|
|
15
|
+
className={cn(
|
|
16
|
+
'border-input dark:bg-input/30 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 h-9 rounded-md border shadow-xs transition-[color,box-shadow] has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot][aria-invalid=true]]:ring-[3px] has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5 in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 group/input-group relative flex w-full min-w-0 items-center outline-none has-[>textarea]:h-auto',
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const inputGroupAddonVariants = cva(
|
|
25
|
+
'text-muted-foreground h-auto gap-2 py-1.5 text-sm font-medium group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*=\'size-\'])]:size-4 flex cursor-text items-center justify-center select-none',
|
|
26
|
+
{
|
|
27
|
+
variants: {
|
|
28
|
+
align: {
|
|
29
|
+
'inline-start': 'pl-2 has-[>button]:ml-[-0.25rem] has-[>kbd]:ml-[-0.15rem] order-first',
|
|
30
|
+
'inline-end': 'pr-2 has-[>button]:mr-[-0.25rem] has-[>kbd]:mr-[-0.15rem] order-last',
|
|
31
|
+
'block-start':
|
|
32
|
+
'px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2 order-first w-full justify-start',
|
|
33
|
+
'block-end': 'px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2 order-last w-full justify-start'
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
defaultVariants: { align: 'inline-start' }
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
export const InputGroupAddon: FunctionComponent<
|
|
41
|
+
ComponentProps<'div'> & VariantProps<typeof inputGroupAddonVariants>
|
|
42
|
+
> = ({ className, align = 'inline-start', ...props }) => {
|
|
43
|
+
return (
|
|
44
|
+
<div
|
|
45
|
+
role='group'
|
|
46
|
+
data-slot='input-group-addon'
|
|
47
|
+
data-align={align}
|
|
48
|
+
className={cn(inputGroupAddonVariants({ align }), className)}
|
|
49
|
+
onClick={(e) => {
|
|
50
|
+
if ((e.target as HTMLElement).closest('button')) {
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
e.currentTarget.parentElement?.querySelector('input')?.focus()
|
|
54
|
+
}}
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const inputGroupButtonVariants = cva('gap-2 text-sm shadow-none flex items-center', {
|
|
61
|
+
variants: {
|
|
62
|
+
size: {
|
|
63
|
+
'xs': 'h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-1.5 [&>svg:not([class*=\'size-\'])]:size-3.5',
|
|
64
|
+
'sm': '',
|
|
65
|
+
'icon-xs': 'size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0',
|
|
66
|
+
'icon-sm': 'size-8 p-0 has-[>svg]:p-0'
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
defaultVariants: { size: 'xs' }
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
export const InputGroupButton: FunctionComponent<
|
|
73
|
+
Omit<ComponentProps<typeof Button>, 'size' | 'type'> &
|
|
74
|
+
VariantProps<typeof inputGroupButtonVariants> & { type?: 'button' | 'submit' | 'reset' }
|
|
75
|
+
> = ({ className, type = 'button', variant = 'ghost', size = 'xs', ...props }) => {
|
|
76
|
+
return (
|
|
77
|
+
<Button
|
|
78
|
+
type={type}
|
|
79
|
+
data-size={size}
|
|
80
|
+
variant={variant}
|
|
81
|
+
className={cn(inputGroupButtonVariants({ size }), className)}
|
|
82
|
+
{...props}
|
|
83
|
+
/>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const InputGroupText: FunctionComponent<ComponentProps<'span'>> = ({ className, ...props }) => {
|
|
88
|
+
return (
|
|
89
|
+
<span
|
|
90
|
+
className={cn(
|
|
91
|
+
'text-muted-foreground gap-2 text-sm [&_svg:not([class*=\'size-\'])]:size-4 flex items-center [&_svg]:pointer-events-none',
|
|
92
|
+
className
|
|
93
|
+
)}
|
|
94
|
+
{...props}
|
|
95
|
+
/>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const InputGroupInput: FunctionComponent<ComponentProps<'input'>> = ({ className, ...props }) => {
|
|
100
|
+
return (
|
|
101
|
+
<Input
|
|
102
|
+
data-slot='input-group-control'
|
|
103
|
+
className={cn(
|
|
104
|
+
'rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent flex-1',
|
|
105
|
+
className
|
|
106
|
+
)}
|
|
107
|
+
{...props}
|
|
108
|
+
/>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const InputGroupTextarea: FunctionComponent<ComponentProps<'textarea'>> = ({ className, ...props }) => {
|
|
113
|
+
return (
|
|
114
|
+
<Textarea
|
|
115
|
+
data-slot='input-group-control'
|
|
116
|
+
className={cn(
|
|
117
|
+
'rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent flex-1 resize-none',
|
|
118
|
+
className
|
|
119
|
+
)}
|
|
120
|
+
{...props}
|
|
121
|
+
/>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { cn } from '@/lib/utils'
|
|
2
|
+
import { ComponentProps, FunctionComponent } from 'react'
|
|
3
|
+
|
|
4
|
+
export const Textarea: FunctionComponent<ComponentProps<'textarea'>> = ({ className, ...props }) => {
|
|
5
|
+
return (
|
|
6
|
+
<textarea
|
|
7
|
+
data-slot='textarea'
|
|
8
|
+
className={cn(
|
|
9
|
+
'border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] focus-visible:ring-[3px] aria-invalid:ring-[3px] md:text-sm placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
|
10
|
+
className
|
|
11
|
+
)}
|
|
12
|
+
{...props}
|
|
13
|
+
/>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { cn } from '@/lib/utils'
|
|
2
|
+
import { Toggle as TogglePrimitive } from '@base-ui/react/toggle'
|
|
3
|
+
import { VariantProps, cva } from 'class-variance-authority'
|
|
4
|
+
import { FunctionComponent } from 'react'
|
|
5
|
+
|
|
6
|
+
const toggleVariants = cva(
|
|
7
|
+
'hover:text-foreground aria-pressed:bg-muted focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive gap-1 rounded-md text-sm font-medium transition-[color,box-shadow] [&_svg:not([class*=\'size-\'])]:size-4 group/toggle hover:bg-muted inline-flex items-center justify-center whitespace-nowrap outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0',
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: { default: 'bg-transparent', outline: 'border-input hover:bg-muted border bg-transparent shadow-xs' },
|
|
11
|
+
size: { default: 'h-9 min-w-9 px-2', sm: 'h-8 min-w-8 px-1.5', lg: 'h-10 min-w-10 px-2.5' }
|
|
12
|
+
},
|
|
13
|
+
defaultVariants: { variant: 'default', size: 'default' }
|
|
14
|
+
}
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
export const Toggle: FunctionComponent<TogglePrimitive.Props & VariantProps<typeof toggleVariants>> = ({
|
|
18
|
+
className,
|
|
19
|
+
variant = 'default',
|
|
20
|
+
size = 'default',
|
|
21
|
+
...props
|
|
22
|
+
}) => {
|
|
23
|
+
return <TogglePrimitive data-slot='toggle' className={cn(toggleVariants({ variant, size, className }))} {...props} />
|
|
24
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
.container {
|
|
2
|
+
background-color: var(--background);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.inner {
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
border-radius: 8px;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.name {
|
|
12
|
+
height: 36px !important;
|
|
13
|
+
min-height: 36px !important;
|
|
14
|
+
width: 100%;
|
|
15
|
+
min-width: 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.name.expanded {
|
|
19
|
+
border-bottom-left-radius: 0;
|
|
20
|
+
border-bottom-right-radius: 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.name:hover {
|
|
24
|
+
box-shadow: 0 0 0 1px var(--ring) inset;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.textarea {
|
|
28
|
+
resize: none;
|
|
29
|
+
border-top-left-radius: 0;
|
|
30
|
+
border-top-right-radius: 0;
|
|
31
|
+
border-top: none;
|
|
32
|
+
min-height: 40px;
|
|
33
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
2
|
+
import { AsteriskSquare, ChevronDown, ChevronUp, Code2, CodeSquare, Text } from 'lucide-react'
|
|
3
|
+
import { FunctionComponent, useState } from 'react'
|
|
4
|
+
import { Button } from '../components/base/Button'
|
|
5
|
+
import { InputGroup, InputGroupAddon, InputGroupInput } from '../components/base/InputGroup'
|
|
6
|
+
import { Textarea } from '../components/base/Textarea'
|
|
7
|
+
import { ValueToggle } from '../components/ValueToggle'
|
|
8
|
+
import { useBlock } from '../hooks/useDocument'
|
|
9
|
+
import { useRecord } from '../hooks/useRecord'
|
|
10
|
+
import { app } from '../util/app'
|
|
11
|
+
import { box } from '../util/box'
|
|
12
|
+
import styles from './Panel.module.css'
|
|
13
|
+
|
|
14
|
+
export interface BlockData {
|
|
15
|
+
name: string
|
|
16
|
+
defaultValue: string
|
|
17
|
+
required: boolean
|
|
18
|
+
escape: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const Panel: FunctionComponent = () => {
|
|
22
|
+
const { docRef, blockRef } = useBlock()
|
|
23
|
+
const { record, setRecord } = useRecord<BlockData>({ name: '', defaultValue: '', required: false, escape: false })
|
|
24
|
+
const [open, setOpen] = useState(false)
|
|
25
|
+
const expanded = !record.required && open
|
|
26
|
+
return (
|
|
27
|
+
<div className={clsx(styles['container'])}>
|
|
28
|
+
<div className={clsx(styles['inner'])}>
|
|
29
|
+
<InputGroup className={clsx(styles['name'], expanded && styles['expanded'])}>
|
|
30
|
+
<InputGroupAddon>
|
|
31
|
+
<Code2 size={16} color='var(--primary)' />
|
|
32
|
+
</InputGroupAddon>
|
|
33
|
+
<InputGroupInput type='text' placeholder='VARIABLE:NAME' style={box({ flex: 1 })}
|
|
34
|
+
value={record.name}
|
|
35
|
+
onKeyDown={(event) => {
|
|
36
|
+
if (event.key === 'Backspace' && record.name.length === 0) {
|
|
37
|
+
app.Block.removeBlock(blockRef)
|
|
38
|
+
app.Selection.clearSelection(docRef)
|
|
39
|
+
}
|
|
40
|
+
}}
|
|
41
|
+
onChange={({ target }) => { setRecord((cur) => ({ ...cur, name: target.value.toUpperCase() })) }} />
|
|
42
|
+
<InputGroupAddon align="inline-end" style={{ marginRight: 0, paddingRight: '0.25rem', gap: 0 }}>
|
|
43
|
+
{(!record.required && record.defaultValue) && <Text size={16} style={{ marginRight: 6 }} />}
|
|
44
|
+
<ValueToggle icon={CodeSquare} label={(value) => value ? 'ESC' : 'PLN'}
|
|
45
|
+
value={record.escape}
|
|
46
|
+
onChange={(escape) => { setRecord((cur) => ({ ...cur, escape })) }} />
|
|
47
|
+
<ValueToggle icon={AsteriskSquare} label={(value) => value ? 'REQ' : 'OPT'}
|
|
48
|
+
value={record.required}
|
|
49
|
+
onChange={(required) => { setRecord((cur) => ({ ...cur, required })) }} />
|
|
50
|
+
{/* <Button size='icon-sm' variant='ghost' onClick={() => {
|
|
51
|
+
app.View.Action.openModal({ title: 'Variable Settings', width: 480, data: { docToken: docRef.docToken } })
|
|
52
|
+
}}><Settings size={16} /></Button> */}
|
|
53
|
+
<Button disabled={record.required} size='icon-xs' variant='ghost' onClick={() => { setOpen(!open) }}>
|
|
54
|
+
{expanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
|
|
55
|
+
</Button>
|
|
56
|
+
</InputGroupAddon>
|
|
57
|
+
</InputGroup>
|
|
58
|
+
{expanded && (
|
|
59
|
+
<Textarea className={styles['textarea']} placeholder='Default value' rows={1}
|
|
60
|
+
value={record.defaultValue}
|
|
61
|
+
onChange={(({ target }) => { setRecord((cur) => ({ ...cur, defaultValue: target.value })) })}
|
|
62
|
+
/>
|
|
63
|
+
)}
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import './Settings.css'
|
|
2
|
+
|
|
3
|
+
import { FunctionComponent } from 'react'
|
|
4
|
+
import { useBlock } from '../hooks/useDocument'
|
|
5
|
+
import styles from './Settings.module.css'
|
|
6
|
+
|
|
7
|
+
export const Settings: FunctionComponent = () => {
|
|
8
|
+
const { blockRef } = useBlock()
|
|
9
|
+
return (
|
|
10
|
+
<div className={styles['container']}>
|
|
11
|
+
<pre>{JSON.stringify(blockRef, null, 2)}</pre>
|
|
12
|
+
</div>
|
|
13
|
+
)
|
|
14
|
+
}
|
package/src/globals.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module '*.module.css'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { BlockHoverChangeEvent } from '@lark-opdev/block-docs-addon-api'
|
|
2
|
+
import { useEffect, useState } from 'react'
|
|
3
|
+
import { app } from '../util/app'
|
|
4
|
+
import { useBlock } from './useDocument'
|
|
5
|
+
|
|
6
|
+
export function useBlockHover() {
|
|
7
|
+
const [hover, setHover] = useState(false)
|
|
8
|
+
|
|
9
|
+
const { blockRef } = useBlock()
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const onHoverChange = (event: BlockHoverChangeEvent) => {
|
|
12
|
+
if (!blockRef || !event.currBlock) { return }
|
|
13
|
+
setHover(blockRef.blockId === event.currBlock.id)
|
|
14
|
+
}
|
|
15
|
+
app.Events.onBlockHoverChange(onHoverChange)
|
|
16
|
+
return () => { app.Events.offBlockHoverChange(onHoverChange) }
|
|
17
|
+
}, [blockRef])
|
|
18
|
+
|
|
19
|
+
return hover
|
|
20
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DependencyList, useEffect } from 'react'
|
|
2
|
+
import useTimeoutFn from './useTimeoutFn'
|
|
3
|
+
|
|
4
|
+
export type UseDebounceReturn = [() => boolean | null, () => void]
|
|
5
|
+
|
|
6
|
+
export default function useDebounce(
|
|
7
|
+
fn: Function,
|
|
8
|
+
ms = 0,
|
|
9
|
+
deps: DependencyList = []
|
|
10
|
+
): UseDebounceReturn {
|
|
11
|
+
const [isReady, cancel, reset] = useTimeoutFn(fn, ms)
|
|
12
|
+
|
|
13
|
+
useEffect(reset, deps)
|
|
14
|
+
|
|
15
|
+
return [isReady, cancel]
|
|
16
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { useContext } from 'react'
|
|
2
|
+
import { BlockContext } from '../providers/BlockProvider.context'
|
|
3
|
+
|
|
4
|
+
export function useBlock() {
|
|
5
|
+
const context = useContext(BlockContext)
|
|
6
|
+
if (!context) { throw new Error('useBlock must be used within a BlockProvider') }
|
|
7
|
+
return context
|
|
8
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
export function useIsMounted(): () => boolean {
|
|
4
|
+
const isMounted = useRef(false)
|
|
5
|
+
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
isMounted.current = true
|
|
8
|
+
|
|
9
|
+
return () => {
|
|
10
|
+
isMounted.current = false
|
|
11
|
+
}
|
|
12
|
+
}, [])
|
|
13
|
+
|
|
14
|
+
return useCallback(() => isMounted.current, [])
|
|
15
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type RecordData } from '@lark-opdev/block-docs-addon-api'
|
|
2
|
+
import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react'
|
|
3
|
+
import { app } from '../util/app'
|
|
4
|
+
import useDebounce from './useDebounce'
|
|
5
|
+
|
|
6
|
+
export type UpdateRecordFn<T extends RecordData> = (value: Partial<T>, path?: string[]) => Promise<T>
|
|
7
|
+
|
|
8
|
+
export interface UseRecordReturn<T extends RecordData> {
|
|
9
|
+
record: T
|
|
10
|
+
setRecord: Dispatch<SetStateAction<T>>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function useRecord<T extends RecordData>(initialValue: T, delay = 500): UseRecordReturn<T> {
|
|
14
|
+
const [record, setRecord] = useState<T>(initialValue)
|
|
15
|
+
const shouldSkip = useRef(true)
|
|
16
|
+
|
|
17
|
+
useDebounce(() => {
|
|
18
|
+
if (shouldSkip.current) {
|
|
19
|
+
shouldSkip.current = false
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
console.info('save')
|
|
23
|
+
app.Record.setRecord([{ type: 'replace', data: { path: [], value: record } }])
|
|
24
|
+
}, delay, [record])
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
app.Record.getRecord().then((value) => {
|
|
28
|
+
setRecord({ ...initialValue, ...value })
|
|
29
|
+
})
|
|
30
|
+
}, [])
|
|
31
|
+
|
|
32
|
+
return { record, setRecord }
|
|
33
|
+
}
|