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.
Files changed (49) hide show
  1. package/.vscode/launch.json +18 -0
  2. package/.vscode/settings.json +42 -0
  3. package/README.md +1 -0
  4. package/app.json +19 -0
  5. package/components.json +38 -0
  6. package/eslint.config.js +123 -0
  7. package/package.json +55 -0
  8. package/src/components/Box.tsx +10 -0
  9. package/src/components/Icon.module.css +3 -0
  10. package/src/components/Icon.tsx +26 -0
  11. package/src/components/ValueToggle.module.css +4 -0
  12. package/src/components/ValueToggle.tsx +25 -0
  13. package/src/components/base/Button.tsx +46 -0
  14. package/src/components/base/Card.tsx +74 -0
  15. package/src/components/base/Combobox.tsx +217 -0
  16. package/src/components/base/Input.tsx +17 -0
  17. package/src/components/base/InputGroup.tsx +123 -0
  18. package/src/components/base/Textarea.tsx +15 -0
  19. package/src/components/base/Toggle.tsx +24 -0
  20. package/src/entries/Panel.module.css +33 -0
  21. package/src/entries/Panel.tsx +67 -0
  22. package/src/entries/Settings.css +3 -0
  23. package/src/entries/Settings.module.css +4 -0
  24. package/src/entries/Settings.tsx +14 -0
  25. package/src/globals.d.ts +1 -0
  26. package/src/hooks/useBlockHover.ts +20 -0
  27. package/src/hooks/useDebounce.ts +16 -0
  28. package/src/hooks/useDocument.ts +8 -0
  29. package/src/hooks/useIsMounted.ts +15 -0
  30. package/src/hooks/useRecord.ts +33 -0
  31. package/src/hooks/useResizeObserver.ts +90 -0
  32. package/src/hooks/useSelection.ts +16 -0
  33. package/src/hooks/useTimeoutFn.ts +40 -0
  34. package/src/index.css +125 -0
  35. package/src/lib/utils.ts +6 -0
  36. package/src/panel.html +11 -0
  37. package/src/panel.tsx +14 -0
  38. package/src/providers/BlockProvider.context.ts +10 -0
  39. package/src/providers/BlockProvider.tsx +30 -0
  40. package/src/public/icon.png +0 -0
  41. package/src/settings.html +11 -0
  42. package/src/settings.tsx +14 -0
  43. package/src/util/app.ts +3 -0
  44. package/src/util/box.ts +27 -0
  45. package/tsconfig.app.json +35 -0
  46. package/tsconfig.json +13 -0
  47. package/tsconfig.node.json +26 -0
  48. package/vite/plugin.ts +131 -0
  49. 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,3 @@
1
+ body {
2
+ background-color: var(--bg-float) !important;
3
+ }
@@ -0,0 +1,4 @@
1
+ .container {
2
+ border-top: 1px solid #171717;
3
+ padding: 24px;
4
+ }
@@ -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
+ }
@@ -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
+ }