periplo-ui 4.5.0 → 4.6.1

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 (27) hide show
  1. package/dist/components/Checkbox/Checkbox.d.ts +4 -4
  2. package/dist/components/Checkbox/Checkbox.js +3 -1
  3. package/dist/components/Checkbox/Checkbox.js.map +1 -1
  4. package/dist/components/Combobox/Combobox.js +1 -2
  5. package/dist/components/Combobox/Combobox.js.map +1 -1
  6. package/dist/components/DataTable/DataTable.d.ts +1 -1
  7. package/dist/components/DataTable/DataTable.js +49 -18
  8. package/dist/components/DataTable/DataTable.js.map +1 -1
  9. package/dist/components/DataTable/components/DataTableBody.d.ts +12 -3
  10. package/dist/components/DataTable/components/DataTableBody.js +43 -8
  11. package/dist/components/DataTable/components/DataTableBody.js.map +1 -1
  12. package/dist/components/DataTable/components/DataTableGroupRow.d.ts +10 -0
  13. package/dist/components/DataTable/components/DataTableGroupRow.js +25 -0
  14. package/dist/components/DataTable/components/DataTableGroupRow.js.map +1 -0
  15. package/dist/components/DataTable/components/DataTableGroupRowSelector.d.ts +9 -0
  16. package/dist/components/DataTable/components/DataTableGroupRowSelector.js +32 -0
  17. package/dist/components/DataTable/components/DataTableGroupRowSelector.js.map +1 -0
  18. package/dist/components/DataTable/components/DataTableSelectHeader.js +16 -10
  19. package/dist/components/DataTable/components/DataTableSelectHeader.js.map +1 -1
  20. package/dist/components/DataTable/hooks/useGroupExpansion.d.ts +19 -0
  21. package/dist/components/DataTable/hooks/useGroupExpansion.js +34 -0
  22. package/dist/components/DataTable/hooks/useGroupExpansion.js.map +1 -0
  23. package/dist/components/DataTable/types.d.ts +27 -6
  24. package/dist/components/DataTable/utils.d.ts +6 -0
  25. package/dist/components/DataTable/utils.js +15 -0
  26. package/dist/components/DataTable/utils.js.map +1 -0
  27. package/package.json +1 -1
@@ -3,8 +3,8 @@ import * as React from 'react';
3
3
  type CheckboxProps = {
4
4
  /** Additional CSS classes to be applied to the checkbox */
5
5
  className?: string;
6
- /** The controlled checked state of the checkbox */
7
- checked?: boolean;
6
+ /** The controlled checked state of the checkbox. `'indeterminate'` renders a partial (dash) state. */
7
+ checked?: CheckboxPrimitive.CheckedState;
8
8
  /** The default checked state when initially rendered */
9
9
  defaultChecked?: boolean;
10
10
  /** Whether the checkbox is disabled */
@@ -42,8 +42,8 @@ type CheckboxProps = {
42
42
  declare const Checkbox: React.ForwardRefExoticComponent<{
43
43
  /** Additional CSS classes to be applied to the checkbox */
44
44
  className?: string;
45
- /** The controlled checked state of the checkbox */
46
- checked?: boolean;
45
+ /** The controlled checked state of the checkbox. `'indeterminate'` renders a partial (dash) state. */
46
+ checked?: CheckboxPrimitive.CheckedState;
47
47
  /** The default checked state when initially rendered */
48
48
  defaultChecked?: boolean;
49
49
  /** Whether the checkbox is disabled */
@@ -1,5 +1,6 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { Check } from '@phosphor-icons/react/dist/ssr/Check';
3
+ import { Minus } from '@phosphor-icons/react/dist/ssr/Minus';
3
4
  import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
4
5
  import * as React from 'react';
5
6
  import { cn } from '../../lib/utils.js';
@@ -14,11 +15,12 @@ const Checkbox = React.forwardRef(
14
15
  "focus-visible:ring-2 focus-visible:ring-neutral-500 focus-visible:ring-offset-2 focus-visible:outline-none",
15
16
  "disabled:cursor-not-allowed disabled:bg-neutral-100 disabled:opacity-50",
16
17
  "data-[state=checked]:border-neutral-500 data-[state=checked]:bg-neutral-500",
18
+ "data-[state=indeterminate]:border-neutral-500 data-[state=indeterminate]:bg-neutral-500",
17
19
  "hover:border-neutral-500 disabled:hover:border-neutral-300 data-[state=checked]:hover:border-neutral-500",
18
20
  className
19
21
  ),
20
22
  ...props,
21
- children: /* @__PURE__ */ jsx(CheckboxPrimitive.Indicator, { className: cn("absolute inset-0 flex items-center justify-center"), children: /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 text-white" }) })
23
+ children: /* @__PURE__ */ jsx(CheckboxPrimitive.Indicator, { className: cn("absolute inset-0 flex items-center justify-center"), children: props.checked === "indeterminate" ? /* @__PURE__ */ jsx(Minus, { className: "h-3.5 w-3.5 text-white", weight: "bold" }) : /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 text-white" }) })
22
24
  }
23
25
  )
24
26
  );
@@ -1 +1 @@
1
- {"version":3,"file":"Checkbox.js","sources":["../../../src/components/Checkbox/Checkbox.tsx"],"sourcesContent":["import { Check } from '@phosphor-icons/react/dist/ssr/Check'\nimport * as CheckboxPrimitive from '@radix-ui/react-checkbox'\nimport * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\ntype CheckboxProps = {\n /** Additional CSS classes to be applied to the checkbox */\n className?: string\n /** The controlled checked state of the checkbox */\n checked?: boolean\n /** The default checked state when initially rendered */\n defaultChecked?: boolean\n /** Whether the checkbox is disabled */\n disabled?: boolean\n /** Whether the checkbox is required in a form */\n required?: boolean\n /** The name of the checkbox when used in a form */\n name?: string\n /** The value of the checkbox when used in a form */\n value?: string\n /** Handler called when the checked state changes */\n onCheckedChange?: (checked: boolean) => void\n} & React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>\n\n/**\n * A controlled checkbox component built on top of Radix UI Checkbox.\n *\n * @example\n * ```tsx\n * // Basic usage\n * <Checkbox />\n *\n * // With controlled state\n * <Checkbox checked={checked} onCheckedChange={setChecked} />\n *\n * // With form integration\n * <Checkbox name=\"terms\" required />\n *\n * // With label\n * <div className=\"flex items-center gap-2\">\n * <Checkbox id=\"terms\" />\n * <label htmlFor=\"terms\">Accept terms</label>\n * </div>\n * ```\n */\nconst Checkbox = React.forwardRef<React.ComponentRef<typeof CheckboxPrimitive.Root>, CheckboxProps>(\n ({ className, ...props }, ref) => (\n <CheckboxPrimitive.Root\n ref={ref}\n className={cn(\n 'peer relative h-5 w-5 shrink-0 rounded-[5px] border border-neutral-300 transition-colors',\n 'focus-visible:ring-2 focus-visible:ring-neutral-500 focus-visible:ring-offset-2 focus-visible:outline-none',\n 'disabled:cursor-not-allowed disabled:bg-neutral-100 disabled:opacity-50',\n 'data-[state=checked]:border-neutral-500 data-[state=checked]:bg-neutral-500',\n 'hover:border-neutral-500 disabled:hover:border-neutral-300 data-[state=checked]:hover:border-neutral-500',\n className,\n )}\n {...props}\n >\n <CheckboxPrimitive.Indicator className={cn('absolute inset-0 flex items-center justify-center')}>\n <Check className=\"h-3.5 w-3.5 text-white\" />\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n ),\n)\n\nCheckbox.displayName = 'Checkbox'\n\nexport type { CheckboxProps }\nexport { Checkbox }\n"],"names":[],"mappings":";;;;;;AA8CA,MAAM,WAAW,KAAA,CAAM,UAAA;AAAA,EACrB,CAAC,EAAE,SAAA,EAAW,GAAG,KAAA,IAAS,GAAA,qBACxB,GAAA;AAAA,IAAC,iBAAA,CAAkB,IAAA;AAAA,IAAlB;AAAA,MACC,GAAA;AAAA,MACA,SAAA,EAAW,EAAA;AAAA,QACT,0FAAA;AAAA,QACA,4GAAA;AAAA,QACA,yEAAA;AAAA,QACA,6EAAA;AAAA,QACA,0GAAA;AAAA,QACA;AAAA,OACF;AAAA,MACC,GAAG,KAAA;AAAA,MAEJ,QAAA,kBAAA,GAAA,CAAC,iBAAA,CAAkB,SAAA,EAAlB,EAA4B,SAAA,EAAW,EAAA,CAAG,mDAAmD,CAAA,EAC5F,QAAA,kBAAA,GAAA,CAAC,KAAA,EAAA,EAAM,SAAA,EAAU,wBAAA,EAAyB,CAAA,EAC5C;AAAA;AAAA;AAGN;AAEA,QAAA,CAAS,WAAA,GAAc,UAAA;;;;"}
1
+ {"version":3,"file":"Checkbox.js","sources":["../../../src/components/Checkbox/Checkbox.tsx"],"sourcesContent":["import { Check } from '@phosphor-icons/react/dist/ssr/Check'\nimport { Minus } from '@phosphor-icons/react/dist/ssr/Minus'\nimport * as CheckboxPrimitive from '@radix-ui/react-checkbox'\nimport * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\ntype CheckboxProps = {\n /** Additional CSS classes to be applied to the checkbox */\n className?: string\n /** The controlled checked state of the checkbox. `'indeterminate'` renders a partial (dash) state. */\n checked?: CheckboxPrimitive.CheckedState\n /** The default checked state when initially rendered */\n defaultChecked?: boolean\n /** Whether the checkbox is disabled */\n disabled?: boolean\n /** Whether the checkbox is required in a form */\n required?: boolean\n /** The name of the checkbox when used in a form */\n name?: string\n /** The value of the checkbox when used in a form */\n value?: string\n /** Handler called when the checked state changes */\n onCheckedChange?: (checked: boolean) => void\n} & React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>\n\n/**\n * A controlled checkbox component built on top of Radix UI Checkbox.\n *\n * @example\n * ```tsx\n * // Basic usage\n * <Checkbox />\n *\n * // With controlled state\n * <Checkbox checked={checked} onCheckedChange={setChecked} />\n *\n * // With form integration\n * <Checkbox name=\"terms\" required />\n *\n * // With label\n * <div className=\"flex items-center gap-2\">\n * <Checkbox id=\"terms\" />\n * <label htmlFor=\"terms\">Accept terms</label>\n * </div>\n * ```\n */\nconst Checkbox = React.forwardRef<React.ComponentRef<typeof CheckboxPrimitive.Root>, CheckboxProps>(\n ({ className, ...props }, ref) => (\n <CheckboxPrimitive.Root\n ref={ref}\n className={cn(\n 'peer relative h-5 w-5 shrink-0 rounded-[5px] border border-neutral-300 transition-colors',\n 'focus-visible:ring-2 focus-visible:ring-neutral-500 focus-visible:ring-offset-2 focus-visible:outline-none',\n 'disabled:cursor-not-allowed disabled:bg-neutral-100 disabled:opacity-50',\n 'data-[state=checked]:border-neutral-500 data-[state=checked]:bg-neutral-500',\n 'data-[state=indeterminate]:border-neutral-500 data-[state=indeterminate]:bg-neutral-500',\n 'hover:border-neutral-500 disabled:hover:border-neutral-300 data-[state=checked]:hover:border-neutral-500',\n className,\n )}\n {...props}\n >\n <CheckboxPrimitive.Indicator className={cn('absolute inset-0 flex items-center justify-center')}>\n {props.checked === 'indeterminate' ? (\n <Minus className=\"h-3.5 w-3.5 text-white\" weight=\"bold\" />\n ) : (\n <Check className=\"h-3.5 w-3.5 text-white\" />\n )}\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n ),\n)\n\nCheckbox.displayName = 'Checkbox'\n\nexport type { CheckboxProps }\nexport { Checkbox }\n"],"names":[],"mappings":";;;;;;;AA+CA,MAAM,WAAW,KAAA,CAAM,UAAA;AAAA,EACrB,CAAC,EAAE,SAAA,EAAW,GAAG,KAAA,IAAS,GAAA,qBACxB,GAAA;AAAA,IAAC,iBAAA,CAAkB,IAAA;AAAA,IAAlB;AAAA,MACC,GAAA;AAAA,MACA,SAAA,EAAW,EAAA;AAAA,QACT,0FAAA;AAAA,QACA,4GAAA;AAAA,QACA,yEAAA;AAAA,QACA,6EAAA;AAAA,QACA,yFAAA;AAAA,QACA,0GAAA;AAAA,QACA;AAAA,OACF;AAAA,MACC,GAAG,KAAA;AAAA,MAEJ,QAAA,kBAAA,GAAA,CAAC,kBAAkB,SAAA,EAAlB,EAA4B,WAAW,EAAA,CAAG,mDAAmD,CAAA,EAC3F,QAAA,EAAA,KAAA,CAAM,OAAA,KAAY,eAAA,uBAChB,KAAA,EAAA,EAAM,SAAA,EAAU,0BAAyB,MAAA,EAAO,MAAA,EAAO,oBAExD,GAAA,CAAC,KAAA,EAAA,EAAM,SAAA,EAAU,wBAAA,EAAyB,CAAA,EAE9C;AAAA;AAAA;AAGN;AAEA,QAAA,CAAS,WAAA,GAAc,UAAA;;;;"}
@@ -109,7 +109,7 @@ const Combobox = (props) => {
109
109
  {
110
110
  "data-testid": "clear-button",
111
111
  className: cn(
112
- "absolute right-4 top-1/2 z-10 h-4 w-4 -translate-y-1/2 cursor-pointer transition-opacity duration-150",
112
+ "absolute top-1/2 right-4 z-10 h-4 w-4 -translate-y-1/2 cursor-pointer transition-opacity duration-150",
113
113
  showClearButton ? "opacity-100 hover:opacity-70" : "opacity-0"
114
114
  ),
115
115
  onPointerDown: (event) => event.stopPropagation(),
@@ -133,7 +133,6 @@ const Combobox = (props) => {
133
133
  {
134
134
  ref: searchInputRef,
135
135
  placeholder: searchPlaceholder,
136
- disabled: loading,
137
136
  value: searchTerm,
138
137
  onValueChange: setSearchTerm
139
138
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Combobox.js","sources":["../../../src/components/Combobox/Combobox.tsx"],"sourcesContent":["import { X } from '@phosphor-icons/react/dist/ssr/X'\nimport { useEffect, useRef } from 'react'\n\nimport { cn } from '../../lib/utils'\nimport { Command, CommandInput } from '../Command'\nimport { PopoverContent, PopoverRoot, PopoverTrigger } from '../Popover'\n\nimport { ComboboxTrigger } from './ComboboxTrigger'\nimport { StaticComboboxList } from './StaticComboboxList'\nimport { useCombobox } from './useCombobox'\nimport { VirtualizedComboboxList } from './VirtualizedComboboxList'\n\ntype ComboboxBaseProps<T> = {\n /** Unique identifier for the combobox */\n id?: string\n /** Array of options to display in the combobox (static mode). */\n options?: Array<T>\n /** Function to get the unique identifier from an option. */\n getOptionValue: (option: T) => string\n /** Function to get the display text from an option. */\n getOptionLabel: (option: T) => string\n /** Custom render function for options. If not provided, defaults to showing a checkmark and label */\n renderOption?: (option: T, isSelected: boolean) => React.ReactNode\n /** Placeholder text shown when no option is selected */\n placeholder?: string\n /** Placeholder text for the search input field */\n searchPlaceholder?: string\n /** Message shown when no options match the search query */\n emptyMessage?: string\n /** Additional CSS classes to apply to the combobox trigger */\n className?: string\n /** Additional CSS classes to apply to the popover content */\n contentClassName?: string\n /** Whether the combobox is disabled */\n disabled?: boolean\n /** Maximum height of the options list. Can be any valid CSS height value */\n maxHeight?: string | number\n /** Whether to close the dropdown when an option is selected. Defaults to `true` for single select and `false` for multi-select. */\n closeOnSelect?: boolean\n /** Whether the combobox is in a loading state */\n loading?: boolean\n /** Message to show when in loading state */\n loadingPlaceholder?: string\n /** Whether the combobox has an error */\n error?: boolean | string\n /** Custom function to filter options based on search term (static mode only) */\n filterOptions?: (options: Array<T>, searchTerm: string) => Array<T>\n /** Callback function executed when the clear button is clicked. When provided, an X button will appear on hover to clear the selection. */\n onClear?: boolean | (() => void)\n /** Whether the selection can be cleared */\n clearable?: boolean\n /** Whether the combobox is inside a modal */\n modal?: boolean\n /** Placeholder text for the selected multiple options */\n selectedMultiplePlaceholder?: string\n /** Placeholder text for the multiple options */\n multipleOptionsPlaceholder?: string\n /** Special options that appear at the top with their own title. When selected, the combobox switches to single-select mode. */\n specialOptions?: Array<T>\n /** Title for the special options group */\n specialOptionsTitle?: string\n /** Container element to position the combobox relative to. */\n container?: HTMLElement\n /** Alignment of the dropdown relative to the trigger. Defaults to 'center'. */\n align?: 'start' | 'center' | 'end'\n /** Auto-focus the search input when the dropdown opens. */\n onOpenAutoFocus?: boolean\n}\n\nexport type ComboboxSingleProps<T> = ComboboxBaseProps<T> & {\n virtualized?: false\n multiple?: false\n value?: string\n onChange: (value: string) => void\n /** Custom render function for the selected value display. */\n renderLabel?: (selectedOption: T) => React.ReactNode\n}\n\nexport type ComboboxMultipleProps<T> = ComboboxBaseProps<T> & {\n virtualized?: false\n multiple: true\n value?: Array<string>\n onChange: (value: Array<string>) => void\n /** Custom render function for the selected value(s) display. */\n renderLabel?: (selectedOptions: Array<T>, onRemove: (value: string) => void) => React.ReactNode\n}\n\nexport type ComboboxVirtualizedProps<T> = ComboboxBaseProps<T> & {\n virtualized: true\n multiple?: false\n value?: string\n onChange: (value: string) => void\n /** Custom render function for the selected value display. */\n renderLabel?: (selectedOption: T) => React.ReactNode\n /** The accumulated, flattened list of loaded items. */\n items: Array<T>\n /** The resolved selected item, fetched independently of the list. Used for the trigger label. */\n selectedItem?: T\n /** Whether more pages can be loaded. */\n hasNextPage: boolean\n /** Whether the next page is currently being fetched. */\n isFetchingNextPage: boolean\n /** Request the next page (triggered near the end of the list). */\n onLoadMore: () => void\n /** Emitted (debounced) when the search term changes. The consumer refetches. */\n onSearchChange: (search: string) => void\n}\n\nexport type ComboboxProps<T> = ComboboxSingleProps<T> | ComboboxMultipleProps<T> | ComboboxVirtualizedProps<T>\n\n/**\n * A searchable combobox component with support for custom rendering, keyboard navigation, and search filtering.\n *\n * @example Basic usage\n * ```tsx\n * <Combobox<User>\n * options={users}\n * value={selectedUserId}\n * onChange={setSelectedUserId}\n * getOptionValue={(user) => user.id}\n * getOptionLabel={(user) => user.name}\n * />\n * ```\n */\nexport const Combobox = <T extends object>(props: ComboboxProps<T>) => {\n const {\n id,\n options = [],\n getOptionValue,\n getOptionLabel,\n searchPlaceholder = 'Search...',\n emptyMessage = 'No results found.',\n className = 'w-60',\n contentClassName,\n disabled = false,\n maxHeight = '300px',\n renderOption,\n loading = false,\n loadingPlaceholder = 'Cargando...',\n error = false,\n multiple,\n onClear,\n modal = false,\n selectedMultiplePlaceholder = 'Selected',\n multipleOptionsPlaceholder = 'Options',\n specialOptions,\n specialOptionsTitle,\n container,\n align,\n onOpenAutoFocus = false,\n } = props\n\n const searchInputRef = useRef<HTMLInputElement>(null)\n\n const normalizedMaxHeight = typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight\n const boundedListMaxHeight = `min(${normalizedMaxHeight}, max(120px, calc(var(--radix-popover-content-available-height, 100dvh) - 68px)))`\n\n const {\n open,\n setOpen,\n searchTerm,\n setSearchTerm,\n setIsHovered,\n hasNextPage,\n loadingMore,\n isVirtualized,\n filteredOptions,\n displayValue,\n hasValue,\n showClearButton,\n handleSelect,\n handleClear,\n loadNextPage,\n } = useCombobox(props)\n\n useEffect(() => {\n if (!open || !onOpenAutoFocus) return\n const timeoutId = globalThis.setTimeout(() => searchInputRef.current?.focus(), 0)\n return () => globalThis.clearTimeout(timeoutId)\n }, [open, onOpenAutoFocus])\n\n const hasMultipleRenderLabel = !!(multiple && 'renderLabel' in props && props.renderLabel)\n\n return (\n <div className=\"flex w-full flex-col gap-1\">\n <PopoverRoot open={open} onOpenChange={setOpen} modal={modal}>\n <div\n className={cn('relative', className)}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n <PopoverTrigger\n asChild\n onPointerDown={(event) => {\n event.stopPropagation()\n if (open) {\n setOpen(false)\n event.preventDefault()\n } else {\n setOpen(true)\n }\n }}\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n }}\n >\n <ComboboxTrigger\n id={id}\n disabled={disabled}\n open={open}\n error={error}\n hasValue={hasValue}\n showClearButton={showClearButton}\n isMultilineLabel={hasMultipleRenderLabel}\n displayValue={displayValue}\n className=\"w-full\"\n />\n </PopoverTrigger>\n {!!onClear && hasValue && (\n <X\n data-testid=\"clear-button\"\n className={cn(\n 'absolute right-4 top-1/2 z-10 h-4 w-4 -translate-y-1/2 cursor-pointer transition-opacity duration-150',\n showClearButton ? 'opacity-100 hover:opacity-70' : 'opacity-0',\n )}\n onPointerDown={(event) => event.stopPropagation()}\n onClick={handleClear}\n />\n )}\n </div>\n <PopoverContent\n className={cn('overflow-hidden p-0', contentClassName)}\n container={container}\n side=\"bottom\"\n align={align}\n onOpenAutoFocus={onOpenAutoFocus ? (event) => event.preventDefault() : undefined}\n >\n <Command shouldFilter={false}>\n <CommandInput\n ref={searchInputRef}\n placeholder={searchPlaceholder}\n disabled={loading}\n value={searchTerm}\n onValueChange={setSearchTerm}\n />\n {isVirtualized ? (\n <VirtualizedComboboxList\n localOptions={filteredOptions}\n loading={loading}\n loadingPlaceholder={loadingPlaceholder}\n emptyMessage={emptyMessage}\n value={props.value as string}\n getOptionValue={getOptionValue}\n getOptionLabel={getOptionLabel}\n handleSelect={handleSelect}\n renderOption={renderOption}\n maxHeight={boundedListMaxHeight}\n hasNextPage={hasNextPage}\n loadingMore={loadingMore}\n onLoadMore={loadNextPage}\n />\n ) : (\n <StaticComboboxList\n filteredOptions={filteredOptions}\n loading={loading}\n loadingPlaceholder={loadingPlaceholder}\n emptyMessage={emptyMessage}\n multiple={!!multiple}\n value={props.value}\n getOptionValue={getOptionValue}\n getOptionLabel={getOptionLabel}\n handleSelect={handleSelect}\n renderOption={renderOption}\n maxHeight={boundedListMaxHeight}\n selectedMultiplePlaceholder={selectedMultiplePlaceholder}\n multipleOptionsPlaceholder={multipleOptionsPlaceholder}\n options={options}\n specialOptions={specialOptions}\n specialOptionsTitle={specialOptionsTitle}\n />\n )}\n </Command>\n </PopoverContent>\n </PopoverRoot>\n {typeof error === 'string' && <span className=\"text-error-500 text-sm\">{error}</span>}\n </div>\n )\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Combobox.js","sources":["../../../src/components/Combobox/Combobox.tsx"],"sourcesContent":["import { X } from '@phosphor-icons/react/dist/ssr/X'\nimport { useEffect, useRef } from 'react'\n\nimport { cn } from '../../lib/utils'\nimport { Command, CommandInput } from '../Command'\nimport { PopoverContent, PopoverRoot, PopoverTrigger } from '../Popover'\n\nimport { ComboboxTrigger } from './ComboboxTrigger'\nimport { StaticComboboxList } from './StaticComboboxList'\nimport { useCombobox } from './useCombobox'\nimport { VirtualizedComboboxList } from './VirtualizedComboboxList'\n\ntype ComboboxBaseProps<T> = {\n /** Unique identifier for the combobox */\n id?: string\n /** Array of options to display in the combobox (static mode). */\n options?: Array<T>\n /** Function to get the unique identifier from an option. */\n getOptionValue: (option: T) => string\n /** Function to get the display text from an option. */\n getOptionLabel: (option: T) => string\n /** Custom render function for options. If not provided, defaults to showing a checkmark and label */\n renderOption?: (option: T, isSelected: boolean) => React.ReactNode\n /** Placeholder text shown when no option is selected */\n placeholder?: string\n /** Placeholder text for the search input field */\n searchPlaceholder?: string\n /** Message shown when no options match the search query */\n emptyMessage?: string\n /** Additional CSS classes to apply to the combobox trigger */\n className?: string\n /** Additional CSS classes to apply to the popover content */\n contentClassName?: string\n /** Whether the combobox is disabled */\n disabled?: boolean\n /** Maximum height of the options list. Can be any valid CSS height value */\n maxHeight?: string | number\n /** Whether to close the dropdown when an option is selected. Defaults to `true` for single select and `false` for multi-select. */\n closeOnSelect?: boolean\n /** Whether the combobox is in a loading state */\n loading?: boolean\n /** Message to show when in loading state */\n loadingPlaceholder?: string\n /** Whether the combobox has an error */\n error?: boolean | string\n /** Custom function to filter options based on search term (static mode only) */\n filterOptions?: (options: Array<T>, searchTerm: string) => Array<T>\n /** Callback function executed when the clear button is clicked. When provided, an X button will appear on hover to clear the selection. */\n onClear?: boolean | (() => void)\n /** Whether the selection can be cleared */\n clearable?: boolean\n /** Whether the combobox is inside a modal */\n modal?: boolean\n /** Placeholder text for the selected multiple options */\n selectedMultiplePlaceholder?: string\n /** Placeholder text for the multiple options */\n multipleOptionsPlaceholder?: string\n /** Special options that appear at the top with their own title. When selected, the combobox switches to single-select mode. */\n specialOptions?: Array<T>\n /** Title for the special options group */\n specialOptionsTitle?: string\n /** Container element to position the combobox relative to. */\n container?: HTMLElement\n /** Alignment of the dropdown relative to the trigger. Defaults to 'center'. */\n align?: 'start' | 'center' | 'end'\n /** Auto-focus the search input when the dropdown opens. */\n onOpenAutoFocus?: boolean\n}\n\nexport type ComboboxSingleProps<T> = ComboboxBaseProps<T> & {\n virtualized?: false\n multiple?: false\n value?: string\n onChange: (value: string) => void\n /** Custom render function for the selected value display. */\n renderLabel?: (selectedOption: T) => React.ReactNode\n}\n\nexport type ComboboxMultipleProps<T> = ComboboxBaseProps<T> & {\n virtualized?: false\n multiple: true\n value?: Array<string>\n onChange: (value: Array<string>) => void\n /** Custom render function for the selected value(s) display. */\n renderLabel?: (selectedOptions: Array<T>, onRemove: (value: string) => void) => React.ReactNode\n}\n\nexport type ComboboxVirtualizedProps<T> = ComboboxBaseProps<T> & {\n virtualized: true\n multiple?: false\n value?: string\n onChange: (value: string) => void\n /** Custom render function for the selected value display. */\n renderLabel?: (selectedOption: T) => React.ReactNode\n /** The accumulated, flattened list of loaded items. */\n items: Array<T>\n /** The resolved selected item, fetched independently of the list. Used for the trigger label. */\n selectedItem?: T\n /** Whether more pages can be loaded. */\n hasNextPage: boolean\n /** Whether the next page is currently being fetched. */\n isFetchingNextPage: boolean\n /** Request the next page (triggered near the end of the list). */\n onLoadMore: () => void\n /** Emitted (debounced) when the search term changes. The consumer refetches. */\n onSearchChange: (search: string) => void\n}\n\nexport type ComboboxProps<T> = ComboboxSingleProps<T> | ComboboxMultipleProps<T> | ComboboxVirtualizedProps<T>\n\n/**\n * A searchable combobox component with support for custom rendering, keyboard navigation, and search filtering.\n *\n * @example Basic usage\n * ```tsx\n * <Combobox<User>\n * options={users}\n * value={selectedUserId}\n * onChange={setSelectedUserId}\n * getOptionValue={(user) => user.id}\n * getOptionLabel={(user) => user.name}\n * />\n * ```\n */\nexport const Combobox = <T extends object>(props: ComboboxProps<T>) => {\n const {\n id,\n options = [],\n getOptionValue,\n getOptionLabel,\n searchPlaceholder = 'Search...',\n emptyMessage = 'No results found.',\n className = 'w-60',\n contentClassName,\n disabled = false,\n maxHeight = '300px',\n renderOption,\n loading = false,\n loadingPlaceholder = 'Cargando...',\n error = false,\n multiple,\n onClear,\n modal = false,\n selectedMultiplePlaceholder = 'Selected',\n multipleOptionsPlaceholder = 'Options',\n specialOptions,\n specialOptionsTitle,\n container,\n align,\n onOpenAutoFocus = false,\n } = props\n\n const searchInputRef = useRef<HTMLInputElement>(null)\n\n const normalizedMaxHeight = typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight\n const boundedListMaxHeight = `min(${normalizedMaxHeight}, max(120px, calc(var(--radix-popover-content-available-height, 100dvh) - 68px)))`\n\n const {\n open,\n setOpen,\n searchTerm,\n setSearchTerm,\n setIsHovered,\n hasNextPage,\n loadingMore,\n isVirtualized,\n filteredOptions,\n displayValue,\n hasValue,\n showClearButton,\n handleSelect,\n handleClear,\n loadNextPage,\n } = useCombobox(props)\n\n useEffect(() => {\n if (!open || !onOpenAutoFocus) return\n const timeoutId = globalThis.setTimeout(() => searchInputRef.current?.focus(), 0)\n return () => globalThis.clearTimeout(timeoutId)\n }, [open, onOpenAutoFocus])\n\n const hasMultipleRenderLabel = !!(multiple && 'renderLabel' in props && props.renderLabel)\n\n return (\n <div className=\"flex w-full flex-col gap-1\">\n <PopoverRoot open={open} onOpenChange={setOpen} modal={modal}>\n <div\n className={cn('relative', className)}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n <PopoverTrigger\n asChild\n onPointerDown={(event) => {\n event.stopPropagation()\n if (open) {\n setOpen(false)\n event.preventDefault()\n } else {\n setOpen(true)\n }\n }}\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n }}\n >\n <ComboboxTrigger\n id={id}\n disabled={disabled}\n open={open}\n error={error}\n hasValue={hasValue}\n showClearButton={showClearButton}\n isMultilineLabel={hasMultipleRenderLabel}\n displayValue={displayValue}\n className=\"w-full\"\n />\n </PopoverTrigger>\n {!!onClear && hasValue && (\n <X\n data-testid=\"clear-button\"\n className={cn(\n 'absolute top-1/2 right-4 z-10 h-4 w-4 -translate-y-1/2 cursor-pointer transition-opacity duration-150',\n showClearButton ? 'opacity-100 hover:opacity-70' : 'opacity-0',\n )}\n onPointerDown={(event) => event.stopPropagation()}\n onClick={handleClear}\n />\n )}\n </div>\n <PopoverContent\n className={cn('overflow-hidden p-0', contentClassName)}\n container={container}\n side=\"bottom\"\n align={align}\n onOpenAutoFocus={onOpenAutoFocus ? (event) => event.preventDefault() : undefined}\n >\n <Command shouldFilter={false}>\n <CommandInput\n ref={searchInputRef}\n placeholder={searchPlaceholder}\n value={searchTerm}\n onValueChange={setSearchTerm}\n />\n {isVirtualized ? (\n <VirtualizedComboboxList\n localOptions={filteredOptions}\n loading={loading}\n loadingPlaceholder={loadingPlaceholder}\n emptyMessage={emptyMessage}\n value={props.value as string}\n getOptionValue={getOptionValue}\n getOptionLabel={getOptionLabel}\n handleSelect={handleSelect}\n renderOption={renderOption}\n maxHeight={boundedListMaxHeight}\n hasNextPage={hasNextPage}\n loadingMore={loadingMore}\n onLoadMore={loadNextPage}\n />\n ) : (\n <StaticComboboxList\n filteredOptions={filteredOptions}\n loading={loading}\n loadingPlaceholder={loadingPlaceholder}\n emptyMessage={emptyMessage}\n multiple={!!multiple}\n value={props.value}\n getOptionValue={getOptionValue}\n getOptionLabel={getOptionLabel}\n handleSelect={handleSelect}\n renderOption={renderOption}\n maxHeight={boundedListMaxHeight}\n selectedMultiplePlaceholder={selectedMultiplePlaceholder}\n multipleOptionsPlaceholder={multipleOptionsPlaceholder}\n options={options}\n specialOptions={specialOptions}\n specialOptionsTitle={specialOptionsTitle}\n />\n )}\n </Command>\n </PopoverContent>\n </PopoverRoot>\n {typeof error === 'string' && <span className=\"text-error-500 text-sm\">{error}</span>}\n </div>\n )\n}\n"],"names":[],"mappings":"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"}
@@ -3,4 +3,4 @@ import { DataTableProps } from './types';
3
3
  * A feature-rich data table component built on top of TanStack Table.
4
4
  * Provides sorting, filtering, pagination, and column visibility controls.
5
5
  */
6
- export declare function DataTable<TData extends object>({ columns: userColumns, data, getRowId, columnVisibility: columnVisibilityConfig, isLoading, pagination, primaryFilters, secondaryFilters, activePrimaryFiltersCount, activeSecondaryFiltersCount, selection, labels, emptyState, className, tableClassName, }: DataTableProps<TData>): import("react/jsx-runtime").JSX.Element;
6
+ export declare function DataTable<TData extends object>({ columns: userColumns, data, getRowId, columnVisibility: columnVisibilityConfig, isLoading, pagination, grouping, primaryFilters, secondaryFilters, activePrimaryFiltersCount, activeSecondaryFiltersCount, selection, labels, emptyState, className, tableClassName, }: DataTableProps<TData>): import("react/jsx-runtime").JSX.Element;
@@ -1,15 +1,18 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from 'react/jsx-runtime';
3
- import { getPaginationRowModel, useReactTable, getCoreRowModel, flexRender } from '@tanstack/react-table';
3
+ import { getPaginationRowModel, useReactTable, getExpandedRowModel, getCoreRowModel, flexRender } from '@tanstack/react-table';
4
4
  import * as React from 'react';
5
5
  import { Table, TableHeader, TableRow, TableHead } from '../Table/Table.js';
6
6
  import { Typography } from '../Typography/Typography.js';
7
7
  import { DataTableBody } from './components/DataTableBody.js';
8
+ import { DataTableGroupRowSelector } from './components/DataTableGroupRowSelector.js';
8
9
  import { DataTablePagination } from './components/DataTablePagination.js';
9
10
  import { DataTableRowSelector } from './components/DataTableRowSelector.js';
10
11
  import { DataTableSelectHeader } from './components/DataTableSelectHeader.js';
11
12
  import { DataTableToolbar } from './components/DataTableToolbar.js';
13
+ import { useGroupExpansion } from './hooks/useGroupExpansion.js';
12
14
  import { useSelection } from './hooks/useSelection.js';
15
+ import { flattenLeaves } from './utils.js';
13
16
  import { cn } from '../../lib/utils.js';
14
17
 
15
18
  function DataTable({
@@ -19,6 +22,7 @@ function DataTable({
19
22
  columnVisibility: columnVisibilityConfig,
20
23
  isLoading = false,
21
24
  pagination,
25
+ grouping,
22
26
  primaryFilters,
23
27
  secondaryFilters,
24
28
  activePrimaryFiltersCount = 0,
@@ -39,12 +43,17 @@ function DataTable({
39
43
  const [pageIndex, setPageIndex] = React.useState(0);
40
44
  const columnVisibilityState = columnVisibilityConfig?.state ?? internalColumnVisibility;
41
45
  const setColumnVisibilityState = columnVisibilityConfig?.onChange ?? setInternalColumnVisibility;
46
+ const { isGroupOpen, toggleGroup } = useGroupExpansion({ grouping, data, getRowId });
47
+ const selectionData = React.useMemo(
48
+ () => grouping ? flattenLeaves(data, grouping.getSubRows) : data,
49
+ [grouping, data]
50
+ );
42
51
  const isBackendPagination = pagination !== void 0 && "onPageChange" in pagination;
43
52
  const total = isBackendPagination ? pagination.total : data.length;
44
53
  const pageSize = pagination?.pageSize ?? (isLoading ? 10 : data.length);
45
54
  const totalPages = Math.ceil(total / pageSize);
46
55
  const { selectedIds, handleRowSelect, handleSelectAll } = useSelection({
47
- data,
56
+ data: selectionData,
48
57
  getRowId,
49
58
  selection
50
59
  });
@@ -58,7 +67,7 @@ function DataTable({
58
67
  {
59
68
  isBackendPagination,
60
69
  selectedIds,
61
- data,
70
+ data: selectionData,
62
71
  handleSelectAll,
63
72
  isLoading,
64
73
  isSelectionDisabled: selection?.isSelectionDisabled ?? false,
@@ -68,7 +77,7 @@ function DataTable({
68
77
  [
69
78
  isBackendPagination,
70
79
  selectedIds,
71
- data,
80
+ selectionData,
72
81
  handleSelectAll,
73
82
  isLoading,
74
83
  selection?.isSelectionDisabled,
@@ -76,18 +85,32 @@ function DataTable({
76
85
  ]
77
86
  );
78
87
  const SelectColumnCell = React.useCallback(
79
- ({ row }) => /* @__PURE__ */ jsx(
80
- DataTableRowSelector,
81
- {
82
- row,
83
- selectedIds,
84
- getRowId,
85
- handleRowSelect,
86
- isRowSelectionDisabled: selection?.isRowSelectionDisabled,
87
- rowSelectionDisabledTooltip: selection?.labels?.rowSelectionDisabledTooltip
88
+ ({ row }) => {
89
+ if (grouping && row.getCanExpand()) {
90
+ return /* @__PURE__ */ jsx(
91
+ DataTableGroupRowSelector,
92
+ {
93
+ row,
94
+ selectedIds,
95
+ getRowId,
96
+ handleRowSelect
97
+ }
98
+ );
88
99
  }
89
- ),
100
+ return /* @__PURE__ */ jsx(
101
+ DataTableRowSelector,
102
+ {
103
+ row,
104
+ selectedIds,
105
+ getRowId,
106
+ handleRowSelect,
107
+ isRowSelectionDisabled: selection?.isRowSelectionDisabled,
108
+ rowSelectionDisabledTooltip: selection?.labels?.rowSelectionDisabledTooltip
109
+ }
110
+ );
111
+ },
90
112
  [
113
+ grouping,
91
114
  selectedIds,
92
115
  getRowId,
93
116
  handleRowSelect,
@@ -149,13 +172,16 @@ function DataTable({
149
172
  columns: tableColumns,
150
173
  getCoreRowModel: getCoreRowModel(),
151
174
  getPaginationRowModel: tableGetPaginationRowModel,
175
+ getSubRows: grouping?.getSubRows,
176
+ getExpandedRowModel: grouping ? getExpandedRowModel() : void 0,
152
177
  onColumnVisibilityChange: setColumnVisibilityState,
153
178
  enableRowSelection: !!selection,
154
179
  getRowId,
155
180
  state: {
156
181
  columnVisibility: columnVisibilityState,
157
182
  rowSelection,
158
- pagination: tablePaginationState
183
+ pagination: tablePaginationState,
184
+ expanded: grouping ? true : void 0
159
185
  },
160
186
  manualPagination: isBackendPagination,
161
187
  onPaginationChange: isBackendPagination ? void 0 : tableOnPaginationChange
@@ -175,7 +201,7 @@ function DataTable({
175
201
  }
176
202
  ),
177
203
  /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col rounded-md border bg-white", children: [
178
- /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: /* @__PURE__ */ jsx("div", { className: "h-full overflow-auto", children: /* @__PURE__ */ jsxs(Table, { className: "w-full overscroll-contain", tableClassName: cn("table-fixed ", tableClassName), children: [
204
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: /* @__PURE__ */ jsx("div", { className: "h-full scrollbar-gutter-stable overflow-auto overscroll-x-none overscroll-y-auto", children: /* @__PURE__ */ jsxs(Table, { className: "w-full", tableClassName: cn("min-w-max", tableClassName), children: [
179
205
  /* @__PURE__ */ jsx(TableHeader, { className: "sticky top-0 z-10 bg-neutral-50", children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx(TableRow, { children: headerGroup.headers.map((header) => /* @__PURE__ */ jsx(TableHead, { style: { width: header.column.columnDef.size }, children: /* @__PURE__ */ jsx(Typography, { weight: "medium", className: "whitespace-nowrap", children: header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext()) }) }, header.id)) }, headerGroup.id)) }),
180
206
  /* @__PURE__ */ jsx(
181
207
  DataTableBody,
@@ -185,8 +211,13 @@ function DataTable({
185
211
  pageSize,
186
212
  getRowId,
187
213
  rowSelection,
188
- isSelectionStarted: selection?.isSelectionStarted ?? false,
189
- emptyState
214
+ emptyState,
215
+ group: grouping ? {
216
+ renderGroupRow: grouping.renderGroupRow,
217
+ renderGroupSelector: selection ? SelectColumnCell : void 0,
218
+ isOpen: (row) => isGroupOpen(getRowId(row.original)),
219
+ onToggle: (row) => toggleGroup(getRowId(row.original))
220
+ } : void 0
190
221
  }
191
222
  )
192
223
  ] }) }) }),
@@ -1 +1 @@
1
- {"version":3,"file":"DataTable.js","sources":["../../../src/components/DataTable/DataTable.tsx"],"sourcesContent":["'use client'\n\nimport {\n flexRender,\n getCoreRowModel,\n getPaginationRowModel,\n useReactTable,\n VisibilityState,\n Row,\n PaginationState,\n Updater,\n} from '@tanstack/react-table'\nimport * as React from 'react'\n\nimport { Table as TableComponent, TableHeader, TableHead, TableRow } from '../Table'\nimport { Typography } from '../Typography'\n\nimport { DataTableBody } from './components/DataTableBody'\nimport { DataTablePagination } from './components/DataTablePagination'\nimport { DataTableRowSelector } from './components/DataTableRowSelector'\nimport { DataTableSelectHeader } from './components/DataTableSelectHeader'\nimport { DataTableToolbar } from './components/DataTableToolbar'\nimport { useSelection } from './hooks/useSelection'\nimport { DataTableProps } from './types'\n\nimport { cn } from '@/lib/utils'\n\n/**\n * A feature-rich data table component built on top of TanStack Table.\n * Provides sorting, filtering, pagination, and column visibility controls.\n */\nexport function DataTable<TData extends object>({\n columns: userColumns,\n data,\n getRowId = (row: TData) => (row as { id: string }).id,\n columnVisibility: columnVisibilityConfig,\n isLoading = false,\n pagination,\n primaryFilters,\n secondaryFilters,\n activePrimaryFiltersCount = 0,\n activeSecondaryFiltersCount = 0,\n selection,\n labels = {\n filters: 'Filters',\n moreFilters: 'More filters',\n },\n emptyState = {\n title: 'No data available',\n description: 'Try adjusting your filters or search criteria',\n },\n className,\n tableClassName,\n}: DataTableProps<TData>) {\n const [internalColumnVisibility, setInternalColumnVisibility] = React.useState<VisibilityState>({})\n const [pageIndex, setPageIndex] = React.useState(0)\n\n const columnVisibilityState = columnVisibilityConfig?.state ?? internalColumnVisibility\n const setColumnVisibilityState = columnVisibilityConfig?.onChange ?? setInternalColumnVisibility\n\n const isBackendPagination = pagination !== undefined && 'onPageChange' in pagination\n\n const total = isBackendPagination ? pagination.total : data.length\n const pageSize = pagination?.pageSize ?? (isLoading ? 10 : data.length)\n const totalPages = Math.ceil(total / pageSize)\n\n const { selectedIds, handleRowSelect, handleSelectAll } = useSelection({\n data,\n getRowId,\n selection,\n })\n\n const rowSelection = React.useMemo(\n () => Object.fromEntries(Array.from(selectedIds).map((id) => [id, true])),\n [selectedIds],\n )\n\n const SelectColumnHeader = React.useCallback(\n () => (\n <DataTableSelectHeader\n isBackendPagination={isBackendPagination}\n selectedIds={selectedIds}\n data={data}\n handleSelectAll={handleSelectAll}\n isLoading={isLoading}\n isSelectionDisabled={selection?.isSelectionDisabled ?? false}\n selectionDisabledTooltip={selection?.labels?.selectionDisabledTooltip}\n />\n ),\n [\n isBackendPagination,\n selectedIds,\n data,\n handleSelectAll,\n isLoading,\n selection?.isSelectionDisabled,\n selection?.labels?.selectionDisabledTooltip,\n ],\n )\n\n const SelectColumnCell = React.useCallback(\n ({ row }: { row: Row<TData> }) => (\n <DataTableRowSelector\n row={row}\n selectedIds={selectedIds}\n getRowId={getRowId}\n handleRowSelect={handleRowSelect}\n isRowSelectionDisabled={selection?.isRowSelectionDisabled}\n rowSelectionDisabledTooltip={selection?.labels?.rowSelectionDisabledTooltip}\n />\n ),\n [\n selectedIds,\n getRowId,\n handleRowSelect,\n selection?.isRowSelectionDisabled,\n selection?.labels?.rowSelectionDisabledTooltip,\n ],\n )\n\n const tableColumns = React.useMemo(() => {\n const baseColumns = [...userColumns]\n if (selection !== undefined) {\n const selectColumn = {\n id: 'select',\n size: 50,\n cell: SelectColumnCell,\n header: SelectColumnHeader,\n }\n baseColumns.unshift(selectColumn)\n }\n return baseColumns\n }, [userColumns, selection, SelectColumnCell, SelectColumnHeader])\n\n const toolbarSelectionProps = React.useMemo(() => {\n if (isBackendPagination && selection) {\n return {\n isSelectionStarted: selection.isSelectionStarted,\n onSelectionStart: selection.onSelectionStart,\n onSelectionCancel: selection.onSelectionCancel,\n selectionSummary: selection.labels?.selectionSummary,\n isSelectionDisabled: selection.isSelectionDisabled,\n labels: selection.labels,\n }\n }\n return undefined\n }, [isBackendPagination, selection])\n\n const currentPage = isBackendPagination ? pagination.currentPage : pageIndex + 1\n const handlePageChange = isBackendPagination ? pagination.onPageChange : (page: number) => setPageIndex(page - 1)\n\n const tablePaginationState = React.useMemo(() => {\n if (!pagination) {\n return undefined\n }\n return {\n pageIndex: isBackendPagination ? pagination.currentPage - 1 : pageIndex,\n pageSize,\n }\n }, [pagination, isBackendPagination, pageIndex, pageSize])\n\n const tableGetPaginationRowModel = React.useMemo(() => {\n return pagination && !isBackendPagination ? getPaginationRowModel() : undefined\n }, [pagination, isBackendPagination])\n\n const tableOnPaginationChange = React.useCallback(\n (updater: Updater<PaginationState>) => {\n if (typeof updater === 'function') {\n const newState = updater({ pageIndex, pageSize })\n setPageIndex(newState.pageIndex)\n }\n },\n [pageIndex, pageSize, setPageIndex],\n )\n\n const table = useReactTable({\n data,\n columns: tableColumns,\n getCoreRowModel: getCoreRowModel(),\n getPaginationRowModel: tableGetPaginationRowModel,\n onColumnVisibilityChange: setColumnVisibilityState,\n enableRowSelection: !!selection,\n getRowId: getRowId,\n state: {\n columnVisibility: columnVisibilityState,\n rowSelection,\n pagination: tablePaginationState,\n },\n manualPagination: isBackendPagination,\n onPaginationChange: isBackendPagination ? undefined : tableOnPaginationChange,\n })\n\n return (\n <div className={cn('flex h-full min-h-0 w-full flex-1 flex-col gap-2 overflow-hidden', className)}>\n <DataTableToolbar\n table={table}\n columnVisibility={columnVisibilityConfig}\n primaryFilters={primaryFilters}\n secondaryFilters={secondaryFilters}\n activePrimaryFiltersCount={activePrimaryFiltersCount}\n activeSecondaryFiltersCount={activeSecondaryFiltersCount}\n labels={labels}\n selection={toolbarSelectionProps}\n />\n\n <div className=\"flex min-h-0 flex-1 flex-col rounded-md border bg-white\">\n <div className=\"min-h-0 flex-1 overflow-auto\">\n <div className=\"h-full overflow-auto\">\n <TableComponent className=\"w-full overscroll-contain\" tableClassName={cn('table-fixed ', tableClassName)}>\n <TableHeader className=\"sticky top-0 z-10 bg-neutral-50\">\n {table.getHeaderGroups().map((headerGroup) => (\n <TableRow key={headerGroup.id}>\n {headerGroup.headers.map((header) => (\n <TableHead key={header.id} style={{ width: header.column.columnDef.size }}>\n <Typography weight=\"medium\" className=\"whitespace-nowrap\">\n {header.isPlaceholder\n ? null\n : flexRender(header.column.columnDef.header, header.getContext())}\n </Typography>\n </TableHead>\n ))}\n </TableRow>\n ))}\n </TableHeader>\n <DataTableBody\n table={table}\n isLoading={isLoading}\n pageSize={pageSize}\n getRowId={getRowId}\n rowSelection={rowSelection}\n isSelectionStarted={selection?.isSelectionStarted ?? false}\n emptyState={emptyState}\n />\n </TableComponent>\n </div>\n </div>\n {!!pagination && (\n <div className=\"border-t px-4 py-2\">\n <DataTablePagination\n table={table}\n total={total}\n pageSize={pageSize}\n currentPage={currentPage}\n totalPages={totalPages}\n onPageChange={handlePageChange}\n isLoading={pagination.isLoading}\n labels={pagination.labels}\n />\n </div>\n )}\n </div>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;AA+BO;AAAyC;AACrC;AACT;AACmD;AACjC;AACN;AACZ;AACA;AACA;AAC4B;AACE;AAC9B;AACS;AACE;AACI;AACf;AACa;AACJ;AACM;AACf;AACA;AAEF;AACE;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAAuE;AACrE;AACA;AACA;AAGF;AAA2B;AAC+C;AAC5D;AAGd;AAAiC;AAE7B;AAAC;AAAA;AACC;AACA;AACA;AACA;AACA;AACuD;AACV;AAAA;AAC/C;AAEF;AACE;AACA;AACA;AACA;AACA;AACW;AACQ;AACrB;AAGF;AAA+B;AAE3B;AAAC;AAAA;AACC;AACA;AACA;AACA;AACmC;AACa;AAAA;AAClD;AAEF;AACE;AACA;AACA;AACW;AACQ;AACrB;AAGF;AACE;AACA;AACE;AAAqB;AACf;AACE;AACA;AACE;AAEV;AAAgC;AAElC;AAAO;AAGT;AACE;AACE;AAAO;AACyB;AACF;AACC;AACO;AACL;AACb;AACpB;AAEF;AAAO;AAGT;AACA;AAEA;AACE;AACE;AAAO;AAET;AAAO;AACyD;AAC9D;AACF;AAGF;AACE;AAAsE;AAGxE;AAAsC;AAElC;AACE;AACA;AAA+B;AACjC;AACF;AACkC;AAGpC;AAA4B;AAC1B;AACS;AACwB;AACV;AACG;AACJ;AACtB;AACO;AACa;AAClB;AACY;AACd;AACkB;AACoC;AAGxD;AAEI;AAAA;AAAC;AAAA;AACC;AACkB;AAClB;AACA;AACA;AACA;AACA;AACW;AAAA;AACb;AAGE;AAGM;AAcA;AACA;AAAC;AAAA;AACC;AACA;AACA;AACA;AACA;AACqD;AACrD;AAAA;AACF;AAGN;AAGI;AAAC;AAAA;AACC;AACA;AACA;AACA;AACA;AACc;AACQ;AACH;AAAA;AAEvB;AAEJ;AAGN;;"}
1
+ {"version":3,"file":"DataTable.js","sources":["../../../src/components/DataTable/DataTable.tsx"],"sourcesContent":["'use client'\n\nimport {\n flexRender,\n getCoreRowModel,\n getExpandedRowModel,\n getPaginationRowModel,\n useReactTable,\n VisibilityState,\n Row,\n PaginationState,\n Updater,\n} from '@tanstack/react-table'\nimport * as React from 'react'\n\nimport { Table as TableComponent, TableHeader, TableHead, TableRow } from '../Table'\nimport { Typography } from '../Typography'\n\nimport { DataTableBody } from './components/DataTableBody'\nimport { DataTableGroupRowSelector } from './components/DataTableGroupRowSelector'\nimport { DataTablePagination } from './components/DataTablePagination'\nimport { DataTableRowSelector } from './components/DataTableRowSelector'\nimport { DataTableSelectHeader } from './components/DataTableSelectHeader'\nimport { DataTableToolbar } from './components/DataTableToolbar'\nimport { useGroupExpansion } from './hooks/useGroupExpansion'\nimport { useSelection } from './hooks/useSelection'\nimport { DataTableProps } from './types'\nimport { flattenLeaves } from './utils'\n\nimport { cn } from '@/lib/utils'\n\n/**\n * A feature-rich data table component built on top of TanStack Table.\n * Provides sorting, filtering, pagination, and column visibility controls.\n */\nexport function DataTable<TData extends object>({\n columns: userColumns,\n data,\n getRowId = (row: TData) => (row as { id: string }).id,\n columnVisibility: columnVisibilityConfig,\n isLoading = false,\n pagination,\n grouping,\n primaryFilters,\n secondaryFilters,\n activePrimaryFiltersCount = 0,\n activeSecondaryFiltersCount = 0,\n selection,\n labels = {\n filters: 'Filters',\n moreFilters: 'More filters',\n },\n emptyState = {\n title: 'No data available',\n description: 'Try adjusting your filters or search criteria',\n },\n className,\n tableClassName,\n}: DataTableProps<TData>) {\n const [internalColumnVisibility, setInternalColumnVisibility] = React.useState<VisibilityState>({})\n const [pageIndex, setPageIndex] = React.useState(0)\n\n const columnVisibilityState = columnVisibilityConfig?.state ?? internalColumnVisibility\n const setColumnVisibilityState = columnVisibilityConfig?.onChange ?? setInternalColumnVisibility\n\n const { isGroupOpen, toggleGroup } = useGroupExpansion({ grouping, data, getRowId })\n\n const selectionData = React.useMemo(\n () => (grouping ? flattenLeaves(data, grouping.getSubRows) : data),\n [grouping, data],\n )\n\n const isBackendPagination = pagination !== undefined && 'onPageChange' in pagination\n\n const total = isBackendPagination ? pagination.total : data.length\n const pageSize = pagination?.pageSize ?? (isLoading ? 10 : data.length)\n const totalPages = Math.ceil(total / pageSize)\n\n const { selectedIds, handleRowSelect, handleSelectAll } = useSelection({\n data: selectionData,\n getRowId,\n selection,\n })\n\n const rowSelection = React.useMemo(\n () => Object.fromEntries(Array.from(selectedIds).map((id) => [id, true])),\n [selectedIds],\n )\n\n const SelectColumnHeader = React.useCallback(\n () => (\n <DataTableSelectHeader\n isBackendPagination={isBackendPagination}\n selectedIds={selectedIds}\n data={selectionData}\n handleSelectAll={handleSelectAll}\n isLoading={isLoading}\n isSelectionDisabled={selection?.isSelectionDisabled ?? false}\n selectionDisabledTooltip={selection?.labels?.selectionDisabledTooltip}\n />\n ),\n [\n isBackendPagination,\n selectedIds,\n selectionData,\n handleSelectAll,\n isLoading,\n selection?.isSelectionDisabled,\n selection?.labels?.selectionDisabledTooltip,\n ],\n )\n\n const SelectColumnCell = React.useCallback(\n ({ row }: { row: Row<TData> }) => {\n if (grouping && row.getCanExpand()) {\n return (\n <DataTableGroupRowSelector\n row={row}\n selectedIds={selectedIds}\n getRowId={getRowId}\n handleRowSelect={handleRowSelect}\n />\n )\n }\n return (\n <DataTableRowSelector\n row={row}\n selectedIds={selectedIds}\n getRowId={getRowId}\n handleRowSelect={handleRowSelect}\n isRowSelectionDisabled={selection?.isRowSelectionDisabled}\n rowSelectionDisabledTooltip={selection?.labels?.rowSelectionDisabledTooltip}\n />\n )\n },\n [\n grouping,\n selectedIds,\n getRowId,\n handleRowSelect,\n selection?.isRowSelectionDisabled,\n selection?.labels?.rowSelectionDisabledTooltip,\n ],\n )\n\n const tableColumns = React.useMemo(() => {\n const baseColumns = [...userColumns]\n if (selection !== undefined) {\n const selectColumn = {\n id: 'select',\n size: 50,\n cell: SelectColumnCell,\n header: SelectColumnHeader,\n }\n baseColumns.unshift(selectColumn)\n }\n return baseColumns\n }, [userColumns, selection, SelectColumnCell, SelectColumnHeader])\n\n const toolbarSelectionProps = React.useMemo(() => {\n if (isBackendPagination && selection) {\n return {\n isSelectionStarted: selection.isSelectionStarted,\n onSelectionStart: selection.onSelectionStart,\n onSelectionCancel: selection.onSelectionCancel,\n selectionSummary: selection.labels?.selectionSummary,\n isSelectionDisabled: selection.isSelectionDisabled,\n labels: selection.labels,\n }\n }\n return undefined\n }, [isBackendPagination, selection])\n\n const currentPage = isBackendPagination ? pagination.currentPage : pageIndex + 1\n const handlePageChange = isBackendPagination ? pagination.onPageChange : (page: number) => setPageIndex(page - 1)\n\n const tablePaginationState = React.useMemo(() => {\n if (!pagination) {\n return undefined\n }\n return {\n pageIndex: isBackendPagination ? pagination.currentPage - 1 : pageIndex,\n pageSize,\n }\n }, [pagination, isBackendPagination, pageIndex, pageSize])\n\n const tableGetPaginationRowModel = React.useMemo(() => {\n return pagination && !isBackendPagination ? getPaginationRowModel() : undefined\n }, [pagination, isBackendPagination])\n\n const tableOnPaginationChange = React.useCallback(\n (updater: Updater<PaginationState>) => {\n if (typeof updater === 'function') {\n const newState = updater({ pageIndex, pageSize })\n setPageIndex(newState.pageIndex)\n }\n },\n [pageIndex, pageSize, setPageIndex],\n )\n\n const table = useReactTable({\n data,\n columns: tableColumns,\n getCoreRowModel: getCoreRowModel(),\n getPaginationRowModel: tableGetPaginationRowModel,\n getSubRows: grouping?.getSubRows,\n getExpandedRowModel: grouping ? getExpandedRowModel() : undefined,\n onColumnVisibilityChange: setColumnVisibilityState,\n enableRowSelection: !!selection,\n getRowId: getRowId,\n state: {\n columnVisibility: columnVisibilityState,\n rowSelection,\n pagination: tablePaginationState,\n expanded: grouping ? true : undefined,\n },\n manualPagination: isBackendPagination,\n onPaginationChange: isBackendPagination ? undefined : tableOnPaginationChange,\n })\n\n return (\n <div className={cn('flex h-full min-h-0 w-full flex-1 flex-col gap-2 overflow-hidden', className)}>\n <DataTableToolbar\n table={table}\n columnVisibility={columnVisibilityConfig}\n primaryFilters={primaryFilters}\n secondaryFilters={secondaryFilters}\n activePrimaryFiltersCount={activePrimaryFiltersCount}\n activeSecondaryFiltersCount={activeSecondaryFiltersCount}\n labels={labels}\n selection={toolbarSelectionProps}\n />\n\n <div className=\"flex min-h-0 flex-1 flex-col rounded-md border bg-white\">\n <div className=\"min-h-0 flex-1 overflow-auto\">\n <div className=\"h-full scrollbar-gutter-stable overflow-auto overscroll-x-none overscroll-y-auto\">\n <TableComponent className=\"w-full\" tableClassName={cn('min-w-max', tableClassName)}>\n <TableHeader className=\"sticky top-0 z-10 bg-neutral-50\">\n {table.getHeaderGroups().map((headerGroup) => (\n <TableRow key={headerGroup.id}>\n {headerGroup.headers.map((header) => (\n <TableHead key={header.id} style={{ width: header.column.columnDef.size }}>\n <Typography weight=\"medium\" className=\"whitespace-nowrap\">\n {header.isPlaceholder\n ? null\n : flexRender(header.column.columnDef.header, header.getContext())}\n </Typography>\n </TableHead>\n ))}\n </TableRow>\n ))}\n </TableHeader>\n <DataTableBody\n table={table}\n isLoading={isLoading}\n pageSize={pageSize}\n getRowId={getRowId}\n rowSelection={rowSelection}\n emptyState={emptyState}\n group={\n grouping\n ? {\n renderGroupRow: grouping.renderGroupRow,\n renderGroupSelector: selection ? SelectColumnCell : undefined,\n isOpen: (row: Row<TData>) => isGroupOpen(getRowId(row.original)),\n onToggle: (row: Row<TData>) => toggleGroup(getRowId(row.original)),\n }\n : undefined\n }\n />\n </TableComponent>\n </div>\n </div>\n {!!pagination && (\n <div className=\"border-t px-4 py-2\">\n <DataTablePagination\n table={table}\n total={total}\n pageSize={pageSize}\n currentPage={currentPage}\n totalPages={totalPages}\n onPageChange={handlePageChange}\n isLoading={pagination.isLoading}\n labels={pagination.labels}\n />\n </div>\n )}\n </div>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAmCO;AAAyC;AACrC;AACT;AACmD;AACjC;AACN;AACZ;AACA;AACA;AACA;AAC4B;AACE;AAC9B;AACS;AACE;AACI;AACf;AACa;AACJ;AACM;AACf;AACA;AAEF;AACE;AACA;AAEA;AACA;AAEA;AAEA;AAA4B;AACmC;AAC9C;AAGjB;AAEA;AACA;AACA;AAEA;AAAuE;AAC/D;AACN;AACA;AAGF;AAA2B;AAC+C;AAC5D;AAGd;AAAiC;AAE7B;AAAC;AAAA;AACC;AACA;AACM;AACN;AACA;AACuD;AACV;AAAA;AAC/C;AAEF;AACE;AACA;AACA;AACA;AACA;AACW;AACQ;AACrB;AAGF;AAA+B;AAE3B;AACE;AACE;AAAC;AAAA;AACC;AACA;AACA;AACA;AAAA;AACF;AAGJ;AACE;AAAC;AAAA;AACC;AACA;AACA;AACA;AACmC;AACa;AAAA;AAClD;AAEJ;AACA;AACE;AACA;AACA;AACA;AACW;AACQ;AACrB;AAGF;AACE;AACA;AACE;AAAqB;AACf;AACE;AACA;AACE;AAEV;AAAgC;AAElC;AAAO;AAGT;AACE;AACE;AAAO;AACyB;AACF;AACC;AACO;AACL;AACb;AACpB;AAEF;AAAO;AAGT;AACA;AAEA;AACE;AACE;AAAO;AAET;AAAO;AACyD;AAC9D;AACF;AAGF;AACE;AAAsE;AAGxE;AAAsC;AAElC;AACE;AACA;AAA+B;AACjC;AACF;AACkC;AAGpC;AAA4B;AAC1B;AACS;AACwB;AACV;AACD;AACkC;AAC9B;AACJ;AACtB;AACO;AACa;AAClB;AACY;AACgB;AAC9B;AACkB;AACoC;AAGxD;AAEI;AAAA;AAAC;AAAA;AACC;AACkB;AAClB;AACA;AACA;AACA;AACA;AACW;AAAA;AACb;AAGE;AAGM;AAcA;AACA;AAAC;AAAA;AACC;AACA;AACA;AACA;AACA;AACA;AAGM;AAC2B;AAC2B;AACW;AACE;AAEnE;AAAA;AAER;AAGN;AAGI;AAAC;AAAA;AACC;AACA;AACA;AACA;AACA;AACc;AACQ;AACH;AAAA;AAEvB;AAEJ;AAGN;;"}
@@ -1,12 +1,20 @@
1
- import { Table } from '@tanstack/react-table';
1
+ import { Table, Row } from '@tanstack/react-table';
2
+ import { ReactNode } from 'react';
2
3
  import { RowIdentifierFn } from '../types';
4
+ type GroupRenderConfig<TData> = {
5
+ readonly renderGroupRow?: (row: Row<TData>) => ReactNode;
6
+ readonly renderGroupSelector?: React.ComponentType<{
7
+ row: Row<TData>;
8
+ }>;
9
+ readonly isOpen: (row: Row<TData>) => boolean;
10
+ readonly onToggle: (row: Row<TData>) => void;
11
+ };
3
12
  type DataTableBodyProps<TData> = {
4
13
  readonly table: Table<TData>;
5
14
  readonly isLoading?: boolean;
6
15
  readonly pageSize: number;
7
16
  readonly getRowId: RowIdentifierFn<TData>;
8
17
  readonly rowSelection: Record<string, boolean>;
9
- readonly isSelectionStarted: boolean;
10
18
  readonly emptyState: {
11
19
  title: string;
12
20
  description: string;
@@ -14,6 +22,7 @@ type DataTableBodyProps<TData> = {
14
22
  imageAlt?: string;
15
23
  imageClassName?: string;
16
24
  };
25
+ readonly group?: GroupRenderConfig<TData>;
17
26
  };
18
- export declare function DataTableBody<TData>({ table, isLoading, pageSize, getRowId, rowSelection, isSelectionStarted, emptyState, }: DataTableBodyProps<TData>): import("react/jsx-runtime").JSX.Element;
27
+ export declare function DataTableBody<TData>({ table, isLoading, pageSize, getRowId, rowSelection, emptyState, group, }: DataTableBodyProps<TData>): import("react/jsx-runtime").JSX.Element;
19
28
  export {};
@@ -3,13 +3,23 @@ import { flexRender } from '@tanstack/react-table';
3
3
  import { Skeleton } from '../../Skeleton/Skeleton.js';
4
4
  import { TableBody, TableRow, TableCell } from '../../Table/Table.js';
5
5
  import { Typography } from '../../Typography/Typography.js';
6
+ import { DataTableGroupRow } from './DataTableGroupRow.js';
6
7
  import { cn } from '../../../lib/utils.js';
7
8
 
8
- function TableRowComponent({
9
- row,
10
- isSelected
11
- }) {
12
- return /* @__PURE__ */ jsx(TableRow, { "data-selected": isSelected, children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx(TableCell, { style: { width: cell.column.columnDef.size }, children: /* @__PURE__ */ jsx(Typography, { children: cell.column.columnDef.cell && flexRender(cell.column.columnDef.cell, cell.getContext()) }) }, cell.id)) });
9
+ function TableRowComponent({ row, isSelected, isCollapsible = false, isOpen = true }) {
10
+ if (!isCollapsible) {
11
+ return /* @__PURE__ */ jsx(TableRow, { "data-selected": isSelected, children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx(TableCell, { style: { width: cell.column.columnDef.size }, children: /* @__PURE__ */ jsx(Typography, { className: "whitespace-nowrap", children: cell.column.columnDef.cell && flexRender(cell.column.columnDef.cell, cell.getContext()) }) }, cell.id)) });
12
+ }
13
+ return /* @__PURE__ */ jsx(TableRow, { "data-selected": isSelected, className: cn("transition-colors", !isOpen && "border-b-0"), children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx(TableCell, { style: { width: cell.column.columnDef.size }, className: "p-0", children: /* @__PURE__ */ jsx(
14
+ "div",
15
+ {
16
+ className: cn(
17
+ "grid transition-[grid-template-rows] duration-200 ease-in-out",
18
+ isOpen ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
19
+ ),
20
+ children: /* @__PURE__ */ jsx("div", { className: "min-w-max overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "px-4 py-4", children: /* @__PURE__ */ jsx(Typography, { className: "whitespace-nowrap", children: cell.column.columnDef.cell && flexRender(cell.column.columnDef.cell, cell.getContext()) }) }) })
21
+ }
22
+ ) }, cell.id)) });
13
23
  }
14
24
  function DataTableBody({
15
25
  table,
@@ -17,8 +27,8 @@ function DataTableBody({
17
27
  pageSize,
18
28
  getRowId,
19
29
  rowSelection,
20
- isSelectionStarted,
21
- emptyState
30
+ emptyState,
31
+ group
22
32
  }) {
23
33
  if (isLoading) {
24
34
  return /* @__PURE__ */ jsx(TableBody, { children: Array.from({ length: pageSize }).map((_unused, index) => /* @__PURE__ */ jsx(TableRow, { children: table.getAllColumns().filter((column) => column.getIsVisible()).map((column) => {
@@ -54,10 +64,35 @@ function DataTableBody({
54
64
  /* @__PURE__ */ jsx(Typography, { variant: "title-sm", className: "text-center opacity-80", children: emptyState.description })
55
65
  ] }) }) }) });
56
66
  }
67
+ const visibleColumnCount = table.getVisibleLeafColumns().length;
57
68
  return /* @__PURE__ */ jsx(TableBody, { children: rows.map((row) => {
58
69
  const rowId = getRowId(row.original);
70
+ if (group && row.getCanExpand()) {
71
+ const content = group.renderGroupRow?.(row) ?? `${String(row.getLeafRows().length)} items`;
72
+ return /* @__PURE__ */ jsx(
73
+ DataTableGroupRow,
74
+ {
75
+ colSpan: visibleColumnCount,
76
+ selector: group.renderGroupSelector ? /* @__PURE__ */ jsx(group.renderGroupSelector, { row }) : void 0,
77
+ content,
78
+ isOpen: group.isOpen(row),
79
+ onToggle: () => group.onToggle(row)
80
+ },
81
+ rowId
82
+ );
83
+ }
59
84
  const isSelected = rowSelection[rowId] ?? false;
60
- return /* @__PURE__ */ jsx(TableRowComponent, { row, isSelected, isSelectionStarted }, rowId);
85
+ const parentRow = group ? row.getParentRow() : void 0;
86
+ return /* @__PURE__ */ jsx(
87
+ TableRowComponent,
88
+ {
89
+ row,
90
+ isSelected,
91
+ isCollapsible: !!group,
92
+ isOpen: parentRow ? group?.isOpen(parentRow) ?? true : true
93
+ },
94
+ rowId
95
+ );
61
96
  }) });
62
97
  }
63
98
 
@@ -1 +1 @@
1
- {"version":3,"file":"DataTableBody.js","sources":["../../../../src/components/DataTable/components/DataTableBody.tsx"],"sourcesContent":["import { Table, flexRender, Row } from '@tanstack/react-table'\n\nimport { Skeleton } from '../../Skeleton'\nimport { TableBody, TableCell, TableRow } from '../../Table'\nimport { Typography } from '../../Typography'\nimport { RowIdentifierFn } from '../types'\n\nimport { cn } from '@/lib/utils'\n\ntype DataTableBodyProps<TData> = {\n readonly table: Table<TData>\n readonly isLoading?: boolean\n readonly pageSize: number\n readonly getRowId: RowIdentifierFn<TData>\n readonly rowSelection: Record<string, boolean>\n readonly isSelectionStarted: boolean\n readonly emptyState: {\n title: string\n description: string\n imageRoute?: string\n imageAlt?: string\n imageClassName?: string\n }\n}\n\ntype TableRowProps<TData> = {\n readonly row: Row<TData>\n readonly isSelectable: boolean\n readonly isSelected: boolean\n readonly isSelectionStarted: boolean\n readonly getRowId: (row: TData) => string\n readonly handleRowSelect: (checked: boolean, row: TData) => void\n}\n\nfunction TableRowComponent<TData>({\n row,\n isSelected,\n}: Readonly<Omit<TableRowProps<TData>, 'isSelectable' | 'getRowId' | 'handleRowSelect'>>) {\n return (\n <TableRow data-selected={isSelected}>\n {row.getVisibleCells().map((cell) => (\n <TableCell key={cell.id} style={{ width: cell.column.columnDef.size }}>\n <Typography>\n {cell.column.columnDef.cell && flexRender(cell.column.columnDef.cell, cell.getContext())}\n </Typography>\n </TableCell>\n ))}\n </TableRow>\n )\n}\n\nexport function DataTableBody<TData>({\n table,\n isLoading,\n pageSize,\n getRowId,\n rowSelection,\n isSelectionStarted,\n emptyState,\n}: DataTableBodyProps<TData>) {\n if (isLoading) {\n return (\n <TableBody>\n {Array.from({ length: pageSize }).map((_unused, index) => (\n <TableRow key={`skeleton-row-${String(index)}`}>\n {table\n .getAllColumns()\n .filter((column) => column.getIsVisible())\n .map((column) => {\n let skeletonClass = 'h-[20px] w-full'\n if (column.id === 'actions') skeletonClass = 'h-8 w-8 shrink-0'\n else if (column.id === 'select') skeletonClass = 'h-5 w-5 shrink-0'\n\n let justifyClass = 'justify-center'\n if (column.id === 'actions') justifyClass = 'justify-end'\n else if (column.id === 'select') justifyClass = 'justify-start'\n\n return (\n <TableCell\n key={`skeleton-cell-${String(index)}-${column.id}`}\n style={{ width: column.columnDef.size }}\n >\n <div className={cn('flex items-center', justifyClass)}>\n <Skeleton className={cn(skeletonClass)} />\n </div>\n </TableCell>\n )\n })}\n </TableRow>\n ))}\n </TableBody>\n )\n }\n\n const rows = table.getRowModel().rows\n\n if (!rows.length) {\n const visibleColumns = table.getAllColumns().filter((col) => col.getIsVisible())\n\n return (\n <TableBody>\n <TableRow className=\"h-full\">\n <TableCell colSpan={visibleColumns.length} className=\"h-full p-0\">\n <div className=\"mt-20 flex h-full w-full flex-col items-center justify-center\">\n {emptyState.imageRoute && (\n <img\n className={cn('mb-6 w-[200px] md:w-[300px]', emptyState.imageClassName)}\n src={emptyState.imageRoute}\n alt={emptyState.imageAlt ?? 'No data available'}\n />\n )}\n <Typography variant=\"title-md\" weight=\"semibold\" className=\"text-center\">\n {emptyState.title}\n </Typography>\n <Typography variant=\"title-sm\" className=\"text-center opacity-80\">\n {emptyState.description}\n </Typography>\n </div>\n </TableCell>\n </TableRow>\n </TableBody>\n )\n }\n\n return (\n <TableBody>\n {rows.map((row) => {\n const rowId = getRowId(row.original)\n const isSelected = rowSelection[rowId] ?? false\n return (\n <TableRowComponent key={rowId} row={row} isSelected={isSelected} isSelectionStarted={isSelectionStarted} />\n )\n })}\n </TableBody>\n )\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DataTableBody.js","sources":["../../../../src/components/DataTable/components/DataTableBody.tsx"],"sourcesContent":["import { Table, flexRender, Row } from '@tanstack/react-table'\nimport { ReactNode } from 'react'\n\nimport { Skeleton } from '../../Skeleton'\nimport { TableBody, TableCell, TableRow } from '../../Table'\nimport { Typography } from '../../Typography'\nimport { RowIdentifierFn } from '../types'\n\nimport { DataTableGroupRow } from './DataTableGroupRow'\n\nimport { cn } from '@/lib/utils'\n\ntype GroupRenderConfig<TData> = {\n readonly renderGroupRow?: (row: Row<TData>) => ReactNode\n readonly renderGroupSelector?: React.ComponentType<{ row: Row<TData> }>\n readonly isOpen: (row: Row<TData>) => boolean\n readonly onToggle: (row: Row<TData>) => void\n}\n\ntype DataTableBodyProps<TData> = {\n readonly table: Table<TData>\n readonly isLoading?: boolean\n readonly pageSize: number\n readonly getRowId: RowIdentifierFn<TData>\n readonly rowSelection: Record<string, boolean>\n readonly emptyState: {\n title: string\n description: string\n imageRoute?: string\n imageAlt?: string\n imageClassName?: string\n }\n readonly group?: GroupRenderConfig<TData>\n}\n\ntype TableRowProps<TData> = {\n readonly row: Row<TData>\n readonly isSelected: boolean\n readonly isCollapsible?: boolean\n readonly isOpen?: boolean\n}\n\nfunction TableRowComponent<TData>({ row, isSelected, isCollapsible = false, isOpen = true }: TableRowProps<TData>) {\n if (!isCollapsible) {\n return (\n <TableRow data-selected={isSelected}>\n {row.getVisibleCells().map((cell) => (\n <TableCell key={cell.id} style={{ width: cell.column.columnDef.size }}>\n <Typography className=\"whitespace-nowrap\">\n {cell.column.columnDef.cell && flexRender(cell.column.columnDef.cell, cell.getContext())}\n </Typography>\n </TableCell>\n ))}\n </TableRow>\n )\n }\n\n return (\n <TableRow data-selected={isSelected} className={cn('transition-colors', !isOpen && 'border-b-0')}>\n {row.getVisibleCells().map((cell) => (\n <TableCell key={cell.id} style={{ width: cell.column.columnDef.size }} className=\"p-0\">\n <div\n className={cn(\n 'grid transition-[grid-template-rows] duration-200 ease-in-out',\n isOpen ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]',\n )}\n >\n <div className=\"min-w-max overflow-hidden\">\n <div className=\"px-4 py-4\">\n <Typography className=\"whitespace-nowrap\">\n {cell.column.columnDef.cell && flexRender(cell.column.columnDef.cell, cell.getContext())}\n </Typography>\n </div>\n </div>\n </div>\n </TableCell>\n ))}\n </TableRow>\n )\n}\n\nexport function DataTableBody<TData>({\n table,\n isLoading,\n pageSize,\n getRowId,\n rowSelection,\n emptyState,\n group,\n}: DataTableBodyProps<TData>) {\n if (isLoading) {\n return (\n <TableBody>\n {Array.from({ length: pageSize }).map((_unused, index) => (\n <TableRow key={`skeleton-row-${String(index)}`}>\n {table\n .getAllColumns()\n .filter((column) => column.getIsVisible())\n .map((column) => {\n let skeletonClass = 'h-[20px] w-full'\n if (column.id === 'actions') skeletonClass = 'h-8 w-8 shrink-0'\n else if (column.id === 'select') skeletonClass = 'h-5 w-5 shrink-0'\n\n let justifyClass = 'justify-center'\n if (column.id === 'actions') justifyClass = 'justify-end'\n else if (column.id === 'select') justifyClass = 'justify-start'\n\n return (\n <TableCell\n key={`skeleton-cell-${String(index)}-${column.id}`}\n style={{ width: column.columnDef.size }}\n >\n <div className={cn('flex items-center', justifyClass)}>\n <Skeleton className={cn(skeletonClass)} />\n </div>\n </TableCell>\n )\n })}\n </TableRow>\n ))}\n </TableBody>\n )\n }\n\n const rows = table.getRowModel().rows\n\n if (!rows.length) {\n const visibleColumns = table.getAllColumns().filter((col) => col.getIsVisible())\n\n return (\n <TableBody>\n <TableRow className=\"h-full\">\n <TableCell colSpan={visibleColumns.length} className=\"h-full p-0\">\n <div className=\"mt-20 flex h-full w-full flex-col items-center justify-center\">\n {emptyState.imageRoute && (\n <img\n className={cn('mb-6 w-[200px] md:w-[300px]', emptyState.imageClassName)}\n src={emptyState.imageRoute}\n alt={emptyState.imageAlt ?? 'No data available'}\n />\n )}\n <Typography variant=\"title-md\" weight=\"semibold\" className=\"text-center\">\n {emptyState.title}\n </Typography>\n <Typography variant=\"title-sm\" className=\"text-center opacity-80\">\n {emptyState.description}\n </Typography>\n </div>\n </TableCell>\n </TableRow>\n </TableBody>\n )\n }\n\n const visibleColumnCount = table.getVisibleLeafColumns().length\n\n return (\n <TableBody>\n {rows.map((row) => {\n const rowId = getRowId(row.original)\n\n if (group && row.getCanExpand()) {\n const content = group.renderGroupRow?.(row) ?? `${String(row.getLeafRows().length)} items`\n return (\n <DataTableGroupRow\n key={rowId}\n colSpan={visibleColumnCount}\n selector={group.renderGroupSelector ? <group.renderGroupSelector row={row} /> : undefined}\n content={content}\n isOpen={group.isOpen(row)}\n onToggle={() => group.onToggle(row)}\n />\n )\n }\n\n const isSelected = rowSelection[rowId] ?? false\n const parentRow = group ? row.getParentRow() : undefined\n return (\n <TableRowComponent\n key={rowId}\n row={row}\n isSelected={isSelected}\n isCollapsible={!!group}\n isOpen={parentRow ? (group?.isOpen(parentRow) ?? true) : true}\n />\n )\n })}\n </TableBody>\n )\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { ReactNode } from 'react';
2
+ type DataTableGroupRowProps = {
3
+ readonly colSpan: number;
4
+ readonly selector?: ReactNode;
5
+ readonly content: ReactNode;
6
+ readonly isOpen: boolean;
7
+ readonly onToggle: () => void;
8
+ };
9
+ export declare function DataTableGroupRow({ colSpan, selector, content, isOpen, onToggle }: DataTableGroupRowProps): import("react/jsx-runtime").JSX.Element;
10
+ export {};
@@ -0,0 +1,25 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { CaretRight } from '@phosphor-icons/react/dist/ssr/CaretRight';
3
+ import { TableRow, TableCell } from '../../Table/Table.js';
4
+ import { Typography } from '../../Typography/Typography.js';
5
+ import { cn } from '../../../lib/utils.js';
6
+
7
+ function DataTableGroupRow({ colSpan, selector, content, isOpen, onToggle }) {
8
+ return /* @__PURE__ */ jsx(TableRow, { onClick: onToggle, "aria-expanded": isOpen, className: "cursor-pointer bg-white hover:bg-neutral-50", children: /* @__PURE__ */ jsx(TableCell, { colSpan, className: "p-0", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
9
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-4", children: [
10
+ selector,
11
+ /* @__PURE__ */ jsx(Typography, { weight: "medium", children: content })
12
+ ] }),
13
+ /* @__PURE__ */ jsx("div", { className: "sticky right-0 flex items-center bg-inherit px-4 py-4", children: /* @__PURE__ */ jsx(
14
+ CaretRight,
15
+ {
16
+ size: 16,
17
+ weight: "bold",
18
+ className: cn("shrink-0 transition-transform", isOpen && "rotate-90")
19
+ }
20
+ ) })
21
+ ] }) }) });
22
+ }
23
+
24
+ export { DataTableGroupRow };
25
+ //# sourceMappingURL=DataTableGroupRow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DataTableGroupRow.js","sources":["../../../../src/components/DataTable/components/DataTableGroupRow.tsx"],"sourcesContent":["import { CaretRight } from '@phosphor-icons/react/dist/ssr/CaretRight'\nimport { ReactNode } from 'react'\n\nimport { TableCell, TableRow } from '../../Table'\nimport { Typography } from '../../Typography'\n\nimport { cn } from '@/lib/utils'\n\ntype DataTableGroupRowProps = {\n readonly colSpan: number\n readonly selector?: ReactNode\n readonly content: ReactNode\n readonly isOpen: boolean\n readonly onToggle: () => void\n}\n\nexport function DataTableGroupRow({ colSpan, selector, content, isOpen, onToggle }: DataTableGroupRowProps) {\n return (\n <TableRow onClick={onToggle} aria-expanded={isOpen} className=\"cursor-pointer bg-white hover:bg-neutral-50\">\n <TableCell colSpan={colSpan} className=\"p-0\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-3 px-4 py-4\">\n {selector}\n <Typography weight=\"medium\">{content}</Typography>\n </div>\n <div className=\"sticky right-0 flex items-center bg-inherit px-4 py-4\">\n <CaretRight\n size={16}\n weight=\"bold\"\n className={cn('shrink-0 transition-transform', isOpen && 'rotate-90')}\n />\n </div>\n </div>\n </TableCell>\n </TableRow>\n )\n}\n"],"names":[],"mappings":";;;;;;AAgBO,SAAS,kBAAkB,EAAE,OAAA,EAAS,UAAU,OAAA,EAAS,MAAA,EAAQ,UAAS,EAA2B;AAC1G,EAAA,2BACG,QAAA,EAAA,EAAS,OAAA,EAAS,QAAA,EAAU,eAAA,EAAe,QAAQ,SAAA,EAAU,6CAAA,EAC5D,QAAA,kBAAA,GAAA,CAAC,SAAA,EAAA,EAAU,SAAkB,SAAA,EAAU,KAAA,EACrC,QAAA,kBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,mCAAA,EACb,QAAA,EAAA;AAAA,oBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,mCAAA,EACZ,QAAA,EAAA;AAAA,MAAA,QAAA;AAAA,sBACD,GAAA,CAAC,UAAA,EAAA,EAAW,MAAA,EAAO,QAAA,EAAU,QAAA,EAAA,OAAA,EAAQ;AAAA,KAAA,EACvC,CAAA;AAAA,oBACA,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,uDAAA,EACb,QAAA,kBAAA,GAAA;AAAA,MAAC,UAAA;AAAA,MAAA;AAAA,QACC,IAAA,EAAM,EAAA;AAAA,QACN,MAAA,EAAO,MAAA;AAAA,QACP,SAAA,EAAW,EAAA,CAAG,+BAAA,EAAiC,MAAA,IAAU,WAAW;AAAA;AAAA,KACtE,EACF;AAAA,GAAA,EACF,GACF,CAAA,EACF,CAAA;AAEJ;;;;"}
@@ -0,0 +1,9 @@
1
+ import { Row } from '@tanstack/react-table';
2
+ type DataTableGroupRowSelectorProps<TData extends object> = {
3
+ readonly row: Row<TData>;
4
+ readonly selectedIds: Set<string>;
5
+ readonly getRowId: (original: TData) => string;
6
+ readonly handleRowSelect: (rowId: string, checked: boolean) => void;
7
+ };
8
+ export declare function DataTableGroupRowSelector<TData extends object>({ row, selectedIds, getRowId, handleRowSelect, }: DataTableGroupRowSelectorProps<TData>): import("react/jsx-runtime").JSX.Element;
9
+ export {};
@@ -0,0 +1,32 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { Checkbox } from '../../Checkbox/Checkbox.js';
3
+
4
+ function DataTableGroupRowSelector({
5
+ row,
6
+ selectedIds,
7
+ getRowId,
8
+ handleRowSelect
9
+ }) {
10
+ const leafRows = row.getLeafRows();
11
+ const leafIds = leafRows.map((leaf) => getRowId(leaf.original));
12
+ const selectedCount = leafIds.filter((id) => selectedIds.has(id)).length;
13
+ const allSelected = leafIds.length > 0 && selectedCount === leafIds.length;
14
+ const someSelected = selectedCount > 0 && !allSelected;
15
+ const handleToggle = (checked) => {
16
+ leafIds.forEach((id) => handleRowSelect(id, checked));
17
+ };
18
+ return /* @__PURE__ */ jsx(
19
+ Checkbox,
20
+ {
21
+ checked: someSelected ? "indeterminate" : allSelected,
22
+ onCheckedChange: (checked) => handleToggle(!!checked),
23
+ onClick: (event) => event.stopPropagation(),
24
+ className: "cursor-default",
25
+ disabled: leafIds.length === 0,
26
+ "aria-label": "Select group"
27
+ }
28
+ );
29
+ }
30
+
31
+ export { DataTableGroupRowSelector };
32
+ //# sourceMappingURL=DataTableGroupRowSelector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DataTableGroupRowSelector.js","sources":["../../../../src/components/DataTable/components/DataTableGroupRowSelector.tsx"],"sourcesContent":["import { Row } from '@tanstack/react-table'\n\nimport { Checkbox } from '@/components/Checkbox'\n\ntype DataTableGroupRowSelectorProps<TData extends object> = {\n readonly row: Row<TData>\n readonly selectedIds: Set<string>\n readonly getRowId: (original: TData) => string\n readonly handleRowSelect: (rowId: string, checked: boolean) => void\n}\n\nexport function DataTableGroupRowSelector<TData extends object>({\n row,\n selectedIds,\n getRowId,\n handleRowSelect,\n}: DataTableGroupRowSelectorProps<TData>) {\n const leafRows = row.getLeafRows()\n const leafIds = leafRows.map((leaf) => getRowId(leaf.original))\n const selectedCount = leafIds.filter((id) => selectedIds.has(id)).length\n\n const allSelected = leafIds.length > 0 && selectedCount === leafIds.length\n const someSelected = selectedCount > 0 && !allSelected\n\n const handleToggle = (checked: boolean) => {\n leafIds.forEach((id) => handleRowSelect(id, checked))\n }\n\n return (\n <Checkbox\n checked={someSelected ? 'indeterminate' : allSelected}\n onCheckedChange={(checked) => handleToggle(!!checked)}\n onClick={(event) => event.stopPropagation()}\n className=\"cursor-default\"\n disabled={leafIds.length === 0}\n aria-label=\"Select group\"\n />\n )\n}\n"],"names":[],"mappings":";;;AAWO,SAAS,yBAAA,CAAgD;AAAA,EAC9D,GAAA;AAAA,EACA,WAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,EAA0C;AACxC,EAAA,MAAM,QAAA,GAAW,IAAI,WAAA,EAAY;AACjC,EAAA,MAAM,OAAA,GAAU,SAAS,GAAA,CAAI,CAAC,SAAS,QAAA,CAAS,IAAA,CAAK,QAAQ,CAAC,CAAA;AAC9D,EAAA,MAAM,aAAA,GAAgB,QAAQ,MAAA,CAAO,CAAC,OAAO,WAAA,CAAY,GAAA,CAAI,EAAE,CAAC,CAAA,CAAE,MAAA;AAElE,EAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,MAAA,GAAS,CAAA,IAAK,kBAAkB,OAAA,CAAQ,MAAA;AACpE,EAAA,MAAM,YAAA,GAAe,aAAA,GAAgB,CAAA,IAAK,CAAC,WAAA;AAE3C,EAAA,MAAM,YAAA,GAAe,CAAC,OAAA,KAAqB;AACzC,IAAA,OAAA,CAAQ,QAAQ,CAAC,EAAA,KAAO,eAAA,CAAgB,EAAA,EAAI,OAAO,CAAC,CAAA;AAAA,EACtD,CAAA;AAEA,EAAA,uBACE,GAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,OAAA,EAAS,eAAe,eAAA,GAAkB,WAAA;AAAA,MAC1C,iBAAiB,CAAC,OAAA,KAAY,YAAA,CAAa,CAAC,CAAC,OAAO,CAAA;AAAA,MACpD,OAAA,EAAS,CAAC,KAAA,KAAU,KAAA,CAAM,eAAA,EAAgB;AAAA,MAC1C,SAAA,EAAU,gBAAA;AAAA,MACV,QAAA,EAAU,QAAQ,MAAA,KAAW,CAAA;AAAA,MAC7B,YAAA,EAAW;AAAA;AAAA,GACb;AAEJ;;;;"}
@@ -14,17 +14,23 @@ function DataTableSelectHeader({
14
14
  if (isBackendPagination) {
15
15
  return null;
16
16
  }
17
+ const allSelected = data.length > 0 && selectedIds.size === data.length;
18
+ const someSelected = selectedIds.size > 0 && !allSelected;
19
+ const checkbox = /* @__PURE__ */ jsx(
20
+ Checkbox,
21
+ {
22
+ checked: someSelected ? "indeterminate" : allSelected,
23
+ onCheckedChange: handleSelectAll,
24
+ disabled: isLoading || !data.length || isSelectionDisabled,
25
+ "aria-label": "Select all rows"
26
+ }
27
+ );
28
+ if (!selectionDisabledTooltip) {
29
+ return checkbox;
30
+ }
17
31
  return /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(TooltipRoot, { delayDuration: 200, children: [
18
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
19
- Checkbox,
20
- {
21
- checked: selectedIds.size === data.length,
22
- onCheckedChange: handleSelectAll,
23
- disabled: isLoading || !data.length || isSelectionDisabled,
24
- "aria-label": "Select all rows"
25
- }
26
- ) }),
27
- selectionDisabledTooltip && /* @__PURE__ */ jsx(TooltipContent, { children: selectionDisabledTooltip })
32
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx("div", { className: "inline-flex", children: checkbox }) }),
33
+ /* @__PURE__ */ jsx(TooltipContent, { children: selectionDisabledTooltip })
28
34
  ] }) });
29
35
  }
30
36
 
@@ -1 +1 @@
1
- {"version":3,"file":"DataTableSelectHeader.js","sources":["../../../../src/components/DataTable/components/DataTableSelectHeader.tsx"],"sourcesContent":["import { Checkbox } from '@/components/Checkbox'\nimport { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from '@/components/Tooltip'\n\ntype SelectAllHeaderProps<TData extends object> = {\n readonly isBackendPagination: boolean\n readonly selectedIds: Set<string>\n readonly data: Array<TData>\n readonly handleSelectAll: (checked: boolean) => void\n readonly isLoading: boolean\n readonly isSelectionDisabled: boolean\n readonly selectionDisabledTooltip: string | undefined\n}\n\nexport function DataTableSelectHeader<TData extends object>({\n isBackendPagination,\n selectedIds,\n data,\n handleSelectAll,\n isLoading,\n isSelectionDisabled,\n selectionDisabledTooltip,\n}: SelectAllHeaderProps<TData>) {\n if (isBackendPagination) {\n return null\n }\n\n return (\n <TooltipProvider>\n <TooltipRoot delayDuration={200}>\n <TooltipTrigger asChild>\n <Checkbox\n checked={selectedIds.size === data.length}\n onCheckedChange={handleSelectAll}\n disabled={isLoading || !data.length || isSelectionDisabled}\n aria-label=\"Select all rows\"\n />\n </TooltipTrigger>\n {selectionDisabledTooltip && <TooltipContent>{selectionDisabledTooltip}</TooltipContent>}\n </TooltipRoot>\n </TooltipProvider>\n )\n}\n"],"names":[],"mappings":";;;;AAaO,SAAS,qBAAA,CAA4C;AAAA,EAC1D,mBAAA;AAAA,EACA,WAAA;AAAA,EACA,IAAA;AAAA,EACA,eAAA;AAAA,EACA,SAAA;AAAA,EACA,mBAAA;AAAA,EACA;AACF,CAAA,EAAgC;AAC9B,EAAA,IAAI,mBAAA,EAAqB;AACvB,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,uBACE,GAAA,CAAC,eAAA,EAAA,EACC,QAAA,kBAAA,IAAA,CAAC,WAAA,EAAA,EAAY,eAAe,GAAA,EAC1B,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,cAAA,EAAA,EAAe,SAAO,IAAA,EACrB,QAAA,kBAAA,GAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,OAAA,EAAS,WAAA,CAAY,IAAA,KAAS,IAAA,CAAK,MAAA;AAAA,QACnC,eAAA,EAAiB,eAAA;AAAA,QACjB,QAAA,EAAU,SAAA,IAAa,CAAC,IAAA,CAAK,MAAA,IAAU,mBAAA;AAAA,QACvC,YAAA,EAAW;AAAA;AAAA,KACb,EACF,CAAA;AAAA,IACC,wBAAA,oBAA4B,GAAA,CAAC,cAAA,EAAA,EAAgB,QAAA,EAAA,wBAAA,EAAyB;AAAA,GAAA,EACzE,CAAA,EACF,CAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"DataTableSelectHeader.js","sources":["../../../../src/components/DataTable/components/DataTableSelectHeader.tsx"],"sourcesContent":["import { Checkbox } from '@/components/Checkbox'\nimport { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from '@/components/Tooltip'\n\ntype SelectAllHeaderProps<TData extends object> = {\n readonly isBackendPagination: boolean\n readonly selectedIds: Set<string>\n readonly data: Array<TData>\n readonly handleSelectAll: (checked: boolean) => void\n readonly isLoading: boolean\n readonly isSelectionDisabled: boolean\n readonly selectionDisabledTooltip: string | undefined\n}\n\nexport function DataTableSelectHeader<TData extends object>({\n isBackendPagination,\n selectedIds,\n data,\n handleSelectAll,\n isLoading,\n isSelectionDisabled,\n selectionDisabledTooltip,\n}: SelectAllHeaderProps<TData>) {\n if (isBackendPagination) {\n return null\n }\n\n const allSelected = data.length > 0 && selectedIds.size === data.length\n const someSelected = selectedIds.size > 0 && !allSelected\n\n const checkbox = (\n <Checkbox\n checked={someSelected ? 'indeterminate' : allSelected}\n onCheckedChange={handleSelectAll}\n disabled={isLoading || !data.length || isSelectionDisabled}\n aria-label=\"Select all rows\"\n />\n )\n\n if (!selectionDisabledTooltip) {\n return checkbox\n }\n\n return (\n <TooltipProvider>\n <TooltipRoot delayDuration={200}>\n <TooltipTrigger asChild>\n <div className=\"inline-flex\">{checkbox}</div>\n </TooltipTrigger>\n <TooltipContent>{selectionDisabledTooltip}</TooltipContent>\n </TooltipRoot>\n </TooltipProvider>\n )\n}\n"],"names":[],"mappings":";;;;AAaO,SAAS,qBAAA,CAA4C;AAAA,EAC1D,mBAAA;AAAA,EACA,WAAA;AAAA,EACA,IAAA;AAAA,EACA,eAAA;AAAA,EACA,SAAA;AAAA,EACA,mBAAA;AAAA,EACA;AACF,CAAA,EAAgC;AAC9B,EAAA,IAAI,mBAAA,EAAqB;AACvB,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,cAAc,IAAA,CAAK,MAAA,GAAS,CAAA,IAAK,WAAA,CAAY,SAAS,IAAA,CAAK,MAAA;AACjE,EAAA,MAAM,YAAA,GAAe,WAAA,CAAY,IAAA,GAAO,CAAA,IAAK,CAAC,WAAA;AAE9C,EAAA,MAAM,QAAA,mBACJ,GAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,OAAA,EAAS,eAAe,eAAA,GAAkB,WAAA;AAAA,MAC1C,eAAA,EAAiB,eAAA;AAAA,MACjB,QAAA,EAAU,SAAA,IAAa,CAAC,IAAA,CAAK,MAAA,IAAU,mBAAA;AAAA,MACvC,YAAA,EAAW;AAAA;AAAA,GACb;AAGF,EAAA,IAAI,CAAC,wBAAA,EAA0B;AAC7B,IAAA,OAAO,QAAA;AAAA,EACT;AAEA,EAAA,uBACE,GAAA,CAAC,eAAA,EAAA,EACC,QAAA,kBAAA,IAAA,CAAC,WAAA,EAAA,EAAY,eAAe,GAAA,EAC1B,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,cAAA,EAAA,EAAe,SAAO,IAAA,EACrB,QAAA,kBAAA,GAAA,CAAC,SAAI,SAAA,EAAU,aAAA,EAAe,oBAAS,CAAA,EACzC,CAAA;AAAA,oBACA,GAAA,CAAC,kBAAgB,QAAA,EAAA,wBAAA,EAAyB;AAAA,GAAA,EAC5C,CAAA,EACF,CAAA;AAEJ;;;;"}
@@ -0,0 +1,19 @@
1
+ import { GroupingConfig, RowIdentifierFn } from '../types';
2
+ type UseGroupExpansionParams<TData> = {
3
+ grouping?: GroupingConfig<TData>;
4
+ data: Array<TData>;
5
+ getRowId: RowIdentifierFn<TData>;
6
+ };
7
+ type UseGroupExpansionResult = {
8
+ /** Whether the group identified by `groupId` is currently expanded. */
9
+ isGroupOpen: (groupId: string) => boolean;
10
+ /** Toggles the expanded state of the group identified by `groupId`. */
11
+ toggleGroup: (groupId: string) => void;
12
+ };
13
+ /**
14
+ * Encapsulates the expanded-state bookkeeping for grouped tables: tracks which
15
+ * groups are open (supporting both controlled and uncontrolled modes) and
16
+ * exposes helpers to query and toggle individual groups.
17
+ */
18
+ export declare function useGroupExpansion<TData>({ grouping, data, getRowId, }: UseGroupExpansionParams<TData>): UseGroupExpansionResult;
19
+ export {};
@@ -0,0 +1,34 @@
1
+ import * as React from 'react';
2
+
3
+ function useGroupExpansion({
4
+ grouping,
5
+ data,
6
+ getRowId
7
+ }) {
8
+ const [internalExpanded, setInternalExpanded] = React.useState(grouping?.defaultOpen ? true : {});
9
+ const expandedState = grouping?.open ?? internalExpanded;
10
+ const setExpandedState = grouping?.onOpenChange ?? setInternalExpanded;
11
+ const isGroupOpen = React.useCallback(
12
+ (groupId) => expandedState === true || typeof expandedState === "object" && expandedState[groupId] === true,
13
+ [expandedState]
14
+ );
15
+ const groupIds = React.useMemo(
16
+ () => grouping ? data.filter((item) => (grouping.getSubRows(item)?.length ?? 0) > 0).map(getRowId) : [],
17
+ [grouping, data, getRowId]
18
+ );
19
+ const toggleGroup = React.useCallback(
20
+ (groupId) => {
21
+ const updater = (prev) => {
22
+ const current = prev === true ? Object.fromEntries(groupIds.map((id) => [id, true])) : { ...prev };
23
+ current[groupId] = !current[groupId];
24
+ return current;
25
+ };
26
+ setExpandedState(updater);
27
+ },
28
+ [setExpandedState, groupIds]
29
+ );
30
+ return { isGroupOpen, toggleGroup };
31
+ }
32
+
33
+ export { useGroupExpansion };
34
+ //# sourceMappingURL=useGroupExpansion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useGroupExpansion.js","sources":["../../../../src/components/DataTable/hooks/useGroupExpansion.ts"],"sourcesContent":["import { ExpandedState, Updater } from '@tanstack/react-table'\nimport * as React from 'react'\n\nimport { GroupingConfig, RowIdentifierFn } from '../types'\n\ntype UseGroupExpansionParams<TData> = {\n grouping?: GroupingConfig<TData>\n data: Array<TData>\n getRowId: RowIdentifierFn<TData>\n}\n\ntype UseGroupExpansionResult = {\n /** Whether the group identified by `groupId` is currently expanded. */\n isGroupOpen: (groupId: string) => boolean\n /** Toggles the expanded state of the group identified by `groupId`. */\n toggleGroup: (groupId: string) => void\n}\n\n/**\n * Encapsulates the expanded-state bookkeeping for grouped tables: tracks which\n * groups are open (supporting both controlled and uncontrolled modes) and\n * exposes helpers to query and toggle individual groups.\n */\nexport function useGroupExpansion<TData>({\n grouping,\n data,\n getRowId,\n}: UseGroupExpansionParams<TData>): UseGroupExpansionResult {\n const [internalExpanded, setInternalExpanded] = React.useState<ExpandedState>(grouping?.defaultOpen ? true : {})\n\n const expandedState = grouping?.open ?? internalExpanded\n const setExpandedState = grouping?.onOpenChange ?? setInternalExpanded\n\n const isGroupOpen = React.useCallback(\n (groupId: string) =>\n expandedState === true || (typeof expandedState === 'object' && expandedState[groupId] === true),\n [expandedState],\n )\n\n const groupIds = React.useMemo(\n () => (grouping ? data.filter((item) => (grouping.getSubRows(item)?.length ?? 0) > 0).map(getRowId) : []),\n [grouping, data, getRowId],\n )\n\n const toggleGroup = React.useCallback(\n (groupId: string) => {\n const updater = (prev: ExpandedState): ExpandedState => {\n const current = prev === true ? Object.fromEntries(groupIds.map((id) => [id, true])) : { ...prev }\n current[groupId] = !current[groupId]\n return current\n }\n ;(setExpandedState as (u: Updater<ExpandedState>) => void)(updater)\n },\n [setExpandedState, groupIds],\n )\n\n return { isGroupOpen, toggleGroup }\n}\n"],"names":[],"mappings":";;AAuBO,SAAS,iBAAA,CAAyB;AAAA,EACvC,QAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA,EAA4D;AAC1D,EAAA,MAAM,CAAC,gBAAA,EAAkB,mBAAmB,CAAA,GAAI,KAAA,CAAM,SAAwB,QAAA,EAAU,WAAA,GAAc,IAAA,GAAO,EAAE,CAAA;AAE/G,EAAA,MAAM,aAAA,GAAgB,UAAU,IAAA,IAAQ,gBAAA;AACxC,EAAA,MAAM,gBAAA,GAAmB,UAAU,YAAA,IAAgB,mBAAA;AAEnD,EAAA,MAAM,cAAc,KAAA,CAAM,WAAA;AAAA,IACxB,CAAC,YACC,aAAA,KAAkB,IAAA,IAAS,OAAO,aAAA,KAAkB,QAAA,IAAY,aAAA,CAAc,OAAO,CAAA,KAAM,IAAA;AAAA,IAC7F,CAAC,aAAa;AAAA,GAChB;AAEA,EAAA,MAAM,WAAW,KAAA,CAAM,OAAA;AAAA,IACrB,MAAO,QAAA,GAAW,IAAA,CAAK,MAAA,CAAO,CAAC,UAAU,QAAA,CAAS,UAAA,CAAW,IAAI,CAAA,EAAG,UAAU,CAAA,IAAK,CAAC,EAAE,GAAA,CAAI,QAAQ,IAAI,EAAC;AAAA,IACvG,CAAC,QAAA,EAAU,IAAA,EAAM,QAAQ;AAAA,GAC3B;AAEA,EAAA,MAAM,cAAc,KAAA,CAAM,WAAA;AAAA,IACxB,CAAC,OAAA,KAAoB;AACnB,MAAA,MAAM,OAAA,GAAU,CAAC,IAAA,KAAuC;AACtD,QAAA,MAAM,UAAU,IAAA,KAAS,IAAA,GAAO,MAAA,CAAO,WAAA,CAAY,SAAS,GAAA,CAAI,CAAC,EAAA,KAAO,CAAC,IAAI,IAAI,CAAC,CAAC,CAAA,GAAI,EAAE,GAAG,IAAA,EAAK;AACjG,QAAA,OAAA,CAAQ,OAAO,CAAA,GAAI,CAAC,OAAA,CAAQ,OAAO,CAAA;AACnC,QAAA,OAAO,OAAA;AAAA,MACT,CAAA;AACC,MAAC,iBAAyD,OAAO,CAAA;AAAA,IACpE,CAAA;AAAA,IACA,CAAC,kBAAkB,QAAQ;AAAA,GAC7B;AAEA,EAAA,OAAO,EAAE,aAAa,WAAA,EAAY;AACpC;;;;"}
@@ -1,4 +1,4 @@
1
- import { ColumnDef, OnChangeFn, VisibilityState } from '@tanstack/react-table';
1
+ import { ColumnDef, ExpandedState, OnChangeFn, Row, VisibilityState } from '@tanstack/react-table';
2
2
  import { ReactNode } from 'react';
3
3
  export type User = {
4
4
  id: string;
@@ -107,7 +107,19 @@ export type BackendPaginationProps = {
107
107
  /** Callback when page changes */
108
108
  readonly onPageChange: (page: number) => void;
109
109
  } & BasePaginationProps;
110
- export type DataTableProps<TData> = {
110
+ export type GroupingConfig<TData> = {
111
+ /** Returns the children for a row. Return `undefined`/empty for leaf rows (no group). */
112
+ readonly getSubRows: (row: TData) => Array<TData> | undefined;
113
+ /** Whether all groups are expanded on first render (default: `false`, all collapsed). */
114
+ readonly defaultOpen?: boolean;
115
+ /** Controlled expanded state. When provided, you must manage it via `onOpenChange`. */
116
+ readonly open?: ExpandedState;
117
+ /** Callback when the expanded state changes (controlled mode). */
118
+ readonly onOpenChange?: OnChangeFn<ExpandedState>;
119
+ /** Renders the content of a group row (label, count, etc.). Defaults to an item count. */
120
+ readonly renderGroupRow?: (row: Row<TData>) => ReactNode;
121
+ };
122
+ type DataTableBaseProps<TData> = {
111
123
  /** Array of column definitions that describe the table structure */
112
124
  readonly columns: Array<ColumnDef<TData>>;
113
125
  /** Array of data items to be displayed in the table */
@@ -116,8 +128,6 @@ export type DataTableProps<TData> = {
116
128
  readonly columnVisibility?: ColumnVisibilityConfig;
117
129
  /** Whether the table is in a loading state */
118
130
  readonly isLoading?: boolean;
119
- /** Pagination configuration. If not provided, pagination is disabled */
120
- readonly pagination?: BasePaginationProps | BackendPaginationProps;
121
131
  /** Primary filters that appear directly above the table */
122
132
  readonly primaryFilters?: ReactNode;
123
133
  /** Secondary filters that appear in the filters dropdown */
@@ -154,13 +164,23 @@ export type DataTableProps<TData> = {
154
164
  readonly className?: string;
155
165
  /** Optional className for the table */
156
166
  readonly tableClassName?: string;
157
- } & (HasId<TData> extends true ? {
167
+ };
168
+ type IdProps<TData> = HasId<TData> extends true ? {
158
169
  /** Function to get unique identifier from a row. Not needed when data has 'id' property */
159
170
  readonly getRowId?: RowIdentifierFn<TData>;
160
171
  } : {
161
172
  /** Function to get unique identifier from a row. Required when data doesn't have 'id' property */
162
173
  readonly getRowId: RowIdentifierFn<TData>;
163
- });
174
+ };
175
+ type PaginatedVariant = {
176
+ readonly pagination?: BasePaginationProps | BackendPaginationProps;
177
+ readonly grouping?: never;
178
+ };
179
+ type GroupedVariant<TData> = {
180
+ readonly grouping: GroupingConfig<TData>;
181
+ readonly pagination?: never;
182
+ };
183
+ export type DataTableProps<TData> = DataTableBaseProps<TData> & IdProps<TData> & (PaginatedVariant | GroupedVariant<TData>);
164
184
  /**
165
185
  * Type helper to check if a type has an 'id' property
166
186
  */
@@ -171,3 +191,4 @@ export type HasId<T> = T extends {
171
191
  * Function to get a unique identifier from a row
172
192
  */
173
193
  export type RowIdentifierFn<T> = (row: T) => string;
194
+ export {};
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Recursively flattens a sub-row tree into its leaf items, discarding group
3
+ * nodes. Used so selection (counts, select-all, ids) operates over the actual
4
+ * selectable leaf rows rather than the group containers.
5
+ */
6
+ export declare function flattenLeaves<TData>(data: Array<TData>, getSubRows: (row: TData) => Array<TData> | undefined): Array<TData>;
@@ -0,0 +1,15 @@
1
+ function flattenLeaves(data, getSubRows) {
2
+ const leaves = [];
3
+ for (const item of data) {
4
+ const children = getSubRows(item);
5
+ if (children && children.length > 0) {
6
+ leaves.push(...flattenLeaves(children, getSubRows));
7
+ } else {
8
+ leaves.push(item);
9
+ }
10
+ }
11
+ return leaves;
12
+ }
13
+
14
+ export { flattenLeaves };
15
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sources":["../../../src/components/DataTable/utils.ts"],"sourcesContent":["/**\n * Recursively flattens a sub-row tree into its leaf items, discarding group\n * nodes. Used so selection (counts, select-all, ids) operates over the actual\n * selectable leaf rows rather than the group containers.\n */\nexport function flattenLeaves<TData>(\n data: Array<TData>,\n getSubRows: (row: TData) => Array<TData> | undefined,\n): Array<TData> {\n const leaves: Array<TData> = []\n for (const item of data) {\n const children = getSubRows(item)\n if (children && children.length > 0) {\n leaves.push(...flattenLeaves(children, getSubRows))\n } else {\n leaves.push(item)\n }\n }\n return leaves\n}\n"],"names":[],"mappings":"AAKO,SAAS,aAAA,CACd,MACA,UAAA,EACc;AACd,EAAA,MAAM,SAAuB,EAAC;AAC9B,EAAA,KAAA,MAAW,QAAQ,IAAA,EAAM;AACvB,IAAA,MAAM,QAAA,GAAW,WAAW,IAAI,CAAA;AAChC,IAAA,IAAI,QAAA,IAAY,QAAA,CAAS,MAAA,GAAS,CAAA,EAAG;AACnC,MAAA,MAAA,CAAO,IAAA,CAAK,GAAG,aAAA,CAAc,QAAA,EAAU,UAAU,CAAC,CAAA;AAAA,IACpD,CAAA,MAAO;AACL,MAAA,MAAA,CAAO,KAAK,IAAI,CAAA;AAAA,IAClB;AAAA,EACF;AACA,EAAA,OAAO,MAAA;AACT;;;;"}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "periplo-ui",
3
3
  "description": "IATI UI library",
4
4
  "private": false,
5
- "version": "4.5.0",
5
+ "version": "4.6.1",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
8
8
  "types": "dist/index.d.ts",