@douglasneuroinformatics/libui 6.2.1 → 6.3.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 (103) hide show
  1. package/dist/components/ComboBox/ComboBox.d.ts +27 -0
  2. package/dist/components/ComboBox/ComboBox.d.ts.map +1 -0
  3. package/dist/components/ComboBox/ComboBox.js +37 -0
  4. package/dist/components/ComboBox/ComboBox.js.map +1 -0
  5. package/dist/components/ComboBox/ComboBoxChips.d.ts +9 -0
  6. package/dist/components/ComboBox/ComboBoxChips.d.ts.map +1 -0
  7. package/dist/components/ComboBox/ComboBoxChips.js +17 -0
  8. package/dist/components/ComboBox/ComboBoxChips.js.map +1 -0
  9. package/dist/components/ComboBox/ComboBoxClear.d.ts +4 -0
  10. package/dist/components/ComboBox/ComboBoxClear.d.ts.map +1 -0
  11. package/dist/components/ComboBox/ComboBoxClear.js +10 -0
  12. package/dist/components/ComboBox/ComboBoxClear.js.map +1 -0
  13. package/dist/components/ComboBox/ComboBoxCollection.d.ts +4 -0
  14. package/dist/components/ComboBox/ComboBoxCollection.d.ts.map +1 -0
  15. package/dist/components/ComboBox/ComboBoxCollection.js +7 -0
  16. package/dist/components/ComboBox/ComboBoxCollection.js.map +1 -0
  17. package/dist/components/ComboBox/ComboBoxContent.d.ts +4 -0
  18. package/dist/components/ComboBox/ComboBoxContent.d.ts.map +1 -0
  19. package/dist/components/ComboBox/ComboBoxContent.js +8 -0
  20. package/dist/components/ComboBox/ComboBoxContent.js.map +1 -0
  21. package/dist/components/ComboBox/ComboBoxEmpty.d.ts +4 -0
  22. package/dist/components/ComboBox/ComboBoxEmpty.d.ts.map +1 -0
  23. package/dist/components/ComboBox/ComboBoxEmpty.js +8 -0
  24. package/dist/components/ComboBox/ComboBoxEmpty.js.map +1 -0
  25. package/dist/components/ComboBox/ComboBoxGroup.d.ts +4 -0
  26. package/dist/components/ComboBox/ComboBoxGroup.d.ts.map +1 -0
  27. package/dist/components/ComboBox/ComboBoxGroup.js +8 -0
  28. package/dist/components/ComboBox/ComboBoxGroup.js.map +1 -0
  29. package/dist/components/ComboBox/ComboBoxInput.d.ts +7 -0
  30. package/dist/components/ComboBox/ComboBoxInput.d.ts.map +1 -0
  31. package/dist/components/ComboBox/ComboBoxInput.js +17 -0
  32. package/dist/components/ComboBox/ComboBoxInput.js.map +1 -0
  33. package/dist/components/ComboBox/ComboBoxItem.d.ts +4 -0
  34. package/dist/components/ComboBox/ComboBoxItem.d.ts.map +1 -0
  35. package/dist/components/ComboBox/ComboBoxItem.js +9 -0
  36. package/dist/components/ComboBox/ComboBoxItem.js.map +1 -0
  37. package/dist/components/ComboBox/ComboBoxLabel.d.ts +4 -0
  38. package/dist/components/ComboBox/ComboBoxLabel.d.ts.map +1 -0
  39. package/dist/components/ComboBox/ComboBoxLabel.js +8 -0
  40. package/dist/components/ComboBox/ComboBoxLabel.js.map +1 -0
  41. package/dist/components/ComboBox/ComboBoxList.d.ts +4 -0
  42. package/dist/components/ComboBox/ComboBoxList.d.ts.map +1 -0
  43. package/dist/components/ComboBox/ComboBoxList.js +8 -0
  44. package/dist/components/ComboBox/ComboBoxList.js.map +1 -0
  45. package/dist/components/ComboBox/ComboBoxSeparator.d.ts +4 -0
  46. package/dist/components/ComboBox/ComboBoxSeparator.d.ts.map +1 -0
  47. package/dist/components/ComboBox/ComboBoxSeparator.js +8 -0
  48. package/dist/components/ComboBox/ComboBoxSeparator.js.map +1 -0
  49. package/dist/components/ComboBox/ComboBoxTrigger.d.ts +4 -0
  50. package/dist/components/ComboBox/ComboBoxTrigger.d.ts.map +1 -0
  51. package/dist/components/ComboBox/ComboBoxTrigger.js +9 -0
  52. package/dist/components/ComboBox/ComboBoxTrigger.js.map +1 -0
  53. package/dist/components/ComboBox/ComboBoxValue.d.ts +4 -0
  54. package/dist/components/ComboBox/ComboBoxValue.d.ts.map +1 -0
  55. package/dist/components/ComboBox/ComboBoxValue.js +7 -0
  56. package/dist/components/ComboBox/ComboBoxValue.js.map +1 -0
  57. package/dist/components/InputGroup/InputGroup.d.ts +4 -0
  58. package/dist/components/InputGroup/InputGroup.d.ts.map +1 -0
  59. package/dist/components/InputGroup/InputGroup.js +8 -0
  60. package/dist/components/InputGroup/InputGroup.js.map +1 -0
  61. package/dist/components/InputGroup/InputGroupAddon.d.ts +8 -0
  62. package/dist/components/InputGroup/InputGroupAddon.d.ts.map +1 -0
  63. package/dist/components/InputGroup/InputGroupAddon.js +23 -0
  64. package/dist/components/InputGroup/InputGroupAddon.js.map +1 -0
  65. package/dist/components/InputGroup/InputGroupButton.d.ts +9 -0
  66. package/dist/components/InputGroup/InputGroupButton.d.ts.map +1 -0
  67. package/dist/components/InputGroup/InputGroupButton.js +29 -0
  68. package/dist/components/InputGroup/InputGroupButton.js.map +1 -0
  69. package/dist/components/InputGroup/InputGroupInput.d.ts +4 -0
  70. package/dist/components/InputGroup/InputGroupInput.d.ts.map +1 -0
  71. package/dist/components/InputGroup/InputGroupInput.js +9 -0
  72. package/dist/components/InputGroup/InputGroupInput.js.map +1 -0
  73. package/dist/components/InputGroup/InputGroupText.d.ts +4 -0
  74. package/dist/components/InputGroup/InputGroupText.d.ts.map +1 -0
  75. package/dist/components/InputGroup/InputGroupText.js +8 -0
  76. package/dist/components/InputGroup/InputGroupText.js.map +1 -0
  77. package/dist/components/InputGroup/InputGroupTextArea.d.ts +4 -0
  78. package/dist/components/InputGroup/InputGroupTextArea.d.ts.map +1 -0
  79. package/dist/components/InputGroup/InputGroupTextArea.js +9 -0
  80. package/dist/components/InputGroup/InputGroupTextArea.js.map +1 -0
  81. package/package.json +2 -1
  82. package/src/components/ComboBox/ComboBox.stories.tsx +31 -0
  83. package/src/components/ComboBox/ComboBox.tsx +41 -0
  84. package/src/components/ComboBox/ComboBoxChips.tsx +69 -0
  85. package/src/components/ComboBox/ComboBoxClear.tsx +23 -0
  86. package/src/components/ComboBox/ComboBoxCollection.tsx +7 -0
  87. package/src/components/ComboBox/ComboBoxContent.tsx +39 -0
  88. package/src/components/ComboBox/ComboBoxEmpty.tsx +18 -0
  89. package/src/components/ComboBox/ComboBoxGroup.tsx +9 -0
  90. package/src/components/ComboBox/ComboBoxInput.tsx +51 -0
  91. package/src/components/ComboBox/ComboBoxItem.tsx +28 -0
  92. package/src/components/ComboBox/ComboBoxLabel.tsx +15 -0
  93. package/src/components/ComboBox/ComboBoxList.tsx +18 -0
  94. package/src/components/ComboBox/ComboBoxSeparator.tsx +15 -0
  95. package/src/components/ComboBox/ComboBoxTrigger.tsx +23 -0
  96. package/src/components/ComboBox/ComboBoxValue.tsx +7 -0
  97. package/src/components/InputGroup/InputGroup.stories.tsx +27 -0
  98. package/src/components/InputGroup/InputGroup.tsx +19 -0
  99. package/src/components/InputGroup/InputGroupAddon.tsx +42 -0
  100. package/src/components/InputGroup/InputGroupButton.tsx +50 -0
  101. package/src/components/InputGroup/InputGroupInput.tsx +20 -0
  102. package/src/components/InputGroup/InputGroupText.tsx +17 -0
  103. package/src/components/InputGroup/InputGroupTextArea.tsx +20 -0
@@ -0,0 +1,4 @@
1
+ import * as React from 'react';
2
+ declare const InputGroupText: ({ className, ...props }: React.ComponentProps<"span">) => import("react/jsx-runtime").JSX.Element;
3
+ export { InputGroupText };
4
+ //# sourceMappingURL=InputGroupText.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InputGroupText.d.ts","sourceRoot":"","sources":["../../../src/components/InputGroup/InputGroupText.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,QAAA,MAAM,cAAc,GAAI,yBAAyB,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,4CAU5E,CAAC;AAEF,OAAO,EAAE,cAAc,EAAE,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from 'react';
3
+ import { cn } from '#utils';
4
+ const InputGroupText = ({ className, ...props }) => {
5
+ return (_jsx("span", { className: cn("text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4", className), ...props }));
6
+ };
7
+ export { InputGroupText };
8
+ //# sourceMappingURL=InputGroupText.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InputGroupText.js","sourceRoot":"","sources":["../../../src/components/InputGroup/InputGroupText.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE5B,MAAM,cAAc,GAAG,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAgC,EAAE,EAAE;IAC/E,OAAO,CACL,eACE,SAAS,EAAE,EAAE,CACX,wHAAwH,EACxH,SAAS,CACV,KACG,KAAK,GACT,CACH,CAAC;AACJ,CAAC,CAAC;AAEF,OAAO,EAAE,cAAc,EAAE,CAAC"}
@@ -0,0 +1,4 @@
1
+ import * as React from 'react';
2
+ declare const InputGroupTextArea: ({ className, ...props }: React.ComponentProps<"textarea">) => import("react/jsx-runtime").JSX.Element;
3
+ export { InputGroupTextArea };
4
+ //# sourceMappingURL=InputGroupTextArea.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InputGroupTextArea.d.ts","sourceRoot":"","sources":["../../../src/components/InputGroup/InputGroupTextArea.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,QAAA,MAAM,kBAAkB,GAAI,yBAAyB,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC,4CAWpF,CAAC;AAEF,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from 'react';
3
+ import { cn } from '#utils';
4
+ import { TextArea } from "../TextArea/TextArea.js";
5
+ const InputGroupTextArea = ({ className, ...props }) => {
6
+ return (_jsx(TextArea, { className: cn('flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent', className), "data-slot": "input-group-control", ...props }));
7
+ };
8
+ export { InputGroupTextArea };
9
+ //# sourceMappingURL=InputGroupTextArea.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InputGroupTextArea.js","sourceRoot":"","sources":["../../../src/components/InputGroup/InputGroupTextArea.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE5B,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAA0B,CAAC;AAEpD,MAAM,kBAAkB,GAAG,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAoC,EAAE,EAAE;IACvF,OAAO,CACL,KAAC,QAAQ,IACP,SAAS,EAAE,EAAE,CACX,mMAAmM,EACnM,SAAS,CACV,eACS,qBAAqB,KAC3B,KAAK,GACT,CACH,CAAC;AACJ,CAAC,CAAC;AAEF,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@douglasneuroinformatics/libui",
3
3
  "type": "module",
4
- "version": "6.2.1",
4
+ "version": "6.3.0",
5
5
  "packageManager": "pnpm@10.7.1",
6
6
  "description": "Generic UI components for DNP projects, built using React and Tailwind CSS",
7
7
  "author": "Joshua Unrau",
@@ -97,6 +97,7 @@
97
97
  "zod": "^3.25.x"
98
98
  },
99
99
  "dependencies": {
100
+ "@base-ui/react": "^1.2.0",
100
101
  "@douglasneuroinformatics/libjs": "^3.0.2",
101
102
  "@douglasneuroinformatics/libui-form-types": "^0.11.0",
102
103
  "@radix-ui/react-accordion": "^1.2.3",
@@ -0,0 +1,31 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+
3
+ import { ComboBox } from './ComboBox.tsx';
4
+
5
+ type Story = StoryObj<typeof ComboBox>;
6
+
7
+ const frameworks: string[] = ['Next.js', 'SvelteKit', 'Nuxt.js', 'Remix', 'Astro'];
8
+
9
+ export default {
10
+ args: {
11
+ children: (
12
+ <ComboBox items={frameworks}>
13
+ <ComboBox.Input placeholder="Select a framework" />
14
+ <ComboBox.Content>
15
+ <ComboBox.Empty>No items found.</ComboBox.Empty>
16
+ <ComboBox.List>
17
+ {(item: string) => (
18
+ <ComboBox.Item key={item} value={item}>
19
+ {item}
20
+ </ComboBox.Item>
21
+ )}
22
+ </ComboBox.List>
23
+ </ComboBox.Content>
24
+ </ComboBox>
25
+ )
26
+ },
27
+ component: ComboBox,
28
+ tags: ['autodocs']
29
+ } as Meta<typeof ComboBox>;
30
+
31
+ export const Default: Story = {};
@@ -0,0 +1,41 @@
1
+ import * as React from 'react';
2
+
3
+ import { Combobox as ComboboxPrimitive } from '@base-ui/react';
4
+
5
+ import { ComboboxChip, ComboboxChips, ComboboxChipsInput } from './ComboBoxChips.tsx';
6
+ import { ComboboxClear } from './ComboBoxClear.tsx';
7
+ import { ComboboxCollection } from './ComboBoxCollection.tsx';
8
+ import { ComboboxContent } from './ComboBoxContent.tsx';
9
+ import { ComboboxEmpty } from './ComboBoxEmpty.tsx';
10
+ import { ComboboxGroup } from './ComboBoxGroup.tsx';
11
+ import { ComboboxInput } from './ComboBoxInput.tsx';
12
+ import { ComboboxItem } from './ComboBoxItem.tsx';
13
+ import { ComboboxLabel } from './ComboBoxLabel.tsx';
14
+ import { ComboboxList } from './ComboBoxList.tsx';
15
+ import { ComboboxSeparator } from './ComboBoxSeparator.tsx';
16
+ import { ComboboxTrigger } from './ComboBoxTrigger.tsx';
17
+ import { ComboboxValue } from './ComboBoxValue.tsx';
18
+
19
+ function useComboboxAnchor() {
20
+ return React.useRef<HTMLDivElement | null>(null);
21
+ }
22
+
23
+ export { useComboboxAnchor };
24
+
25
+ export const ComboBox = Object.assign(ComboboxPrimitive.Root.bind(null), {
26
+ Chip: ComboboxChip,
27
+ Chips: ComboboxChips,
28
+ ChipsInput: ComboboxChipsInput,
29
+ Clear: ComboboxClear,
30
+ Collection: ComboboxCollection,
31
+ Content: ComboboxContent,
32
+ Empty: ComboboxEmpty,
33
+ Group: ComboboxGroup,
34
+ Input: ComboboxInput,
35
+ Item: ComboboxItem,
36
+ Label: ComboboxLabel,
37
+ List: ComboboxList,
38
+ Separator: ComboboxSeparator,
39
+ Trigger: ComboboxTrigger,
40
+ Value: ComboboxValue
41
+ });
@@ -0,0 +1,69 @@
1
+ import * as React from 'react';
2
+
3
+ import { Combobox as ComboboxPrimitive } from '@base-ui/react';
4
+ import { XIcon } from 'lucide-react';
5
+
6
+ import { cn } from '#utils';
7
+
8
+ import { Button } from '../Button/Button.tsx';
9
+
10
+ const ComboboxChips = ({
11
+ className,
12
+ ...props
13
+ }: ComboboxPrimitive.Chips.Props & React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips>) => {
14
+ return (
15
+ <ComboboxPrimitive.Chips
16
+ className={cn(
17
+ '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-8 flex-wrap items-center gap-1 rounded-lg border bg-transparent bg-clip-padding px-2.5 py-1 text-sm transition-colors focus-within:ring-3 has-aria-invalid:ring-3 has-data-[slot=combobox-chip]:px-1',
18
+ className
19
+ )}
20
+ data-slot="combobox-chips"
21
+ {...props}
22
+ />
23
+ );
24
+ };
25
+
26
+ const ComboboxChip = ({
27
+ children,
28
+ className,
29
+ showRemove = true,
30
+ ...props
31
+ }: ComboboxPrimitive.Chip.Props & {
32
+ showRemove?: boolean;
33
+ }) => {
34
+ return (
35
+ <ComboboxPrimitive.Chip
36
+ className={cn(
37
+ 'bg-muted text-foreground flex h-[calc(--spacing(5.25))] w-fit items-center justify-center gap-1 rounded-sm px-1.5 text-xs font-medium whitespace-nowrap has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 has-data-[slot=combobox-chip-remove]:pr-0',
38
+ className
39
+ )}
40
+ data-slot="combobox-chip"
41
+ {...props}
42
+ >
43
+ {children}
44
+ {showRemove && (
45
+ <ComboboxPrimitive.ChipRemove
46
+ className="-ml-1 opacity-50 hover:opacity-100"
47
+ data-slot="combobox-chip-remove"
48
+ render={
49
+ <Button size="sm" variant="ghost">
50
+ <XIcon className="pointer-events-none" />
51
+ </Button>
52
+ }
53
+ />
54
+ )}
55
+ </ComboboxPrimitive.Chip>
56
+ );
57
+ };
58
+
59
+ const ComboboxChipsInput = ({ className, ...props }: ComboboxPrimitive.Input.Props) => {
60
+ return (
61
+ <ComboboxPrimitive.Input
62
+ className={cn('min-w-16 flex-1 outline-none', className)}
63
+ data-slot="combobox-chip-input"
64
+ {...props}
65
+ />
66
+ );
67
+ };
68
+
69
+ export { ComboboxChip, ComboboxChips, ComboboxChipsInput };
@@ -0,0 +1,23 @@
1
+ import { Combobox as ComboboxPrimitive } from '@base-ui/react';
2
+ import { XIcon } from 'lucide-react';
3
+
4
+ import { cn } from '#utils';
5
+
6
+ import { InputGroupButton } from '../InputGroup/InputGroupButton.tsx';
7
+
8
+ const ComboboxClear = ({ className, ...props }: ComboboxPrimitive.Clear.Props) => {
9
+ return (
10
+ <ComboboxPrimitive.Clear
11
+ className={cn(className)}
12
+ data-slot="combobox-clear"
13
+ {...props}
14
+ render={
15
+ <InputGroupButton size="icon-xs" variant="ghost">
16
+ <XIcon className="pointer-events-none" />
17
+ </InputGroupButton>
18
+ }
19
+ />
20
+ );
21
+ };
22
+
23
+ export { ComboboxClear };
@@ -0,0 +1,7 @@
1
+ import { Combobox as ComboboxPrimitive } from '@base-ui/react';
2
+
3
+ const ComboboxCollection = ({ ...props }: ComboboxPrimitive.Collection.Props) => {
4
+ return <ComboboxPrimitive.Collection data-slot="combobox-collection" {...props} />;
5
+ };
6
+
7
+ export { ComboboxCollection };
@@ -0,0 +1,39 @@
1
+ import { Combobox as ComboboxPrimitive } from '@base-ui/react';
2
+
3
+ import { cn } from '#utils';
4
+
5
+ const ComboboxContent = ({
6
+ align = 'start',
7
+ alignOffset = 0,
8
+ anchor,
9
+ className,
10
+ side = 'bottom',
11
+ sideOffset = 6,
12
+ ...props
13
+ }: ComboboxPrimitive.Popup.Props &
14
+ Pick<ComboboxPrimitive.Positioner.Props, 'align' | 'alignOffset' | 'anchor' | 'side' | 'sideOffset'>) => {
15
+ return (
16
+ <ComboboxPrimitive.Portal>
17
+ <ComboboxPrimitive.Positioner
18
+ align={align}
19
+ alignOffset={alignOffset}
20
+ anchor={anchor}
21
+ className="isolate z-50"
22
+ side={side}
23
+ sideOffset={sideOffset}
24
+ >
25
+ <ComboboxPrimitive.Popup
26
+ className={cn(
27
+ '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 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden rounded-lg shadow-md ring-1 duration-100 data-[chips=true]:min-w-(--anchor-width) *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:shadow-none',
28
+ className
29
+ )}
30
+ data-chips={!!anchor}
31
+ data-slot="combobox-content"
32
+ {...props}
33
+ />
34
+ </ComboboxPrimitive.Positioner>
35
+ </ComboboxPrimitive.Portal>
36
+ );
37
+ };
38
+
39
+ export { ComboboxContent };
@@ -0,0 +1,18 @@
1
+ import { Combobox as ComboboxPrimitive } from '@base-ui/react';
2
+
3
+ import { cn } from '#utils';
4
+
5
+ const ComboboxEmpty = ({ className, ...props }: ComboboxPrimitive.Empty.Props) => {
6
+ return (
7
+ <ComboboxPrimitive.Empty
8
+ className={cn(
9
+ 'text-muted-foreground hidden w-full justify-center py-2 text-center text-sm group-data-empty/combobox-content:flex',
10
+ className
11
+ )}
12
+ data-slot="combobox-empty"
13
+ {...props}
14
+ />
15
+ );
16
+ };
17
+
18
+ export { ComboboxEmpty };
@@ -0,0 +1,9 @@
1
+ import { Combobox as ComboboxPrimitive } from '@base-ui/react';
2
+
3
+ import { cn } from '#utils';
4
+
5
+ const ComboboxGroup = ({ className, ...props }: ComboboxPrimitive.Group.Props) => {
6
+ return <ComboboxPrimitive.Group className={cn(className)} data-slot="combobox-group" {...props} />;
7
+ };
8
+
9
+ export { ComboboxGroup };
@@ -0,0 +1,51 @@
1
+ import { Combobox as ComboboxPrimitive } from '@base-ui/react';
2
+ import { ChevronDownIcon } from 'lucide-react';
3
+
4
+ import { cn } from '#utils';
5
+
6
+ import { InputGroup } from '../InputGroup/InputGroup.tsx';
7
+ import { InputGroupAddon } from '../InputGroup/InputGroupAddon.tsx';
8
+ import { InputGroupButton } from '../InputGroup/InputGroupButton.tsx';
9
+ import { InputGroupInput } from '../InputGroup/InputGroupInput.tsx';
10
+ import { ComboboxClear } from './ComboBoxClear.tsx';
11
+ import { ComboboxTrigger } from './ComboBoxTrigger.tsx';
12
+
13
+ const ComboboxInput = ({
14
+ children,
15
+ className,
16
+ disabled = false,
17
+ showClear = false,
18
+ showTrigger = true,
19
+ ...props
20
+ }: ComboboxPrimitive.Input.Props & {
21
+ showClear?: boolean;
22
+ showTrigger?: boolean;
23
+ }) => {
24
+ return (
25
+ <InputGroup className={cn('w-auto', className)}>
26
+ <ComboboxPrimitive.Input render={<InputGroupInput disabled={disabled} />} {...props} />
27
+ <InputGroupAddon align="inline-end">
28
+ {showTrigger && (
29
+ <ComboboxTrigger
30
+ disabled={disabled}
31
+ /* Now this works because ComboboxTrigger is expecting 'render' */
32
+ render={
33
+ <InputGroupButton
34
+ className="group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent"
35
+ data-slot="input-group-button"
36
+ size="icon-xs"
37
+ variant="ghost"
38
+ >
39
+ <ChevronDownIcon className="text-muted-foreground pointer-events-none size-4" />
40
+ </InputGroupButton>
41
+ }
42
+ />
43
+ )}
44
+ {showClear && <ComboboxClear disabled={disabled} />}
45
+ </InputGroupAddon>
46
+ {children}
47
+ </InputGroup>
48
+ );
49
+ };
50
+
51
+ export { ComboboxInput };
@@ -0,0 +1,28 @@
1
+ import { Combobox as ComboboxPrimitive } from '@base-ui/react';
2
+ import { CheckIcon } from 'lucide-react';
3
+
4
+ import { cn } from '#utils';
5
+
6
+ const ComboboxItem = ({ children, className, ...props }: ComboboxPrimitive.Item.Props) => {
7
+ return (
8
+ <ComboboxPrimitive.Item
9
+ className={cn(
10
+ "data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground relative flex w-full cursor-default items-center gap-2 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
11
+ className
12
+ )}
13
+ data-slot="combobox-item"
14
+ {...props}
15
+ >
16
+ {children}
17
+ <ComboboxPrimitive.ItemIndicator
18
+ render={
19
+ <span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center">
20
+ <CheckIcon className="pointer-events-none" />
21
+ </span>
22
+ }
23
+ />
24
+ </ComboboxPrimitive.Item>
25
+ );
26
+ };
27
+
28
+ export { ComboboxItem };
@@ -0,0 +1,15 @@
1
+ import { Combobox as ComboboxPrimitive } from '@base-ui/react';
2
+
3
+ import { cn } from '#utils';
4
+
5
+ const ComboboxLabel = ({ className, ...props }: ComboboxPrimitive.GroupLabel.Props) => {
6
+ return (
7
+ <ComboboxPrimitive.GroupLabel
8
+ className={cn('text-muted-foreground px-2 py-1.5 text-xs', className)}
9
+ data-slot="combobox-label"
10
+ {...props}
11
+ />
12
+ );
13
+ };
14
+
15
+ export { ComboboxLabel };
@@ -0,0 +1,18 @@
1
+ import { Combobox as ComboboxPrimitive } from '@base-ui/react';
2
+
3
+ import { cn } from '#utils';
4
+
5
+ const ComboboxList = ({ className, ...props }: ComboboxPrimitive.List.Props) => {
6
+ return (
7
+ <ComboboxPrimitive.List
8
+ className={cn(
9
+ 'no-scrollbar max-h-[min(calc(--spacing(72)---spacing(9)),calc(var(--available-height)---spacing(9)))] scroll-py-1 overflow-y-auto overscroll-contain p-1 data-empty:p-0',
10
+ className
11
+ )}
12
+ data-slot="combobox-list"
13
+ {...props}
14
+ />
15
+ );
16
+ };
17
+
18
+ export { ComboboxList };
@@ -0,0 +1,15 @@
1
+ import { Combobox as ComboboxPrimitive } from '@base-ui/react';
2
+
3
+ import { cn } from '#utils';
4
+
5
+ const ComboboxSeparator = ({ className, ...props }: ComboboxPrimitive.Separator.Props) => {
6
+ return (
7
+ <ComboboxPrimitive.Separator
8
+ className={cn('bg-border -mx-1 my-1 h-px', className)}
9
+ data-slot="combobox-separator"
10
+ {...props}
11
+ />
12
+ );
13
+ };
14
+
15
+ export { ComboboxSeparator };
@@ -0,0 +1,23 @@
1
+ import { Combobox as ComboboxPrimitive } from '@base-ui/react';
2
+ import { ChevronDownIcon } from 'lucide-react';
3
+
4
+ import { cn } from '#utils';
5
+
6
+ const ComboboxTrigger = ({ children, className, render, ...props }: ComboboxPrimitive.Trigger.Props) => {
7
+ return (
8
+ <ComboboxPrimitive.Trigger
9
+ className={cn("[&_svg:not([class*='size-'])]:size-4", className)}
10
+ data-slot="combobox-trigger"
11
+ render={render} // This allows it to "become" the InputGroupButton
12
+ {...props}
13
+ >
14
+ {children}
15
+ {/* We move the icon here so it's always included,
16
+ unless you prefer passing it as children manually.
17
+ */}
18
+ {!render && <ChevronDownIcon className="text-muted-foreground pointer-events-none size-4" />}
19
+ </ComboboxPrimitive.Trigger>
20
+ );
21
+ };
22
+
23
+ export { ComboboxTrigger };
@@ -0,0 +1,7 @@
1
+ import { Combobox as ComboboxPrimitive } from '@base-ui/react';
2
+
3
+ const ComboboxValue = ({ ...props }: ComboboxPrimitive.Value.Props) => {
4
+ return <ComboboxPrimitive.Value data-slot="combobox-value" {...props} />;
5
+ };
6
+
7
+ export { ComboboxValue };
@@ -0,0 +1,27 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { SearchIcon } from 'lucide-react';
3
+
4
+ import { InputGroup } from './InputGroup.tsx';
5
+ import { InputGroupAddon } from './InputGroupAddon.tsx';
6
+ import { InputGroupInput } from './InputGroupInput.tsx';
7
+
8
+ type Story = StoryObj<typeof InputGroup>;
9
+
10
+ export default {
11
+ args: {
12
+ children: (
13
+ <>
14
+ <InputGroup>
15
+ <InputGroupInput placeholder="Search..." />
16
+ <InputGroupAddon>
17
+ <SearchIcon />
18
+ </InputGroupAddon>
19
+ </InputGroup>
20
+ </>
21
+ )
22
+ },
23
+ component: InputGroup,
24
+ tags: ['autodocs']
25
+ } as Meta<typeof InputGroup>;
26
+
27
+ export const Default: Story = {};
@@ -0,0 +1,19 @@
1
+ import * as React from 'react';
2
+
3
+ import { cn } from '#utils';
4
+
5
+ const InputGroup = ({ className, ...props }: React.ComponentProps<'div'>) => {
6
+ return (
7
+ <div
8
+ className={cn(
9
+ '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 has-disabled:bg-input/50 dark:has-disabled:bg-input/80 group/input-group relative flex h-8 w-full min-w-0 items-center rounded-lg border transition-colors outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-3 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-[>textarea]:h-auto 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',
10
+ className
11
+ )}
12
+ data-slot="input-group"
13
+ role="group"
14
+ {...props}
15
+ />
16
+ );
17
+ };
18
+
19
+ export { InputGroup };
@@ -0,0 +1,42 @@
1
+ import * as React from 'react';
2
+
3
+ import { cva } from 'class-variance-authority';
4
+ import type { VariantProps } from 'class-variance-authority';
5
+
6
+ import { cn } from '#utils';
7
+
8
+ const inputGroupAddonVariants = cva(
9
+ "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",
10
+ {
11
+ defaultVariants: {
12
+ align: 'inline-start'
13
+ },
14
+ variants: {
15
+ align: {
16
+ 'block-end': 'px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2 order-last w-full justify-start',
17
+ 'block-start':
18
+ 'px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2 order-first w-full justify-start',
19
+ 'inline-end': 'pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem] order-last',
20
+ 'inline-start': 'pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem] order-first'
21
+ }
22
+ }
23
+ }
24
+ );
25
+
26
+ //ensure that Group encapsulates the input and the addon
27
+ const InputGroupAddon = ({
28
+ align = 'inline-start',
29
+ className,
30
+ ...props
31
+ }: React.ComponentProps<'div'> & VariantProps<typeof inputGroupAddonVariants>) => {
32
+ return (
33
+ <div
34
+ className={cn(inputGroupAddonVariants({ align }), className)}
35
+ data-align={align}
36
+ data-slot="input-group-addon"
37
+ {...props}
38
+ />
39
+ );
40
+ };
41
+
42
+ export { InputGroupAddon };
@@ -0,0 +1,50 @@
1
+ import * as React from 'react';
2
+
3
+ import { cva } from 'class-variance-authority';
4
+ import type { VariantProps } from 'class-variance-authority';
5
+
6
+ import { cn } from '#utils';
7
+
8
+ import { Button } from '../Button/Button.tsx';
9
+
10
+ const inputGroupButtonVariants = cva('gap-2 text-sm flex items-center shadow-none', {
11
+ defaultVariants: {
12
+ size: 'xs'
13
+ },
14
+ variants: {
15
+ size: {
16
+ 'icon-sm': 'size-8 p-0 has-[>svg]:p-0',
17
+ 'icon-xs': 'size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0',
18
+ sm: '',
19
+ xs: "h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5"
20
+ }
21
+ }
22
+ });
23
+
24
+ const buttonSizeMap = {
25
+ 'icon-sm': 'icon',
26
+ 'icon-xs': 'icon',
27
+ sm: 'sm',
28
+ xs: 'sm'
29
+ } as const;
30
+
31
+ const InputGroupButton = ({
32
+ className,
33
+ size = 'xs',
34
+ type = 'button',
35
+ variant = 'ghost',
36
+ ...props
37
+ }: Omit<React.ComponentProps<typeof Button>, 'size'> & VariantProps<typeof inputGroupButtonVariants>) => {
38
+ return (
39
+ <Button
40
+ className={cn(inputGroupButtonVariants({ size }), className)}
41
+ data-size={size}
42
+ size={buttonSizeMap[size ?? 'xs']}
43
+ type={type}
44
+ variant={variant}
45
+ {...props}
46
+ />
47
+ );
48
+ };
49
+
50
+ export { InputGroupButton };
@@ -0,0 +1,20 @@
1
+ import * as React from 'react';
2
+
3
+ import { cn } from '#utils';
4
+
5
+ import { Input } from '../Input/Input.tsx';
6
+
7
+ const InputGroupInput = ({ className, ...props }: React.ComponentProps<'input'>) => {
8
+ return (
9
+ <Input
10
+ className={cn(
11
+ 'flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',
12
+ className
13
+ )}
14
+ data-slot="input-group-control"
15
+ {...props}
16
+ />
17
+ );
18
+ };
19
+
20
+ export { InputGroupInput };