@choice-ui/command 0.0.3
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/README.md +571 -0
- package/dist/index.cjs +1309 -0
- package/dist/index.d.cts +130 -0
- package/dist/index.d.ts +130 -0
- package/dist/index.js +1300 -0
- package/package.json +50 -0
- package/src/command-score.ts +171 -0
- package/src/command.tsx +482 -0
- package/src/components/command-divider.tsx +30 -0
- package/src/components/command-empty.tsx +30 -0
- package/src/components/command-footer.tsx +22 -0
- package/src/components/command-group.tsx +76 -0
- package/src/components/command-input.tsx +66 -0
- package/src/components/command-item.tsx +165 -0
- package/src/components/command-list.tsx +77 -0
- package/src/components/command-loading.tsx +30 -0
- package/src/components/command-tabs.tsx +20 -0
- package/src/components/command-value.tsx +23 -0
- package/src/components/index.ts +10 -0
- package/src/context/command-context.ts +5 -0
- package/src/context/create-command-context.ts +140 -0
- package/src/context/index.ts +2 -0
- package/src/hooks/index.ts +10 -0
- package/src/hooks/use-as-ref.ts +12 -0
- package/src/hooks/use-command-state.ts +18 -0
- package/src/hooks/use-command.ts +10 -0
- package/src/hooks/use-schedule-layout-effect.ts +19 -0
- package/src/hooks/use-value.ts +39 -0
- package/src/index.ts +31 -0
- package/src/store/index.ts +1 -0
- package/src/tv.ts +248 -0
- package/src/types.ts +84 -0
- package/src/utils/constants.ts +7 -0
- package/src/utils/dom.ts +19 -0
- package/src/utils/helpers.ts +45 -0
- package/src/utils/index.ts +3 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { tcx } from "@choice-ui/shared"
|
|
2
|
+
import { forwardRef, HTMLProps } from "react"
|
|
3
|
+
import { useCommand, useCommandState } from "../hooks"
|
|
4
|
+
import { commandTv } from "../tv"
|
|
5
|
+
|
|
6
|
+
export interface CommandDividerProps extends HTMLProps<HTMLDivElement> {
|
|
7
|
+
/** 是否始终渲染此分隔符。当禁用自动过滤功能时特别有用。 */
|
|
8
|
+
alwaysRender?: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const CommandDivider = forwardRef<HTMLDivElement, CommandDividerProps>(
|
|
12
|
+
({ className, alwaysRender, ...props }, forwardedRef) => {
|
|
13
|
+
const context = useCommand()
|
|
14
|
+
const render = useCommandState((state) => !state.search)
|
|
15
|
+
const tv = commandTv({ variant: context.variant })
|
|
16
|
+
|
|
17
|
+
if (!alwaysRender && !render) return null
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
ref={forwardedRef}
|
|
22
|
+
{...props}
|
|
23
|
+
className={tcx(tv.divider({ className }))}
|
|
24
|
+
role="separator"
|
|
25
|
+
/>
|
|
26
|
+
)
|
|
27
|
+
},
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
CommandDivider.displayName = "CommandDivider"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { tcx } from "@choice-ui/shared"
|
|
2
|
+
import { forwardRef, HTMLProps } from "react"
|
|
3
|
+
import { useCommand, useCommandState } from "../hooks"
|
|
4
|
+
import { commandEmptyTv } from "../tv"
|
|
5
|
+
|
|
6
|
+
export interface CommandEmptyProps extends HTMLProps<HTMLDivElement> {
|
|
7
|
+
className?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const CommandEmpty = forwardRef<HTMLDivElement, CommandEmptyProps>(
|
|
11
|
+
({ className, ...props }, forwardedRef) => {
|
|
12
|
+
const context = useCommand()
|
|
13
|
+
const render = useCommandState((state) => state.filtered.count === 0)
|
|
14
|
+
|
|
15
|
+
const tv = commandEmptyTv({ variant: context.variant })
|
|
16
|
+
|
|
17
|
+
if (!render) return null
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
ref={forwardedRef}
|
|
22
|
+
{...props}
|
|
23
|
+
className={tcx(tv.root({ className }))}
|
|
24
|
+
role="presentation"
|
|
25
|
+
/>
|
|
26
|
+
)
|
|
27
|
+
},
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
CommandEmpty.displayName = "CommandEmpty"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { tcx } from "@choice-ui/shared"
|
|
2
|
+
import { forwardRef, HTMLProps } from "react"
|
|
3
|
+
import { useCommand } from "../hooks/use-command"
|
|
4
|
+
import { commandFooterTv } from "../tv"
|
|
5
|
+
|
|
6
|
+
export const CommandFooter = forwardRef<HTMLDivElement, HTMLProps<HTMLDivElement>>(
|
|
7
|
+
({ className, ...props }, ref) => {
|
|
8
|
+
const context = useCommand()
|
|
9
|
+
|
|
10
|
+
const tv = commandFooterTv({ variant: context.variant })
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div
|
|
14
|
+
ref={ref}
|
|
15
|
+
className={tcx(tv.root({ className }))}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
)
|
|
19
|
+
},
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
CommandFooter.displayName = "CommandFooter"
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { tcx } from "@choice-ui/shared"
|
|
2
|
+
import React, { forwardRef, HTMLProps, useEffect, useId, useMemo, useRef } from "react"
|
|
3
|
+
import { GroupContext, useCommand, useCommandState, useValue } from "../hooks"
|
|
4
|
+
import { commandGroupTv } from "../tv"
|
|
5
|
+
|
|
6
|
+
export interface CommandGroupProps extends HTMLProps<HTMLDivElement> {
|
|
7
|
+
forceMount?: boolean
|
|
8
|
+
heading?: React.ReactNode
|
|
9
|
+
value?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const CommandGroup = forwardRef<HTMLDivElement, CommandGroupProps>((props, forwardedRef) => {
|
|
13
|
+
const { className, heading, children, forceMount, value, ...rest } = props
|
|
14
|
+
const id = useId()
|
|
15
|
+
const ref = useRef<HTMLDivElement | null>(null)
|
|
16
|
+
const headingRef = useRef<HTMLDivElement | null>(null)
|
|
17
|
+
const headingId = React.useId()
|
|
18
|
+
const context = useCommand()
|
|
19
|
+
|
|
20
|
+
const render = useCommandState((state) =>
|
|
21
|
+
forceMount
|
|
22
|
+
? true
|
|
23
|
+
: context.filter() === false
|
|
24
|
+
? true
|
|
25
|
+
: !state.search
|
|
26
|
+
? true
|
|
27
|
+
: state.filtered.groups.has(id),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
// 注册group
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
return context.group(id)
|
|
33
|
+
}, [context, id])
|
|
34
|
+
|
|
35
|
+
const valueDeps = useMemo(() => [value, heading, headingRef], [value, heading])
|
|
36
|
+
useValue(id, ref, valueDeps)
|
|
37
|
+
|
|
38
|
+
const contextValue = useMemo(() => ({ id, forceMount }), [id, forceMount])
|
|
39
|
+
|
|
40
|
+
const tv = commandGroupTv({ variant: context.variant })
|
|
41
|
+
|
|
42
|
+
if (!render) return null
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div
|
|
46
|
+
ref={(el) => {
|
|
47
|
+
ref.current = el
|
|
48
|
+
if (typeof forwardedRef === "function") forwardedRef(el)
|
|
49
|
+
else if (forwardedRef) forwardedRef.current = el
|
|
50
|
+
}}
|
|
51
|
+
{...rest}
|
|
52
|
+
className={tcx(tv.root({ className }))}
|
|
53
|
+
role="presentation"
|
|
54
|
+
data-value={value}
|
|
55
|
+
>
|
|
56
|
+
{heading && (
|
|
57
|
+
<div
|
|
58
|
+
ref={headingRef}
|
|
59
|
+
className={tcx(tv.heading())}
|
|
60
|
+
aria-hidden
|
|
61
|
+
id={headingId}
|
|
62
|
+
>
|
|
63
|
+
{heading}
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
<div
|
|
67
|
+
role="group"
|
|
68
|
+
aria-labelledby={heading ? headingId : undefined}
|
|
69
|
+
>
|
|
70
|
+
<GroupContext.Provider value={contextValue}>{children}</GroupContext.Provider>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
CommandGroup.displayName = "CommandGroup"
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { tcx } from "@choice-ui/shared"
|
|
2
|
+
import { Input, type InputProps } from "@choice-ui/input"
|
|
3
|
+
import { forwardRef, ReactNode, useEffect } from "react"
|
|
4
|
+
import { useEventCallback } from "usehooks-ts"
|
|
5
|
+
import { useCommand, useCommandState } from "../hooks"
|
|
6
|
+
import { commandInputTv } from "../tv"
|
|
7
|
+
|
|
8
|
+
export interface CommandInputProps extends Omit<InputProps, "value" | "onChange" | "type"> {
|
|
9
|
+
onChange?: (search: string) => void
|
|
10
|
+
prefixElement?: ReactNode
|
|
11
|
+
suffixElement?: ReactNode
|
|
12
|
+
value?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const CommandInput = forwardRef<HTMLInputElement, CommandInputProps>((props, ref) => {
|
|
16
|
+
const { className, onChange, value, prefixElement, suffixElement, ...rest } = props
|
|
17
|
+
const isControlled = value != null
|
|
18
|
+
const store = useCommand().store
|
|
19
|
+
const search = useCommandState((state) => state.search)
|
|
20
|
+
const selectedItemId = useCommandState((state) => state.selectedItemId)
|
|
21
|
+
const context = useCommand()
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (value != null) {
|
|
25
|
+
store.setState("search", value)
|
|
26
|
+
}
|
|
27
|
+
}, [value, store])
|
|
28
|
+
|
|
29
|
+
const handleChange = useEventCallback((value: string) => {
|
|
30
|
+
if (!isControlled) {
|
|
31
|
+
store.setState("search", value)
|
|
32
|
+
}
|
|
33
|
+
onChange?.(value)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const tv = commandInputTv({ size: context.size })
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className={tcx(tv.root({ className }))}>
|
|
40
|
+
{prefixElement}
|
|
41
|
+
<Input
|
|
42
|
+
ref={ref}
|
|
43
|
+
{...rest}
|
|
44
|
+
className={tcx(tv.input({ className }))}
|
|
45
|
+
variant={props.variant || context.variant}
|
|
46
|
+
data-command-input=""
|
|
47
|
+
autoComplete="off"
|
|
48
|
+
autoCorrect="off"
|
|
49
|
+
spellCheck={false}
|
|
50
|
+
aria-autocomplete="list"
|
|
51
|
+
role="combobox"
|
|
52
|
+
aria-expanded={true}
|
|
53
|
+
aria-controls={context.listId}
|
|
54
|
+
aria-labelledby={context.labelId}
|
|
55
|
+
aria-activedescendant={selectedItemId}
|
|
56
|
+
id={context.inputId}
|
|
57
|
+
type="text"
|
|
58
|
+
value={isControlled ? value : search}
|
|
59
|
+
onChange={handleChange}
|
|
60
|
+
/>
|
|
61
|
+
{suffixElement}
|
|
62
|
+
</div>
|
|
63
|
+
)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
CommandInput.displayName = "CommandInput"
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { tcx } from "@choice-ui/shared"
|
|
2
|
+
import { Kbd, type KbdKey } from "@choice-ui/kbd"
|
|
3
|
+
import React, { forwardRef, HTMLProps, ReactNode, useEffect, useMemo, useRef } from "react"
|
|
4
|
+
import { useEventCallback } from "usehooks-ts"
|
|
5
|
+
import { GroupContext, useCommand, useCommandState, useValue } from "../hooks"
|
|
6
|
+
import { commandItemTv } from "../tv"
|
|
7
|
+
import { SELECT_EVENT } from "../utils"
|
|
8
|
+
|
|
9
|
+
export interface CommandItemProps extends Omit<HTMLProps<HTMLDivElement>, "onSelect"> {
|
|
10
|
+
disabled?: boolean
|
|
11
|
+
forceMount?: boolean
|
|
12
|
+
keywords?: string[]
|
|
13
|
+
onSelect?: (value: string) => void
|
|
14
|
+
prefixElement?: ReactNode
|
|
15
|
+
shortcut?: {
|
|
16
|
+
keys?: ReactNode
|
|
17
|
+
modifier?: KbdKey | KbdKey[] | undefined
|
|
18
|
+
}
|
|
19
|
+
suffixElement?: ReactNode
|
|
20
|
+
value?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const CommandItem = forwardRef<HTMLDivElement, CommandItemProps>((props, forwardedRef) => {
|
|
24
|
+
const {
|
|
25
|
+
className,
|
|
26
|
+
disabled,
|
|
27
|
+
forceMount,
|
|
28
|
+
keywords,
|
|
29
|
+
onSelect,
|
|
30
|
+
value,
|
|
31
|
+
children,
|
|
32
|
+
prefixElement,
|
|
33
|
+
suffixElement,
|
|
34
|
+
shortcut,
|
|
35
|
+
...rest
|
|
36
|
+
} = props
|
|
37
|
+
|
|
38
|
+
const ref = useRef<HTMLDivElement | null>(null)
|
|
39
|
+
const id = React.useId()
|
|
40
|
+
const context = useCommand()
|
|
41
|
+
const groupContext = React.useContext(GroupContext)
|
|
42
|
+
|
|
43
|
+
const propsRef = useRef({
|
|
44
|
+
disabled,
|
|
45
|
+
forceMount: forceMount ?? groupContext?.forceMount,
|
|
46
|
+
keywords,
|
|
47
|
+
onSelect,
|
|
48
|
+
value,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
propsRef.current = {
|
|
52
|
+
disabled,
|
|
53
|
+
forceMount: forceMount ?? groupContext?.forceMount,
|
|
54
|
+
keywords,
|
|
55
|
+
onSelect,
|
|
56
|
+
value,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 注册item
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (!propsRef.current.forceMount) {
|
|
62
|
+
return context.item(id, groupContext?.id)
|
|
63
|
+
}
|
|
64
|
+
}, [context, groupContext?.id, id])
|
|
65
|
+
|
|
66
|
+
const valueDeps = useMemo(() => [value, children, ref], [value, children])
|
|
67
|
+
const stableKeywords = useMemo(() => keywords || [], [keywords])
|
|
68
|
+
const valueRef = useValue(id, ref, valueDeps, stableKeywords)
|
|
69
|
+
|
|
70
|
+
const store = context.store
|
|
71
|
+
|
|
72
|
+
const selected = useCommandState((state) =>
|
|
73
|
+
Boolean(state.value && state.value === valueRef?.current),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
const render = useCommandState((state) =>
|
|
77
|
+
propsRef.current.forceMount
|
|
78
|
+
? true
|
|
79
|
+
: context.filter() === false
|
|
80
|
+
? true
|
|
81
|
+
: !state.search
|
|
82
|
+
? true
|
|
83
|
+
: (state.filtered.items.get(id) ?? 0) > 0,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
// 处理选择事件,当用户点击或按下Enter键时触发onSelect回调
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
const element = ref.current
|
|
89
|
+
if (!element || disabled) return
|
|
90
|
+
|
|
91
|
+
const handleSelect = () => {
|
|
92
|
+
select()
|
|
93
|
+
propsRef.current.onSelect?.(valueRef.current || "")
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
element.addEventListener(SELECT_EVENT, handleSelect)
|
|
97
|
+
return () => element.removeEventListener(SELECT_EVENT, handleSelect)
|
|
98
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
99
|
+
}, [render, disabled, valueRef])
|
|
100
|
+
|
|
101
|
+
const select = () => {
|
|
102
|
+
store.setState("value", valueRef.current || "", true)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const hasValidShortcut = shortcut && (shortcut.modifier || shortcut.keys)
|
|
106
|
+
|
|
107
|
+
const handlePointerMove = useEventCallback(() => {
|
|
108
|
+
if (disabled || context.getDisablePointerSelection()) return
|
|
109
|
+
select()
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const handleClick = useEventCallback(() => {
|
|
113
|
+
if (disabled) return
|
|
114
|
+
propsRef.current.onSelect?.(valueRef.current || "")
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
const tv = commandItemTv({
|
|
118
|
+
selected,
|
|
119
|
+
disabled,
|
|
120
|
+
size: context.size,
|
|
121
|
+
hasPrefix: !!prefixElement,
|
|
122
|
+
hasSuffix: !!suffixElement,
|
|
123
|
+
variant: context.variant,
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
if (!render) return null
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<div
|
|
130
|
+
ref={(el) => {
|
|
131
|
+
ref.current = el
|
|
132
|
+
if (typeof forwardedRef === "function") forwardedRef(el)
|
|
133
|
+
else if (forwardedRef) forwardedRef.current = el
|
|
134
|
+
}}
|
|
135
|
+
{...rest}
|
|
136
|
+
id={id}
|
|
137
|
+
className={tcx(tv.root({ className }))}
|
|
138
|
+
role="option"
|
|
139
|
+
aria-disabled={disabled}
|
|
140
|
+
aria-selected={selected || undefined}
|
|
141
|
+
data-disabled={disabled}
|
|
142
|
+
data-selected={selected}
|
|
143
|
+
data-value={valueRef.current}
|
|
144
|
+
onPointerMove={handlePointerMove}
|
|
145
|
+
onClick={handleClick}
|
|
146
|
+
>
|
|
147
|
+
{prefixElement && <div className={tv.icon()}>{prefixElement}</div>}
|
|
148
|
+
|
|
149
|
+
{children}
|
|
150
|
+
|
|
151
|
+
{hasValidShortcut && (
|
|
152
|
+
<Kbd
|
|
153
|
+
className={tv.shortcut()}
|
|
154
|
+
keys={shortcut!.modifier}
|
|
155
|
+
>
|
|
156
|
+
{shortcut!.keys}
|
|
157
|
+
</Kbd>
|
|
158
|
+
)}
|
|
159
|
+
|
|
160
|
+
{suffixElement && <div className={tv.icon()}>{suffixElement}</div>}
|
|
161
|
+
</div>
|
|
162
|
+
)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
CommandItem.displayName = "CommandItem"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { tcx } from "@choice-ui/shared"
|
|
2
|
+
import { ScrollArea, type ScrollAreaProps } from "@choice-ui/scroll-area"
|
|
3
|
+
import { forwardRef, useEffect, useRef } from "react"
|
|
4
|
+
import { useCommand, useCommandState } from "../hooks"
|
|
5
|
+
import { commandListTv } from "../tv"
|
|
6
|
+
|
|
7
|
+
export interface CommandListProps extends ScrollAreaProps {
|
|
8
|
+
children: React.ReactNode
|
|
9
|
+
className?: string
|
|
10
|
+
label?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const CommandList = forwardRef<HTMLDivElement, CommandListProps>((props, forwardedRef) => {
|
|
14
|
+
const { children, className, label = "Suggestions", hoverBoundary = "none", ...rest } = props
|
|
15
|
+
const ref = useRef<HTMLDivElement | null>(null)
|
|
16
|
+
const height = useRef<HTMLDivElement | null>(null)
|
|
17
|
+
const selectedItemId = useCommandState((state) => state.selectedItemId)
|
|
18
|
+
const context = useCommand()
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (height.current && ref.current) {
|
|
22
|
+
const el = height.current
|
|
23
|
+
const wrapper = ref.current
|
|
24
|
+
let animationFrame: number
|
|
25
|
+
const observer = new ResizeObserver(() => {
|
|
26
|
+
animationFrame = requestAnimationFrame(() => {
|
|
27
|
+
const height = el.offsetHeight
|
|
28
|
+
wrapper.style.setProperty(`--cmdk-list-height`, height.toFixed(1) + "px")
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
observer.observe(el)
|
|
32
|
+
return () => {
|
|
33
|
+
cancelAnimationFrame(animationFrame)
|
|
34
|
+
observer.unobserve(el)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}, [])
|
|
38
|
+
|
|
39
|
+
const tv = commandListTv()
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<ScrollArea
|
|
43
|
+
variant={context.variant}
|
|
44
|
+
hoverBoundary={hoverBoundary}
|
|
45
|
+
{...rest}
|
|
46
|
+
>
|
|
47
|
+
<ScrollArea.Viewport
|
|
48
|
+
ref={(el) => {
|
|
49
|
+
ref.current = el
|
|
50
|
+
if (typeof forwardedRef === "function") forwardedRef(el)
|
|
51
|
+
else if (forwardedRef) forwardedRef.current = el
|
|
52
|
+
}}
|
|
53
|
+
{...rest}
|
|
54
|
+
className={tcx(tv.root({ className }))}
|
|
55
|
+
role="listbox"
|
|
56
|
+
tabIndex={-1}
|
|
57
|
+
aria-activedescendant={selectedItemId}
|
|
58
|
+
aria-label={label}
|
|
59
|
+
id={context.listId}
|
|
60
|
+
>
|
|
61
|
+
<ScrollArea.Content
|
|
62
|
+
className={tcx(tv.content())}
|
|
63
|
+
ref={(el) => {
|
|
64
|
+
height.current = el
|
|
65
|
+
if (context.listInnerRef) {
|
|
66
|
+
context.listInnerRef.current = el
|
|
67
|
+
}
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
{children}
|
|
71
|
+
</ScrollArea.Content>
|
|
72
|
+
</ScrollArea.Viewport>
|
|
73
|
+
</ScrollArea>
|
|
74
|
+
)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
CommandList.displayName = "CommandList"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { tcx } from "@choice-ui/shared"
|
|
2
|
+
import React, { forwardRef } from "react"
|
|
3
|
+
import { commandLoadingTv } from "../tv"
|
|
4
|
+
|
|
5
|
+
export interface CommandLoadingProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
label?: string
|
|
7
|
+
progress?: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const CommandLoading = forwardRef<HTMLDivElement, CommandLoadingProps>((props, ref) => {
|
|
11
|
+
const { className, children, label = "Loading...", progress, ...rest } = props
|
|
12
|
+
const tv = commandLoadingTv()
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div
|
|
16
|
+
ref={ref}
|
|
17
|
+
{...props}
|
|
18
|
+
className={tcx(tv.root({ className }))}
|
|
19
|
+
role="progressbar"
|
|
20
|
+
aria-valuenow={progress}
|
|
21
|
+
aria-valuemin={0}
|
|
22
|
+
aria-valuemax={100}
|
|
23
|
+
aria-label={label}
|
|
24
|
+
>
|
|
25
|
+
<div aria-hidden>{children}</div>
|
|
26
|
+
</div>
|
|
27
|
+
)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
CommandLoading.displayName = "CommandLoading"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Tabs, type TabsProps } from "@choice-ui/tabs"
|
|
2
|
+
import { forwardRef } from "react"
|
|
3
|
+
import { useCommand } from "../hooks/use-command"
|
|
4
|
+
import { commandTabsTv } from "../tv"
|
|
5
|
+
|
|
6
|
+
export const CommandTabs = forwardRef<HTMLDivElement, TabsProps>((props, ref) => {
|
|
7
|
+
const context = useCommand()
|
|
8
|
+
const tv = commandTabsTv()
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<Tabs
|
|
12
|
+
ref={ref}
|
|
13
|
+
variant={props.variant || context.variant}
|
|
14
|
+
className={tv.tabs()}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
CommandTabs.displayName = "CommandTabs"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { tcx } from "@choice-ui/shared"
|
|
2
|
+
import { forwardRef, HTMLProps } from "react"
|
|
3
|
+
import { useCommand } from "../hooks"
|
|
4
|
+
import { commandItemTv } from "../tv"
|
|
5
|
+
|
|
6
|
+
export const CommandValue = forwardRef<HTMLDivElement, HTMLProps<HTMLDivElement>>((props, ref) => {
|
|
7
|
+
const { className, children, ...rest } = props
|
|
8
|
+
const context = useCommand()
|
|
9
|
+
|
|
10
|
+
const tv = commandItemTv({ size: context.size })
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div
|
|
14
|
+
ref={ref}
|
|
15
|
+
{...rest}
|
|
16
|
+
className={tcx(tv.value({ className }))}
|
|
17
|
+
>
|
|
18
|
+
{children}
|
|
19
|
+
</div>
|
|
20
|
+
)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
CommandValue.displayName = "CommandValue"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./command-empty"
|
|
2
|
+
export * from "./command-footer"
|
|
3
|
+
export * from "./command-group"
|
|
4
|
+
export * from "./command-input"
|
|
5
|
+
export * from "./command-item"
|
|
6
|
+
export * from "./command-list"
|
|
7
|
+
export * from "./command-loading"
|
|
8
|
+
export * from "./command-divider"
|
|
9
|
+
export * from "./command-value"
|
|
10
|
+
export * from "./command-tabs"
|