@wallarm-org/design-system 1.28.2-rc-fix-table-window-scroll-root.2 → 1.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/dist/components/Avatar/Avatar.d.ts +32 -0
  2. package/dist/components/Avatar/Avatar.figma.d.ts +1 -0
  3. package/dist/components/Avatar/Avatar.figma.js +27 -0
  4. package/dist/components/Avatar/Avatar.js +42 -0
  5. package/dist/components/Avatar/AvatarFallback.d.ts +11 -0
  6. package/dist/components/Avatar/AvatarFallback.js +25 -0
  7. package/dist/components/Avatar/AvatarImage.d.ts +9 -0
  8. package/dist/components/Avatar/AvatarImage.js +18 -0
  9. package/dist/components/Avatar/AvatarOverlay.d.ts +11 -0
  10. package/dist/components/Avatar/AvatarOverlay.js +22 -0
  11. package/dist/components/Avatar/classes.d.ts +6 -0
  12. package/dist/components/Avatar/classes.js +17 -0
  13. package/dist/components/Avatar/index.d.ts +4 -0
  14. package/dist/components/Avatar/index.js +4 -0
  15. package/dist/components/FileUpload/FileUploadTrigger.d.ts +5 -0
  16. package/dist/components/FileUpload/FileUploadTrigger.js +18 -5
  17. package/dist/components/FileUpload/index.d.ts +1 -0
  18. package/dist/components/FileUpload/index.js +1 -0
  19. package/dist/components/FileUpload/useFilePreviewUrl.d.ts +6 -0
  20. package/dist/components/FileUpload/useFilePreviewUrl.js +14 -0
  21. package/dist/components/FilterDropdown/FilterDropdown.d.ts +58 -0
  22. package/dist/components/FilterDropdown/FilterDropdown.js +168 -0
  23. package/dist/components/FilterDropdown/FilterDropdownAllOption.d.ts +14 -0
  24. package/dist/components/FilterDropdown/FilterDropdownAllOption.js +25 -0
  25. package/dist/components/FilterDropdown/FilterDropdownClear.d.ts +12 -0
  26. package/dist/components/FilterDropdown/FilterDropdownClear.js +33 -0
  27. package/dist/components/FilterDropdown/FilterDropdownContent.d.ts +13 -0
  28. package/dist/components/FilterDropdown/FilterDropdownContent.js +57 -0
  29. package/dist/components/FilterDropdown/FilterDropdownContext.d.ts +60 -0
  30. package/dist/components/FilterDropdown/FilterDropdownContext.js +21 -0
  31. package/dist/components/FilterDropdown/FilterDropdownEmpty.d.ts +9 -0
  32. package/dist/components/FilterDropdown/FilterDropdownEmpty.js +21 -0
  33. package/dist/components/FilterDropdown/FilterDropdownFooter.d.ts +9 -0
  34. package/dist/components/FilterDropdown/FilterDropdownFooter.js +19 -0
  35. package/dist/components/FilterDropdown/FilterDropdownFooterClear.d.ts +9 -0
  36. package/dist/components/FilterDropdown/FilterDropdownFooterClear.js +40 -0
  37. package/dist/components/FilterDropdown/FilterDropdownGroup.d.ts +5 -0
  38. package/dist/components/FilterDropdown/FilterDropdownGroup.js +8 -0
  39. package/dist/components/FilterDropdown/FilterDropdownGroupLabel.d.ts +4 -0
  40. package/dist/components/FilterDropdown/FilterDropdownGroupLabel.js +8 -0
  41. package/dist/components/FilterDropdown/FilterDropdownOption.d.ts +15 -0
  42. package/dist/components/FilterDropdown/FilterDropdownOption.js +32 -0
  43. package/dist/components/FilterDropdown/FilterDropdownSearch.d.ts +12 -0
  44. package/dist/components/FilterDropdown/FilterDropdownSearch.js +38 -0
  45. package/dist/components/FilterDropdown/FilterDropdownSelected.d.ts +16 -0
  46. package/dist/components/FilterDropdown/FilterDropdownSelected.js +38 -0
  47. package/dist/components/FilterDropdown/FilterDropdownTrigger.d.ts +16 -0
  48. package/dist/components/FilterDropdown/FilterDropdownTrigger.js +139 -0
  49. package/dist/components/FilterDropdown/classes.d.ts +16 -0
  50. package/dist/components/FilterDropdown/classes.js +43 -0
  51. package/dist/components/FilterDropdown/index.d.ts +15 -0
  52. package/dist/components/FilterDropdown/index.js +15 -0
  53. package/dist/components/FilterDropdown/lib/index.d.ts +1 -0
  54. package/dist/components/FilterDropdown/lib/index.js +1 -0
  55. package/dist/components/FilterDropdown/lib/values.d.ts +59 -0
  56. package/dist/components/FilterDropdown/lib/values.js +58 -0
  57. package/dist/components/NavRail/NavRailItem.d.ts +1 -1
  58. package/dist/components/NavRail/NavRailItem.js +9 -12
  59. package/dist/components/Select/Select.d.ts +1 -1
  60. package/dist/components/Select/SelectContent.d.ts +2 -1
  61. package/dist/components/Select/SelectContent.js +3 -2
  62. package/dist/components/Select/SelectEmptyState.d.ts +1 -0
  63. package/dist/components/Select/SelectEmptyState.js +2 -1
  64. package/dist/components/Select/SelectFooter.d.ts +7 -2
  65. package/dist/components/Select/SelectFooter.js +6 -3
  66. package/dist/components/Select/SelectGroup.d.ts +8 -2
  67. package/dist/components/Select/SelectGroup.js +3 -2
  68. package/dist/components/Select/SelectGroupLabel.d.ts +5 -2
  69. package/dist/components/Select/SelectGroupLabel.js +2 -2
  70. package/dist/components/Select/SelectOption.js +1 -1
  71. package/dist/components/Select/SelectOptionHint.d.ts +11 -0
  72. package/dist/components/Select/SelectOptionHint.js +15 -0
  73. package/dist/components/Select/SelectPositioner.d.ts +13 -3
  74. package/dist/components/Select/SelectPositioner.js +2 -1
  75. package/dist/components/Select/SelectSearchInput.d.ts +5 -2
  76. package/dist/components/Select/SelectSearchInput.js +11 -1
  77. package/dist/components/Select/classes.d.ts +3 -0
  78. package/dist/components/Select/classes.js +13 -0
  79. package/dist/components/Select/index.d.ts +4 -2
  80. package/dist/components/Select/index.js +2 -0
  81. package/dist/components/Table/TableBody/TableBodyVirtualizedCore.js +1 -1
  82. package/dist/components/Table/TableBody/TableBodyVirtualizedWindow.js +8 -26
  83. package/dist/components/Table/TableInner/TableInnerWindow.js +2 -7
  84. package/dist/components/Table/hooks/index.d.ts +0 -1
  85. package/dist/components/Table/hooks/index.js +0 -1
  86. package/dist/components/Table/hooks/infiniteScroll/useInfiniteScroll.d.ts +2 -4
  87. package/dist/components/Table/hooks/infiniteScroll/useInfiniteScroll.js +1 -4
  88. package/dist/components/Table/hooks/infiniteScroll/usePrependScrollAnchor.d.ts +1 -4
  89. package/dist/components/Table/hooks/infiniteScroll/usePrependScrollAnchor.js +11 -13
  90. package/dist/components/Table/hooks/infiniteScroll/useScrollEdge.d.ts +1 -4
  91. package/dist/components/Table/hooks/infiniteScroll/useScrollEdge.js +18 -6
  92. package/dist/components/Table/lib/index.d.ts +0 -1
  93. package/dist/components/Table/lib/index.js +0 -1
  94. package/dist/components/Table/types.d.ts +1 -4
  95. package/dist/index.d.ts +4 -2
  96. package/dist/index.js +4 -2
  97. package/dist/metadata/components.json +16122 -9667
  98. package/dist/theme/semantic.css +2 -0
  99. package/dist/utils/getInitials.d.ts +5 -0
  100. package/dist/utils/getInitials.js +13 -0
  101. package/package.json +1 -1
  102. package/dist/components/Table/hooks/useWindowScrollRoot.d.ts +0 -14
  103. package/dist/components/Table/hooks/useWindowScrollRoot.js +0 -35
  104. package/dist/components/Table/lib/scrollRoot.d.ts +0 -24
  105. package/dist/components/Table/lib/scrollRoot.js +0 -22
@@ -0,0 +1,32 @@
1
+ import type { ButtonHTMLAttributes, FC, HTMLAttributes, Ref } from 'react';
2
+ import { Avatar as ArkAvatar } from '@ark-ui/react/avatar';
3
+ import type { VariantProps } from 'class-variance-authority';
4
+ import { type TestableProps } from '../../utils/testId';
5
+ import { avatarVariants } from './classes';
6
+ export type AvatarStatusChangeDetails = ArkAvatar.StatusChangeDetails;
7
+ type AvatarButtonAttributes = Pick<ButtonHTMLAttributes<HTMLButtonElement>, 'disabled' | 'type' | 'name' | 'value' | 'form'>;
8
+ interface AvatarBaseProps extends HTMLAttributes<HTMLElement>, VariantProps<typeof avatarVariants>, TestableProps {
9
+ ref?: Ref<HTMLElement>;
10
+ onStatusChange?: (details: AvatarStatusChangeDetails) => void;
11
+ }
12
+ type AvatarRootProps = ({
13
+ /** Root tag. 'button' makes the avatar itself interactive (e.g. inside FileUploadTrigger asChild). */
14
+ as?: 'span';
15
+ /** Render the single child element as the root instead of `as`; takes precedence over it. */
16
+ asChild?: false;
17
+ } & {
18
+ [K in keyof AvatarButtonAttributes]?: never;
19
+ }) | ({
20
+ as: 'button';
21
+ asChild?: false;
22
+ } & AvatarButtonAttributes) | ({
23
+ as?: 'span' | 'button';
24
+ asChild: true;
25
+ } & AvatarButtonAttributes);
26
+ export type AvatarProps = AvatarBaseProps & AvatarRootProps;
27
+ /**
28
+ * A user's photo on a rounded plate, falling back to initials or an icon while the photo loads,
29
+ * when it fails, or when there is none.
30
+ */
31
+ export declare const Avatar: FC<AvatarProps>;
32
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import code_connect from "@figma/code-connect";
3
+ import { Avatar, AvatarFallback, AvatarImage } from "./index.js";
4
+ const AVATAR_URL = 'https://www.figma.com/design/VKb5gW46uSGw0rqrhZsbXT/WADS-Components?node-id=12336-5504';
5
+ code_connect.connect(Avatar, AVATAR_URL, {
6
+ variant: {
7
+ Photo: 'On'
8
+ },
9
+ example: ()=>/*#__PURE__*/ jsxs(Avatar, {
10
+ children: [
11
+ /*#__PURE__*/ jsx(AvatarImage, {
12
+ src: "/me.png"
13
+ }),
14
+ /*#__PURE__*/ jsx(AvatarFallback, {
15
+ name: "Ada Lovelace"
16
+ })
17
+ ]
18
+ })
19
+ });
20
+ code_connect.connect(Avatar, AVATAR_URL, {
21
+ variant: {
22
+ Photo: 'Off'
23
+ },
24
+ example: ()=>/*#__PURE__*/ jsx(Avatar, {
25
+ children: /*#__PURE__*/ jsx(AvatarFallback, {})
26
+ })
27
+ });
@@ -0,0 +1,42 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { Avatar } from "@ark-ui/react/avatar";
3
+ import { cn } from "../../utils/cn.js";
4
+ import { TestIdProvider } from "../../utils/testId.js";
5
+ import { avatarVariants } from "./classes.js";
6
+ const Avatar_Avatar = ({ ref, id, size, as: Tag = 'span', asChild = false, disabled, type, name, value, form, onStatusChange, className, children, 'data-testid': testId, ...props })=>{
7
+ const role = props.role ?? (props['aria-label'] && !asChild && 'button' !== Tag ? 'img' : void 0);
8
+ const buttonAttributes = asChild || 'button' === Tag ? Object.fromEntries(Object.entries({
9
+ disabled,
10
+ name,
11
+ value,
12
+ form
13
+ }).filter(([, v])=>void 0 !== v)) : {};
14
+ return /*#__PURE__*/ jsx(TestIdProvider, {
15
+ value: testId,
16
+ children: /*#__PURE__*/ jsx(Avatar.Root, {
17
+ "data-slot": "avatar",
18
+ ...props,
19
+ ids: id ? {
20
+ root: id
21
+ } : void 0,
22
+ ...buttonAttributes,
23
+ ref: ref,
24
+ role: role,
25
+ ...asChild && type ? {
26
+ type
27
+ } : {},
28
+ "data-testid": testId,
29
+ onStatusChange: onStatusChange,
30
+ className: cn(avatarVariants({
31
+ size
32
+ }), className),
33
+ asChild: true,
34
+ children: asChild ? children : /*#__PURE__*/ jsx(Tag, {
35
+ type: 'button' === Tag ? type ?? 'button' : void 0,
36
+ children: children
37
+ })
38
+ })
39
+ });
40
+ };
41
+ Avatar_Avatar.displayName = 'Avatar';
42
+ export { Avatar_Avatar as Avatar };
@@ -0,0 +1,11 @@
1
+ import type { ComponentType, FC, HTMLAttributes, Ref } from 'react';
2
+ import { type SvgIconProps } from '../../icons';
3
+ export interface AvatarFallbackProps extends HTMLAttributes<HTMLSpanElement> {
4
+ ref?: Ref<HTMLSpanElement>;
5
+ /** Initials are taken from this: first letter of the first and of the last word. */
6
+ name?: string;
7
+ /** Shown when there are no children and `name` gives no initials. */
8
+ icon?: ComponentType<SvgIconProps>;
9
+ }
10
+ /** Shown while the photo loads, when it fails, and when there is none. */
11
+ export declare const AvatarFallback: FC<AvatarFallbackProps>;
@@ -0,0 +1,25 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { Avatar } from "@ark-ui/react/avatar";
3
+ import { UserRound } from "../../icons/index.js";
4
+ import { cn } from "../../utils/cn.js";
5
+ import { getInitials } from "../../utils/getInitials.js";
6
+ import { useTestId } from "../../utils/testId.js";
7
+ import { avatarFallbackClassNames } from "./classes.js";
8
+ const AvatarFallback = ({ ref, name, icon: Icon = UserRound, className, children, ...props })=>{
9
+ const testId = useTestId('fallback');
10
+ const initials = name ? getInitials(name) : '';
11
+ const showsInitials = null == children && '' !== initials;
12
+ return /*#__PURE__*/ jsx(Avatar.Fallback, {
13
+ "aria-hidden": true,
14
+ ...props,
15
+ ref: ref,
16
+ "data-slot": "avatar-fallback",
17
+ "data-testid": testId,
18
+ className: cn(avatarFallbackClassNames, showsInitials && 'text-text-primary', className),
19
+ children: children ?? (initials || /*#__PURE__*/ jsx(Icon, {
20
+ size: "md"
21
+ }))
22
+ });
23
+ };
24
+ AvatarFallback.displayName = 'AvatarFallback';
25
+ export { AvatarFallback };
@@ -0,0 +1,9 @@
1
+ import type { FC, ImgHTMLAttributes, Ref } from 'react';
2
+ export interface AvatarImageProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, 'id'> {
3
+ ref?: Ref<HTMLImageElement>;
4
+ }
5
+ /**
6
+ * The photo. Always render it — Ark tracks `src` changes on the mounted element — and pass
7
+ * `src={undefined}` for "no photo". Decorative by default (`alt=''`).
8
+ */
9
+ export declare const AvatarImage: FC<AvatarImageProps>;
@@ -0,0 +1,18 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { Avatar } from "@ark-ui/react/avatar";
3
+ import { cn } from "../../utils/cn.js";
4
+ import { useTestId } from "../../utils/testId.js";
5
+ import { avatarImageClassNames } from "./classes.js";
6
+ const AvatarImage = ({ ref, alt = '', className, ...props })=>{
7
+ const testId = useTestId('image');
8
+ return /*#__PURE__*/ jsx(Avatar.Image, {
9
+ ...props,
10
+ ref: ref,
11
+ alt: alt,
12
+ "data-slot": "avatar-image",
13
+ "data-testid": testId,
14
+ className: cn(avatarImageClassNames, className)
15
+ });
16
+ };
17
+ AvatarImage.displayName = 'AvatarImage';
18
+ export { AvatarImage };
@@ -0,0 +1,11 @@
1
+ import type { FC, HTMLAttributes, Ref } from 'react';
2
+ export interface AvatarOverlayProps extends HTMLAttributes<HTMLSpanElement> {
3
+ ref?: Ref<HTMLSpanElement>;
4
+ /** Keep it shown regardless of hover — e.g. a `Loader` while the new photo uploads. */
5
+ visible?: boolean;
6
+ }
7
+ /**
8
+ * The "change photo" affordance, shown on hover and keyboard focus of an interactive
9
+ * (`as='button'`) avatar. Over a photo it adds a wash; over the fallback it swaps the icon.
10
+ */
11
+ export declare const AvatarOverlay: FC<AvatarOverlayProps>;
@@ -0,0 +1,22 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { RefreshCcw } from "../../icons/index.js";
3
+ import { cn } from "../../utils/cn.js";
4
+ import { useTestId } from "../../utils/testId.js";
5
+ import { avatarOverlayClassNames } from "./classes.js";
6
+ const AvatarOverlay = ({ ref, visible = false, className, children, ...props })=>{
7
+ const testId = useTestId('overlay');
8
+ return /*#__PURE__*/ jsx("span", {
9
+ ...props,
10
+ ref: ref,
11
+ "aria-hidden": true,
12
+ "data-slot": "avatar-overlay",
13
+ "data-visible": visible ? '' : void 0,
14
+ "data-testid": testId,
15
+ className: cn(avatarOverlayClassNames, className),
16
+ children: children ?? /*#__PURE__*/ jsx(RefreshCcw, {
17
+ size: "md"
18
+ })
19
+ });
20
+ };
21
+ AvatarOverlay.displayName = 'AvatarOverlay';
22
+ export { AvatarOverlay };
@@ -0,0 +1,6 @@
1
+ export declare const avatarVariants: (props?: ({
2
+ size?: "sm" | "xs" | null | undefined;
3
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
4
+ export declare const avatarImageClassNames: string;
5
+ export declare const avatarFallbackClassNames: string;
6
+ export declare const avatarOverlayClassNames: string;
@@ -0,0 +1,17 @@
1
+ import { cva } from "class-variance-authority";
2
+ import { cn } from "../../utils/cn.js";
3
+ const avatarVariants = cva(cn('group/avatar inline-grid shrink-0 place-items-center overflow-hidden align-middle *:col-start-1 *:row-start-1', 'border border-border-primary bg-states-primary-hover text-icon-primary font-sans font-medium', 'branded:border-border-brand branded:bg-states-brand-hover branded:text-icon-brand', 'has-[>img[data-state=visible]]:border-0', 'hover:not-disabled:[&:has(>[data-slot=avatar-overlay])>[data-slot=avatar-fallback]]:invisible', 'focus-visible:[&:has(>[data-slot=avatar-overlay])>[data-slot=avatar-fallback]]:invisible', '[&:has(>[data-slot=avatar-overlay][data-visible])>[data-slot=avatar-fallback]]:invisible', 'enabled:cursor-pointer disabled:cursor-not-allowed', 'focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-focus-primary'), {
4
+ variants: {
5
+ size: {
6
+ xs: 'size-24 rounded-8 text-2xs',
7
+ sm: 'size-32 rounded-12 text-xs'
8
+ }
9
+ },
10
+ defaultVariants: {
11
+ size: 'sm'
12
+ }
13
+ });
14
+ const avatarImageClassNames = cn('size-full object-cover data-[state=hidden]:hidden');
15
+ const avatarFallbackClassNames = cn('inline-flex items-center justify-center select-none data-[state=hidden]:hidden');
16
+ const avatarOverlayClassNames = cn('hidden size-full place-items-center text-icon-primary', 'group-hover/avatar:grid group-focus-visible/avatar:grid group-disabled/avatar:not-data-[visible]:hidden', 'data-[visible]:grid', 'group-has-[>img[data-state=visible]]/avatar:bg-component-avatar-overlay');
17
+ export { avatarFallbackClassNames, avatarImageClassNames, avatarOverlayClassNames, avatarVariants };
@@ -0,0 +1,4 @@
1
+ export { Avatar, type AvatarProps, type AvatarStatusChangeDetails } from './Avatar';
2
+ export { AvatarFallback, type AvatarFallbackProps } from './AvatarFallback';
3
+ export { AvatarImage, type AvatarImageProps } from './AvatarImage';
4
+ export { AvatarOverlay, type AvatarOverlayProps } from './AvatarOverlay';
@@ -0,0 +1,4 @@
1
+ export { Avatar } from "./Avatar.js";
2
+ export { AvatarFallback } from "./AvatarFallback.js";
3
+ export { AvatarImage } from "./AvatarImage.js";
4
+ export { AvatarOverlay } from "./AvatarOverlay.js";
@@ -4,5 +4,10 @@ export type FileUploadTriggerProps = ButtonProps;
4
4
  /**
5
5
  * "Select file" button — the compact picker for when space is tight. Takes every
6
6
  * `Button` prop (defaults: primary / brand / large). Hidden (read-only) and blocked like the Area.
7
+ *
8
+ * `asChild`: no Button is rendered. The single child element (e.g. `<Avatar as='button'>`)
9
+ * becomes the trigger and receives `data-slot`, `data-testid`, `aria-describedby`, `disabled` and the
10
+ * Ark trigger props. Button-only props (`variant`, `color`, `size`, `loading`, `fullWidth`, `as`) are
11
+ * ignored. Pass `disabled` / `aria-describedby` here, not on the child — a child value wins the merge.
7
12
  */
8
13
  export declare const FileUploadTrigger: FC<FileUploadTriggerProps>;
@@ -4,7 +4,7 @@ import { Share } from "../../icons/index.js";
4
4
  import { useTestId } from "../../utils/testId.js";
5
5
  import { Button } from "../Button/index.js";
6
6
  import { useRequiredFileUploadRootContext } from "./FileUploadContext.js";
7
- const FileUploadTrigger = ({ children, disabled, 'data-testid': testIdProp, 'aria-describedby': describedByProp, ...props })=>{
7
+ const FileUploadTrigger = ({ children, disabled, asChild = false, 'data-testid': testIdProp, 'aria-describedby': describedByProp, ...props })=>{
8
8
  const ctx = useRequiredFileUploadRootContext('FileUploadTrigger');
9
9
  const testId = useTestId('trigger', testIdProp);
10
10
  if (ctx.pickerHidden) return null;
@@ -12,6 +12,22 @@ const FileUploadTrigger = ({ children, disabled, 'data-testid': testIdProp, 'ari
12
12
  describedByProp,
13
13
  ctx.rejections.length ? ctx.errorId : void 0
14
14
  ].filter(Boolean).join(' ') || void 0;
15
+ const shared = {
16
+ 'aria-describedby': describedBy,
17
+ 'data-slot': 'file-upload-trigger',
18
+ 'data-testid': testId,
19
+ disabled: disabled || ctx.pickerBlocked
20
+ };
21
+ if (asChild) {
22
+ const { variant, color, size, loading, fullWidth, as, ref, ...rest } = props;
23
+ return /*#__PURE__*/ jsx(FileUpload.Trigger, {
24
+ ...rest,
25
+ ...shared,
26
+ ref: ref,
27
+ asChild: true,
28
+ children: children
29
+ });
30
+ }
15
31
  return /*#__PURE__*/ jsx(FileUpload.Trigger, {
16
32
  asChild: true,
17
33
  children: /*#__PURE__*/ jsx(Button, {
@@ -19,10 +35,7 @@ const FileUploadTrigger = ({ children, disabled, 'data-testid': testIdProp, 'ari
19
35
  color: "brand",
20
36
  size: "large",
21
37
  ...props,
22
- "aria-describedby": describedBy,
23
- "data-slot": "file-upload-trigger",
24
- "data-testid": testId,
25
- disabled: disabled || ctx.pickerBlocked,
38
+ ...shared,
26
39
  children: children ?? /*#__PURE__*/ jsxs(Fragment, {
27
40
  children: [
28
41
  /*#__PURE__*/ jsx(Share, {}),
@@ -9,3 +9,4 @@ export { FileUploadItemGroup, type FileUploadItemGroupProps } from './FileUpload
9
9
  export { FileUploadItemReplaceTrigger, type FileUploadItemReplaceTriggerProps, } from './FileUploadItemReplaceTrigger';
10
10
  export { FileUploadTrigger, type FileUploadTriggerProps } from './FileUploadTrigger';
11
11
  export type { FileUploadErrorCode, FileUploadItemFile, FileUploadRejection } from './types';
12
+ export { useFilePreviewUrl } from './useFilePreviewUrl';
@@ -8,3 +8,4 @@ export { FileUploadItemDeleteTrigger } from "./FileUploadItemDeleteTrigger.js";
8
8
  export { FileUploadItemGroup } from "./FileUploadItemGroup.js";
9
9
  export { FileUploadItemReplaceTrigger } from "./FileUploadItemReplaceTrigger.js";
10
10
  export { FileUploadTrigger } from "./FileUploadTrigger.js";
11
+ export { useFilePreviewUrl } from "./useFilePreviewUrl.js";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * An object URL for previewing a picked file (e.g. in `AvatarImage`), revoked when the file
3
+ * changes or the component unmounts. The effect manages the blob URL's lifetime — an external
4
+ * resource, not derived state.
5
+ */
6
+ export declare const useFilePreviewUrl: (file?: File | null) => string | undefined;
@@ -0,0 +1,14 @@
1
+ import { useEffect, useState } from "react";
2
+ const useFilePreviewUrl = (file)=>{
3
+ const [url, setUrl] = useState();
4
+ useEffect(()=>{
5
+ if (!file) return void setUrl(void 0);
6
+ const next = URL.createObjectURL(file);
7
+ setUrl(next);
8
+ return ()=>URL.revokeObjectURL(next);
9
+ }, [
10
+ file
11
+ ]);
12
+ return url;
13
+ };
14
+ export { useFilePreviewUrl };
@@ -0,0 +1,58 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type CollectionItem, type ListCollection } from '@ark-ui/react/collection';
3
+ import type { TestableProps } from '../../utils/testId';
4
+ import { type SelectProps } from '../Select/Select';
5
+ import { type UseSelectSearchOptions } from '../Select/useSelectSearch';
6
+ export interface FilterDropdownValueChangeDetails<T extends CollectionItem = CollectionItem> {
7
+ /** Picked values; `[]` means unset («All» in single mode). */
8
+ value: string[];
9
+ items: T[];
10
+ }
11
+ export interface FilterDropdownOpenChangeDetails {
12
+ open: boolean;
13
+ value: string[];
14
+ }
15
+ /**
16
+ * Select props FilterDropdown owns itself: the value model (alias and sentinel values must never
17
+ * reach the consumer), the highlight (reset when the query changes), and form wiring (the hidden
18
+ * select would submit internal values).
19
+ */
20
+ type FilterDropdownOmittedSelectProps = 'collection' | 'multiple' | 'value' | 'defaultValue' | 'onValueChange' | 'onOpenChange' | 'highlightedValue' | 'defaultHighlightedValue' | 'onHighlightChange' | 'onSelect' | 'deselectable' | 'name' | 'form' | 'loading' | 'children';
21
+ export type FilterDropdownProps<T extends CollectionItem = CollectionItem> = Omit<SelectProps<T>, FilterDropdownOmittedSelectProps> & TestableProps & {
22
+ /** Attribute name: trigger text in multi mode, the trigger's aria-label, «Clear {label}». */
23
+ label: string;
24
+ /**
25
+ * Single mode: trigger text while unset and the default text of `FilterDropdownAllOption`.
26
+ * @default 'All'
27
+ */
28
+ allLabel?: string;
29
+ /** Items to pick from. `groupBy` is supported. */
30
+ collection: ListCollection<T>;
31
+ /** Checkbox mode: several values, OR-ed, the menu stays open. */
32
+ multiple?: boolean;
33
+ /** Controlled picked values; `[]` = unset. */
34
+ value?: string[];
35
+ defaultValue?: string[];
36
+ onValueChange?: (details: FilterDropdownValueChangeDetails<T>) => void;
37
+ onOpenChange?: (details: FilterDropdownOpenChangeDetails) => void;
38
+ /**
39
+ * Item count from which the search field and (multi) the "Selected" section appear. Counts
40
+ * the collection's own items only.
41
+ * @default 8
42
+ */
43
+ searchThreshold?: number;
44
+ /** Overrides the default case-insensitive match on the item label. */
45
+ filterFn?: UseSelectSearchOptions<T>['filterFn'];
46
+ /**
47
+ * Multi mode: pin the picked values under «Selected» on top of the list (from
48
+ * `searchThreshold` items, hidden while searching). Render `FilterDropdownSelected` for it.
49
+ * @default true
50
+ */
51
+ selectedOnTop?: boolean;
52
+ children?: ReactNode;
53
+ };
54
+ export declare function FilterDropdown<T extends CollectionItem>({ label, allLabel, collection, multiple, value: valueProp, defaultValue, onValueChange, open: openProp, defaultOpen, onOpenChange, searchThreshold, filterFn, selectedOnTop, disabled, className, children, 'data-testid': testId, ...props }: FilterDropdownProps<T>): import("react").JSX.Element;
55
+ export declare namespace FilterDropdown {
56
+ var displayName: string;
57
+ }
58
+ export {};
@@ -0,0 +1,168 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useMemo, useRef, useState } from "react";
3
+ import { createListCollection } from "@ark-ui/react/collection";
4
+ import { cn } from "../../utils/cn.js";
5
+ import { Select } from "../Select/Select.js";
6
+ import { useSelectSearch } from "../Select/useSelectSearch.js";
7
+ import { FilterDropdownContext } from "./FilterDropdownContext.js";
8
+ import { createAliasItem, createAllItem, isSpecialItem, toConsumerValue, toInternalValue } from "./lib/index.js";
9
+ const DEFAULT_SEARCH_THRESHOLD = 8;
10
+ const EMPTY = [];
11
+ const FilterDropdown = ({ label, allLabel = 'All', collection, multiple = false, value: valueProp, defaultValue, onValueChange, open: openProp, defaultOpen, onOpenChange, searchThreshold = DEFAULT_SEARCH_THRESHOLD, filterFn, selectedOnTop = true, disabled = false, className, children, 'data-testid': testId, ...props })=>{
12
+ const [valueState, setValueState] = useState(defaultValue ?? EMPTY);
13
+ const value = valueProp ?? valueState;
14
+ const [openState, setOpenState] = useState(defaultOpen ?? false);
15
+ const open = openProp ?? openState;
16
+ const baseCollection = collection;
17
+ const searchOptions = useMemo(()=>({
18
+ filterFn: filterFn
19
+ }), [
20
+ filterFn
21
+ ]);
22
+ const { searchValue: query, onSearchChange, filteredCollection } = useSelectSearch(baseCollection, searchOptions);
23
+ const [snapshot, setSnapshot] = useState(value);
24
+ const [lockedWidth, setLockedWidth] = useState();
25
+ const [highlightState, setHighlightState] = useState(null);
26
+ const [resetHighlight, setResetHighlight] = useState(false);
27
+ const [prevOpen, setPrevOpen] = useState(open);
28
+ if (open !== prevOpen) {
29
+ setPrevOpen(open);
30
+ onSearchChange('');
31
+ setLockedWidth(void 0);
32
+ setResetHighlight(false);
33
+ if (open) setSnapshot(value);
34
+ }
35
+ const selectedValues = open ? snapshot : value;
36
+ const triggerRef = useRef(null);
37
+ const contentRef = useRef(null);
38
+ const isSearchable = baseCollection.size >= searchThreshold;
39
+ const isSearchActive = isSearchable && query.length > 0;
40
+ const showAll = !multiple && !isSearchActive;
41
+ const showSelected = selectedOnTop && multiple && isSearchable && !isSearchActive && selectedValues.length > 0;
42
+ const allItem = useMemo(()=>createAllItem(allLabel), [
43
+ allLabel
44
+ ]);
45
+ const groups = useMemo(()=>filteredCollection.group(), [
46
+ filteredCollection
47
+ ]);
48
+ const aliasItems = useMemo(()=>showSelected ? baseCollection.findMany(selectedValues).map((item)=>createAliasItem(item, baseCollection)) : [], [
49
+ showSelected,
50
+ baseCollection,
51
+ selectedValues
52
+ ]);
53
+ const arkCollection = useMemo(()=>createListCollection({
54
+ items: [
55
+ ...showAll ? [
56
+ allItem
57
+ ] : [],
58
+ ...aliasItems,
59
+ ...groups.flatMap(([, items])=>items)
60
+ ],
61
+ itemToValue: (item)=>isSpecialItem(item) ? item.value : baseCollection.getItemValue(item) ?? '',
62
+ itemToString: (item)=>isSpecialItem(item) ? item.label : baseCollection.stringifyItem(item) ?? '',
63
+ isItemDisabled: (item)=>{
64
+ if (!isSpecialItem(item)) return baseCollection.getItemDisabled(item);
65
+ return item.source ? baseCollection.getItemDisabled(item.source) : false;
66
+ }
67
+ }), [
68
+ showAll,
69
+ allItem,
70
+ aliasItems,
71
+ groups,
72
+ baseCollection
73
+ ]);
74
+ const aliased = useMemo(()=>new Set(aliasItems.map((item)=>baseCollection.getItemValue(item.source ?? null) ?? '')), [
75
+ aliasItems,
76
+ baseCollection
77
+ ]);
78
+ const internalValue = useMemo(()=>toInternalValue(value, {
79
+ multiple,
80
+ aliased,
81
+ hasAll: showAll
82
+ }), [
83
+ value,
84
+ multiple,
85
+ aliased,
86
+ showAll
87
+ ]);
88
+ const highlightedValue = resetHighlight ? arkCollection.firstValue : highlightState;
89
+ const commit = (next)=>{
90
+ if (void 0 === valueProp) setValueState(next);
91
+ onValueChange?.({
92
+ value: next,
93
+ items: collection.findMany(next)
94
+ });
95
+ };
96
+ const clear = ()=>{
97
+ if (disabled || 0 === value.length) return;
98
+ commit([]);
99
+ };
100
+ const handleValueChange = (details)=>{
101
+ commit(toConsumerValue(details.value, {
102
+ multiple,
103
+ previousInternal: internalValue,
104
+ previousValue: value
105
+ }));
106
+ };
107
+ const handleOpenChange = ({ open: nextOpen })=>{
108
+ setOpenState(nextOpen);
109
+ onOpenChange?.({
110
+ open: nextOpen,
111
+ value
112
+ });
113
+ };
114
+ const handleHighlightChange = (details)=>{
115
+ setHighlightState(details.highlightedValue);
116
+ setResetHighlight(false);
117
+ };
118
+ const setQuery = (next)=>{
119
+ if (!query && next) setLockedWidth(contentRef.current?.offsetWidth || void 0);
120
+ if (query && !next) setSnapshot(value);
121
+ onSearchChange(next);
122
+ setResetHighlight(true);
123
+ };
124
+ const context = {
125
+ label,
126
+ allLabel,
127
+ multiple,
128
+ disabled,
129
+ value,
130
+ collection: baseCollection,
131
+ filteredCollection,
132
+ groups,
133
+ query,
134
+ setQuery,
135
+ isSearchable,
136
+ isSearchActive,
137
+ showAll,
138
+ allItem,
139
+ showSelected,
140
+ aliasItems,
141
+ clear,
142
+ triggerRef,
143
+ contentRef,
144
+ lockedWidth
145
+ };
146
+ return /*#__PURE__*/ jsx(FilterDropdownContext.Provider, {
147
+ value: context,
148
+ children: /*#__PURE__*/ jsx(Select, {
149
+ ...props,
150
+ "data-slot": "filter-dropdown",
151
+ "data-testid": testId,
152
+ className: cn('inline-flex max-w-full', className),
153
+ collection: arkCollection,
154
+ multiple: multiple,
155
+ disabled: disabled,
156
+ value: internalValue,
157
+ onValueChange: handleValueChange,
158
+ open: openProp,
159
+ defaultOpen: defaultOpen,
160
+ onOpenChange: handleOpenChange,
161
+ highlightedValue: highlightedValue,
162
+ onHighlightChange: handleHighlightChange,
163
+ children: children
164
+ })
165
+ });
166
+ };
167
+ FilterDropdown.displayName = 'FilterDropdown';
168
+ export { FilterDropdown };
@@ -0,0 +1,14 @@
1
+ import type { ComponentProps, FC, ReactNode, Ref } from 'react';
2
+ import { SelectOption } from '../Select/SelectOption';
3
+ export interface FilterDropdownAllOptionProps extends Omit<ComponentProps<typeof SelectOption>, 'item' | 'children'> {
4
+ ref?: Ref<HTMLDivElement>;
5
+ /** @default the root's `allLabel` */
6
+ children?: ReactNode;
7
+ }
8
+ /**
9
+ * The «All …» row of single mode: first in the list, picked while the filter is unset, and
10
+ * picking it resets the filter to `[]`. Hidden in multi mode and while a query is active.
11
+ * Always render it in single mode: the row is part of keyboard navigation whenever the menu is
12
+ * open in single mode.
13
+ */
14
+ export declare const FilterDropdownAllOption: FC<FilterDropdownAllOptionProps>;
@@ -0,0 +1,25 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useTestId } from "../../utils/testId.js";
3
+ import { SelectOption } from "../Select/SelectOption.js";
4
+ import { SelectOptionIndicator } from "../Select/SelectOptionIndicator.js";
5
+ import { SelectOptionText } from "../Select/SelectOptionText.js";
6
+ import { useFilterDropdownContext } from "./FilterDropdownContext.js";
7
+ const FilterDropdownAllOption = ({ children, 'data-testid': testIdProp, ...props })=>{
8
+ const { showAll, allItem, allLabel } = useFilterDropdownContext();
9
+ const testId = useTestId('all-option', testIdProp);
10
+ if (!showAll) return null;
11
+ return /*#__PURE__*/ jsxs(SelectOption, {
12
+ ...props,
13
+ item: allItem,
14
+ "data-slot": "filter-dropdown-all-option",
15
+ "data-testid": testId,
16
+ children: [
17
+ /*#__PURE__*/ jsx(SelectOptionText, {
18
+ children: children ?? allLabel
19
+ }),
20
+ /*#__PURE__*/ jsx(SelectOptionIndicator, {})
21
+ ]
22
+ });
23
+ };
24
+ FilterDropdownAllOption.displayName = 'FilterDropdownAllOption';
25
+ export { FilterDropdownAllOption };
@@ -0,0 +1,12 @@
1
+ import type { ButtonHTMLAttributes, FC, Ref } from 'react';
2
+ import { type TestableProps } from '../../utils/testId';
3
+ export interface FilterDropdownClearProps extends ButtonHTMLAttributes<HTMLButtonElement>, TestableProps {
4
+ ref?: Ref<HTMLButtonElement>;
5
+ }
6
+ /**
7
+ * The ✕ that clears a multi filter. `FilterDropdownTrigger` renders one on its own; pass a
8
+ * `FilterDropdownClear` as the trigger's child to replace it (for example to add attributes).
9
+ * Renders only in multi mode while something is picked, and hands focus back to the trigger
10
+ * before it disappears.
11
+ */
12
+ export declare const FilterDropdownClear: FC<FilterDropdownClearProps>;