@lateralus-ai/shipping-ui 2.0.0-dev.22 → 2.0.0-dev.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/domain/Filters/FilterDropdown.d.ts +33 -7
- package/dist/domain/Filters/FilterPill.d.ts +13 -3
- package/dist/domain/Filters/index.d.ts +2 -2
- package/dist/index.cjs +23 -23
- package/dist/index.esm.js +3302 -3270
- package/dist/primitives/Switch.d.ts +4 -0
- package/package.json +1 -1
- package/src/domain/Filters/FilterDropdown.tsx +540 -438
- package/src/domain/Filters/FilterPill.tsx +74 -61
- package/src/domain/Filters/FilterPills.tsx +34 -34
- package/src/domain/Filters/FilteredPill.tsx +59 -59
- package/src/domain/Filters/FiltersBar.tsx +72 -72
- package/src/domain/Filters/formatActiveFilterChipLabel.ts +23 -23
- package/src/domain/Filters/index.ts +37 -35
- package/src/primitives/Switch.tsx +16 -6
- package/src/stories/canvases/FiltersCanvas.tsx +171 -167
|
@@ -1,61 +1,74 @@
|
|
|
1
|
-
import { forwardRef, type ButtonHTMLAttributes } from "react";
|
|
2
|
-
import { cn } from "../../utils/cn";
|
|
3
|
-
import { FiltersIcon } from "../../icons/generated";
|
|
4
|
-
import { Badge } from "../../primitives/Badge";
|
|
5
|
-
|
|
6
|
-
export type
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
{
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
1
|
+
import { forwardRef, type ButtonHTMLAttributes } from "react";
|
|
2
|
+
import { cn } from "../../utils/cn";
|
|
3
|
+
import { FiltersIcon } from "../../icons/generated";
|
|
4
|
+
import { Badge } from "../../primitives/Badge";
|
|
5
|
+
|
|
6
|
+
export type FilterPillAppearance = "filled" | "ghost";
|
|
7
|
+
|
|
8
|
+
export type FilterPillProps = Omit<
|
|
9
|
+
ButtonHTMLAttributes<HTMLButtonElement>,
|
|
10
|
+
"children"
|
|
11
|
+
> & {
|
|
12
|
+
/** When > 0, shows the orange count badge. */
|
|
13
|
+
activeFilterCount?: number;
|
|
14
|
+
/**
|
|
15
|
+
* `filled` — grey idle / blue when active (Figma default).
|
|
16
|
+
* `ghost` — white idle and active; only hover changes surface color.
|
|
17
|
+
*/
|
|
18
|
+
appearance?: FilterPillAppearance;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Filter trigger — Figma Filter Pill (6154:149608 / 6154:149599).
|
|
23
|
+
*/
|
|
24
|
+
export const FilterPill = forwardRef<HTMLButtonElement, FilterPillProps>(
|
|
25
|
+
(
|
|
26
|
+
{
|
|
27
|
+
activeFilterCount = 0,
|
|
28
|
+
appearance = "filled",
|
|
29
|
+
className,
|
|
30
|
+
type = "button",
|
|
31
|
+
"aria-label": ariaLabel = "Filters",
|
|
32
|
+
...props
|
|
33
|
+
},
|
|
34
|
+
ref,
|
|
35
|
+
) => {
|
|
36
|
+
const active = activeFilterCount > 0;
|
|
37
|
+
const ghost = appearance === "ghost";
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<button
|
|
41
|
+
ref={ref}
|
|
42
|
+
type={type}
|
|
43
|
+
aria-label={ariaLabel}
|
|
44
|
+
aria-pressed={active}
|
|
45
|
+
className={cn(
|
|
46
|
+
"relative flex size-9 shrink-0 items-center justify-center rounded-full transition-colors",
|
|
47
|
+
ghost
|
|
48
|
+
? "bg-white text-display-on-light-secondary hover:bg-grey-50 hover:text-display-on-light-primary"
|
|
49
|
+
: active
|
|
50
|
+
? "bg-accent-bg-light text-display-on-light-secondary hover:text-display-on-light-primary"
|
|
51
|
+
: "bg-background-secondary text-display-on-light-secondary hover:text-display-on-light-primary",
|
|
52
|
+
className,
|
|
53
|
+
)}
|
|
54
|
+
{...props}
|
|
55
|
+
>
|
|
56
|
+
<FiltersIcon size="small" />
|
|
57
|
+
{active && (
|
|
58
|
+
<Badge
|
|
59
|
+
color="orange"
|
|
60
|
+
type="icon"
|
|
61
|
+
className={cn(
|
|
62
|
+
"absolute -right-1 -top-2 border-2 text-display-on-light-primary",
|
|
63
|
+
ghost ? "border-white" : "border-background-primary",
|
|
64
|
+
)}
|
|
65
|
+
>
|
|
66
|
+
{activeFilterCount > 99 ? "99+" : activeFilterCount}
|
|
67
|
+
</Badge>
|
|
68
|
+
)}
|
|
69
|
+
</button>
|
|
70
|
+
);
|
|
71
|
+
},
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
FilterPill.displayName = "FilterPill";
|
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
import { cn } from "../../utils/cn";
|
|
2
|
-
import { FilteredPill, type FilteredPillProps } from "./FilteredPill";
|
|
3
|
-
|
|
4
|
-
export type FilterPillsItem = {
|
|
5
|
-
key: string;
|
|
6
|
-
label: string;
|
|
7
|
-
onRemove: () => void;
|
|
8
|
-
removeAriaLabel?: string;
|
|
9
|
-
classNames?: FilteredPillProps["classNames"];
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export type FilterPillsProps = {
|
|
13
|
-
chips: FilterPillsItem[];
|
|
14
|
-
className?: string;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Partner row for FilterDropdown — active filter pills.
|
|
19
|
-
* Always renders the row wrapper (even when empty) so sibling filter
|
|
20
|
-
* triggers can stay pinned with `ml-auto` / flex layouts.
|
|
21
|
-
*/
|
|
22
|
-
export const FilterPills = ({ chips, className }: FilterPillsProps) => (
|
|
23
|
-
<div className={cn("flex min-w-0 flex-wrap items-start gap-2", className)}>
|
|
24
|
-
{chips.map((chip) => (
|
|
25
|
-
<FilteredPill
|
|
26
|
-
key={chip.key}
|
|
27
|
-
label={chip.label}
|
|
28
|
-
onRemove={chip.onRemove}
|
|
29
|
-
removeAriaLabel={chip.removeAriaLabel}
|
|
30
|
-
classNames={chip.classNames}
|
|
31
|
-
/>
|
|
32
|
-
))}
|
|
33
|
-
</div>
|
|
34
|
-
);
|
|
1
|
+
import { cn } from "../../utils/cn";
|
|
2
|
+
import { FilteredPill, type FilteredPillProps } from "./FilteredPill";
|
|
3
|
+
|
|
4
|
+
export type FilterPillsItem = {
|
|
5
|
+
key: string;
|
|
6
|
+
label: string;
|
|
7
|
+
onRemove: () => void;
|
|
8
|
+
removeAriaLabel?: string;
|
|
9
|
+
classNames?: FilteredPillProps["classNames"];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type FilterPillsProps = {
|
|
13
|
+
chips: FilterPillsItem[];
|
|
14
|
+
className?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Partner row for FilterDropdown — active filter pills.
|
|
19
|
+
* Always renders the row wrapper (even when empty) so sibling filter
|
|
20
|
+
* triggers can stay pinned with `ml-auto` / flex layouts.
|
|
21
|
+
*/
|
|
22
|
+
export const FilterPills = ({ chips, className }: FilterPillsProps) => (
|
|
23
|
+
<div className={cn("flex min-w-0 flex-wrap items-start gap-2", className)}>
|
|
24
|
+
{chips.map((chip) => (
|
|
25
|
+
<FilteredPill
|
|
26
|
+
key={chip.key}
|
|
27
|
+
label={chip.label}
|
|
28
|
+
onRemove={chip.onRemove}
|
|
29
|
+
removeAriaLabel={chip.removeAriaLabel}
|
|
30
|
+
classNames={chip.classNames}
|
|
31
|
+
/>
|
|
32
|
+
))}
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
import { cn } from "../../utils/cn";
|
|
2
|
-
import { ClearIcon } from "../../icons";
|
|
3
|
-
import { Tooltip } from "../../primitives/Tooltip";
|
|
4
|
-
|
|
5
|
-
export type FilteredPillProps = {
|
|
6
|
-
label: string;
|
|
7
|
-
onRemove: () => void;
|
|
8
|
-
removeAriaLabel?: string;
|
|
9
|
-
className?: string;
|
|
10
|
-
/** Optional tone overrides. Prefer default beige unless a product needs a custom tone. */
|
|
11
|
-
classNames?: {
|
|
12
|
-
root?: string;
|
|
13
|
-
label?: string;
|
|
14
|
-
remove?: string;
|
|
15
|
-
};
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Active filter value pill — Figma Filtered (6154:149787).
|
|
20
|
-
* Clear control on the left, label on the right. Max 300px with ellipsis;
|
|
21
|
-
* full label in a tooltip on hover.
|
|
22
|
-
*/
|
|
23
|
-
export const FilteredPill = ({
|
|
24
|
-
label,
|
|
25
|
-
onRemove,
|
|
26
|
-
removeAriaLabel,
|
|
27
|
-
className,
|
|
28
|
-
classNames,
|
|
29
|
-
}: FilteredPillProps) => (
|
|
30
|
-
<Tooltip content={label} hint side="top">
|
|
31
|
-
<div
|
|
32
|
-
className={cn(
|
|
33
|
-
"inline-flex max-w-[300px] min-h-9 items-center justify-center gap-2.5 rounded-full bg-background-secondary px-3 py-1",
|
|
34
|
-
classNames?.root,
|
|
35
|
-
className,
|
|
36
|
-
)}
|
|
37
|
-
>
|
|
38
|
-
<button
|
|
39
|
-
type="button"
|
|
40
|
-
onClick={onRemove}
|
|
41
|
-
aria-label={removeAriaLabel ?? `Remove ${label} filter`}
|
|
42
|
-
className={cn(
|
|
43
|
-
"inline-flex size-4 shrink-0 items-center justify-center rounded-control text-display-on-light-secondary transition-colors hover:text-display-on-light-primary",
|
|
44
|
-
classNames?.remove,
|
|
45
|
-
)}
|
|
46
|
-
>
|
|
47
|
-
<ClearIcon size="xs" />
|
|
48
|
-
</button>
|
|
49
|
-
<span
|
|
50
|
-
className={cn(
|
|
51
|
-
"min-w-0 truncate text-caption-2-em text-display-on-light-primary",
|
|
52
|
-
classNames?.label,
|
|
53
|
-
)}
|
|
54
|
-
>
|
|
55
|
-
{label}
|
|
56
|
-
</span>
|
|
57
|
-
</div>
|
|
58
|
-
</Tooltip>
|
|
59
|
-
);
|
|
1
|
+
import { cn } from "../../utils/cn";
|
|
2
|
+
import { ClearIcon } from "../../icons";
|
|
3
|
+
import { Tooltip } from "../../primitives/Tooltip";
|
|
4
|
+
|
|
5
|
+
export type FilteredPillProps = {
|
|
6
|
+
label: string;
|
|
7
|
+
onRemove: () => void;
|
|
8
|
+
removeAriaLabel?: string;
|
|
9
|
+
className?: string;
|
|
10
|
+
/** Optional tone overrides. Prefer default beige unless a product needs a custom tone. */
|
|
11
|
+
classNames?: {
|
|
12
|
+
root?: string;
|
|
13
|
+
label?: string;
|
|
14
|
+
remove?: string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Active filter value pill — Figma Filtered (6154:149787).
|
|
20
|
+
* Clear control on the left, label on the right. Max 300px with ellipsis;
|
|
21
|
+
* full label in a tooltip on hover.
|
|
22
|
+
*/
|
|
23
|
+
export const FilteredPill = ({
|
|
24
|
+
label,
|
|
25
|
+
onRemove,
|
|
26
|
+
removeAriaLabel,
|
|
27
|
+
className,
|
|
28
|
+
classNames,
|
|
29
|
+
}: FilteredPillProps) => (
|
|
30
|
+
<Tooltip content={label} hint side="top">
|
|
31
|
+
<div
|
|
32
|
+
className={cn(
|
|
33
|
+
"inline-flex max-w-[300px] min-h-9 items-center justify-center gap-2.5 rounded-full bg-background-secondary px-3 py-1",
|
|
34
|
+
classNames?.root,
|
|
35
|
+
className,
|
|
36
|
+
)}
|
|
37
|
+
>
|
|
38
|
+
<button
|
|
39
|
+
type="button"
|
|
40
|
+
onClick={onRemove}
|
|
41
|
+
aria-label={removeAriaLabel ?? `Remove ${label} filter`}
|
|
42
|
+
className={cn(
|
|
43
|
+
"inline-flex size-4 shrink-0 items-center justify-center rounded-control text-display-on-light-secondary transition-colors hover:text-display-on-light-primary",
|
|
44
|
+
classNames?.remove,
|
|
45
|
+
)}
|
|
46
|
+
>
|
|
47
|
+
<ClearIcon size="xs" />
|
|
48
|
+
</button>
|
|
49
|
+
<span
|
|
50
|
+
className={cn(
|
|
51
|
+
"min-w-0 truncate text-caption-2-em text-display-on-light-primary",
|
|
52
|
+
classNames?.label,
|
|
53
|
+
)}
|
|
54
|
+
>
|
|
55
|
+
{label}
|
|
56
|
+
</span>
|
|
57
|
+
</div>
|
|
58
|
+
</Tooltip>
|
|
59
|
+
);
|
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
import { cn } from "../../utils/cn";
|
|
2
|
-
import { SearchIcon } from "../../icons/generated";
|
|
3
|
-
import { FilterPill } from "./FilterPill";
|
|
4
|
-
import { FilterPills } from "./FilterPills";
|
|
5
|
-
|
|
6
|
-
export type FiltersBarTabs = "on" | "off";
|
|
7
|
-
export type FiltersBarFilters = "on" | "off";
|
|
8
|
-
|
|
9
|
-
export type FiltersBarProps = {
|
|
10
|
-
tabs?: FiltersBarTabs;
|
|
11
|
-
filters?: FiltersBarFilters;
|
|
12
|
-
tabLabels?: string[];
|
|
13
|
-
activeTab?: string;
|
|
14
|
-
searchPlaceholder?: string;
|
|
15
|
-
filterChips?: string[];
|
|
16
|
-
className?: string;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const defaultTabs = ["All", "Reports", "Chats", "Issues", "Forms", "Workflows"];
|
|
20
|
-
|
|
21
|
-
export const FiltersBar = ({
|
|
22
|
-
tabs = "on",
|
|
23
|
-
filters = "off",
|
|
24
|
-
tabLabels = defaultTabs,
|
|
25
|
-
activeTab = "All",
|
|
26
|
-
searchPlaceholder = "Search",
|
|
27
|
-
filterChips = ["Status", "Date", "Chief"],
|
|
28
|
-
className,
|
|
29
|
-
}: FiltersBarProps) => (
|
|
30
|
-
<div className={cn("flex w-full flex-col gap-3", className)}>
|
|
31
|
-
<div className="flex items-center justify-between gap-4">
|
|
32
|
-
{tabs === "on" && (
|
|
33
|
-
<div className="flex flex-wrap gap-2">
|
|
34
|
-
{tabLabels.map((tab) => (
|
|
35
|
-
<button
|
|
36
|
-
key={tab}
|
|
37
|
-
type="button"
|
|
38
|
-
className={cn(
|
|
39
|
-
"rounded-full px-3 py-1.5 text-caption-2-em transition-colors",
|
|
40
|
-
tab === activeTab
|
|
41
|
-
? "bg-background-selected text-display-on-dark-primary"
|
|
42
|
-
: "bg-background-secondary text-display-on-light-tertiary hover:text-display-on-light-primary",
|
|
43
|
-
)}
|
|
44
|
-
>
|
|
45
|
-
{tab}
|
|
46
|
-
</button>
|
|
47
|
-
))}
|
|
48
|
-
</div>
|
|
49
|
-
)}
|
|
50
|
-
<div className="flex shrink-0 items-center gap-4">
|
|
51
|
-
<FilterPill
|
|
52
|
-
activeFilterCount={filters === "on" ? filterChips.length : 0}
|
|
53
|
-
/>
|
|
54
|
-
<div className="flex w-[250px] items-center gap-2 rounded-md border border-divider-primary px-3 py-2">
|
|
55
|
-
<span className="flex-1 text-body text-display-on-light-secondary">
|
|
56
|
-
{searchPlaceholder}
|
|
57
|
-
</span>
|
|
58
|
-
<SearchIcon size="small" className="text-display-on-light-tertiary" />
|
|
59
|
-
</div>
|
|
60
|
-
</div>
|
|
61
|
-
</div>
|
|
62
|
-
{filters === "on" && (
|
|
63
|
-
<FilterPills
|
|
64
|
-
chips={filterChips.map((chip) => ({
|
|
65
|
-
key: chip,
|
|
66
|
-
label: chip,
|
|
67
|
-
onRemove: () => undefined,
|
|
68
|
-
}))}
|
|
69
|
-
/>
|
|
70
|
-
)}
|
|
71
|
-
</div>
|
|
72
|
-
);
|
|
1
|
+
import { cn } from "../../utils/cn";
|
|
2
|
+
import { SearchIcon } from "../../icons/generated";
|
|
3
|
+
import { FilterPill } from "./FilterPill";
|
|
4
|
+
import { FilterPills } from "./FilterPills";
|
|
5
|
+
|
|
6
|
+
export type FiltersBarTabs = "on" | "off";
|
|
7
|
+
export type FiltersBarFilters = "on" | "off";
|
|
8
|
+
|
|
9
|
+
export type FiltersBarProps = {
|
|
10
|
+
tabs?: FiltersBarTabs;
|
|
11
|
+
filters?: FiltersBarFilters;
|
|
12
|
+
tabLabels?: string[];
|
|
13
|
+
activeTab?: string;
|
|
14
|
+
searchPlaceholder?: string;
|
|
15
|
+
filterChips?: string[];
|
|
16
|
+
className?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const defaultTabs = ["All", "Reports", "Chats", "Issues", "Forms", "Workflows"];
|
|
20
|
+
|
|
21
|
+
export const FiltersBar = ({
|
|
22
|
+
tabs = "on",
|
|
23
|
+
filters = "off",
|
|
24
|
+
tabLabels = defaultTabs,
|
|
25
|
+
activeTab = "All",
|
|
26
|
+
searchPlaceholder = "Search",
|
|
27
|
+
filterChips = ["Status", "Date", "Chief"],
|
|
28
|
+
className,
|
|
29
|
+
}: FiltersBarProps) => (
|
|
30
|
+
<div className={cn("flex w-full flex-col gap-3", className)}>
|
|
31
|
+
<div className="flex items-center justify-between gap-4">
|
|
32
|
+
{tabs === "on" && (
|
|
33
|
+
<div className="flex flex-wrap gap-2">
|
|
34
|
+
{tabLabels.map((tab) => (
|
|
35
|
+
<button
|
|
36
|
+
key={tab}
|
|
37
|
+
type="button"
|
|
38
|
+
className={cn(
|
|
39
|
+
"rounded-full px-3 py-1.5 text-caption-2-em transition-colors",
|
|
40
|
+
tab === activeTab
|
|
41
|
+
? "bg-background-selected text-display-on-dark-primary"
|
|
42
|
+
: "bg-background-secondary text-display-on-light-tertiary hover:text-display-on-light-primary",
|
|
43
|
+
)}
|
|
44
|
+
>
|
|
45
|
+
{tab}
|
|
46
|
+
</button>
|
|
47
|
+
))}
|
|
48
|
+
</div>
|
|
49
|
+
)}
|
|
50
|
+
<div className="flex shrink-0 items-center gap-4">
|
|
51
|
+
<FilterPill
|
|
52
|
+
activeFilterCount={filters === "on" ? filterChips.length : 0}
|
|
53
|
+
/>
|
|
54
|
+
<div className="flex w-[250px] items-center gap-2 rounded-md border border-divider-primary px-3 py-2">
|
|
55
|
+
<span className="flex-1 text-body text-display-on-light-secondary">
|
|
56
|
+
{searchPlaceholder}
|
|
57
|
+
</span>
|
|
58
|
+
<SearchIcon size="small" className="text-display-on-light-tertiary" />
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
{filters === "on" && (
|
|
63
|
+
<FilterPills
|
|
64
|
+
chips={filterChips.map((chip) => ({
|
|
65
|
+
key: chip,
|
|
66
|
+
label: chip,
|
|
67
|
+
onRemove: () => undefined,
|
|
68
|
+
}))}
|
|
69
|
+
/>
|
|
70
|
+
)}
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Format the user-visible label for an active-filter chip (audit-prep rules).
|
|
3
|
-
*
|
|
4
|
-
* - 0 selected → "" (caller should not render)
|
|
5
|
-
* - 1 selected → `<filterName> (<value>)`
|
|
6
|
-
* - 2 selected → `<filterName> (<v1>, <v2>)`
|
|
7
|
-
* - 3+ selected → `<filterName> (<v1>, <v2>...)` (literal `...`)
|
|
8
|
-
*
|
|
9
|
-
* `selectedLabels` are already human-readable, in selection order.
|
|
10
|
-
*/
|
|
11
|
-
export function formatActiveFilterChipLabel(
|
|
12
|
-
filterName: string,
|
|
13
|
-
selectedLabels: string[],
|
|
14
|
-
): string {
|
|
15
|
-
if (selectedLabels.length === 0) return "";
|
|
16
|
-
if (selectedLabels.length === 1) {
|
|
17
|
-
return `${filterName} (${selectedLabels[0]})`;
|
|
18
|
-
}
|
|
19
|
-
if (selectedLabels.length === 2) {
|
|
20
|
-
return `${filterName} (${selectedLabels[0]}, ${selectedLabels[1]})`;
|
|
21
|
-
}
|
|
22
|
-
return `${filterName} (${selectedLabels[0]}, ${selectedLabels[1]}...)`;
|
|
23
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Format the user-visible label for an active-filter chip (audit-prep rules).
|
|
3
|
+
*
|
|
4
|
+
* - 0 selected → "" (caller should not render)
|
|
5
|
+
* - 1 selected → `<filterName> (<value>)`
|
|
6
|
+
* - 2 selected → `<filterName> (<v1>, <v2>)`
|
|
7
|
+
* - 3+ selected → `<filterName> (<v1>, <v2>...)` (literal `...`)
|
|
8
|
+
*
|
|
9
|
+
* `selectedLabels` are already human-readable, in selection order.
|
|
10
|
+
*/
|
|
11
|
+
export function formatActiveFilterChipLabel(
|
|
12
|
+
filterName: string,
|
|
13
|
+
selectedLabels: string[],
|
|
14
|
+
): string {
|
|
15
|
+
if (selectedLabels.length === 0) return "";
|
|
16
|
+
if (selectedLabels.length === 1) {
|
|
17
|
+
return `${filterName} (${selectedLabels[0]})`;
|
|
18
|
+
}
|
|
19
|
+
if (selectedLabels.length === 2) {
|
|
20
|
+
return `${filterName} (${selectedLabels[0]}, ${selectedLabels[1]})`;
|
|
21
|
+
}
|
|
22
|
+
return `${filterName} (${selectedLabels[0]}, ${selectedLabels[1]}...)`;
|
|
23
|
+
}
|
|
@@ -1,35 +1,37 @@
|
|
|
1
|
-
export {
|
|
2
|
-
FiltersBar,
|
|
3
|
-
type FiltersBarFilters,
|
|
4
|
-
type FiltersBarProps,
|
|
5
|
-
type FiltersBarTabs,
|
|
6
|
-
} from "./FiltersBar";
|
|
7
|
-
export {
|
|
8
|
-
FilteredPill,
|
|
9
|
-
type FilteredPillProps,
|
|
10
|
-
} from "./FilteredPill";
|
|
11
|
-
export {
|
|
12
|
-
FilterPill,
|
|
13
|
-
type
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
type
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
type
|
|
25
|
-
type
|
|
26
|
-
type
|
|
27
|
-
type
|
|
28
|
-
type
|
|
29
|
-
type
|
|
30
|
-
type
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
1
|
+
export {
|
|
2
|
+
FiltersBar,
|
|
3
|
+
type FiltersBarFilters,
|
|
4
|
+
type FiltersBarProps,
|
|
5
|
+
type FiltersBarTabs,
|
|
6
|
+
} from "./FiltersBar";
|
|
7
|
+
export {
|
|
8
|
+
FilteredPill,
|
|
9
|
+
type FilteredPillProps,
|
|
10
|
+
} from "./FilteredPill";
|
|
11
|
+
export {
|
|
12
|
+
FilterPill,
|
|
13
|
+
type FilterPillAppearance,
|
|
14
|
+
type FilterPillProps,
|
|
15
|
+
} from "./FilterPill";
|
|
16
|
+
export {
|
|
17
|
+
FilterPills,
|
|
18
|
+
type FilterPillsItem,
|
|
19
|
+
type FilterPillsProps,
|
|
20
|
+
} from "./FilterPills";
|
|
21
|
+
export {
|
|
22
|
+
FilterDropdown,
|
|
23
|
+
resolveSubmenuView,
|
|
24
|
+
type FilterCategoryInlineOptionsRow,
|
|
25
|
+
type FilterCategoryRow,
|
|
26
|
+
type FilterCategorySubmenuRow,
|
|
27
|
+
type FilterCategoryToggleRow,
|
|
28
|
+
type FilterDropdownProps,
|
|
29
|
+
type FilterNestedItem,
|
|
30
|
+
type FilterOption,
|
|
31
|
+
type FilterSelectionMode,
|
|
32
|
+
type FilterSubmenuContent,
|
|
33
|
+
} from "./FilterDropdown";
|
|
34
|
+
export { formatActiveFilterChipLabel } from "./formatActiveFilterChipLabel";
|
|
35
|
+
|
|
36
|
+
/** @deprecated Use `activeFilterCount` on FilterPill instead. */
|
|
37
|
+
export type FilterPillIndicator = "on" | "off";
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { type ButtonHTMLAttributes } from "react";
|
|
2
2
|
import { cn } from "../utils/cn";
|
|
3
3
|
|
|
4
|
-
export type SwitchProps = Omit<
|
|
4
|
+
export type SwitchProps = Omit<
|
|
5
|
+
ButtonHTMLAttributes<HTMLButtonElement>,
|
|
6
|
+
"onChange"
|
|
7
|
+
> & {
|
|
5
8
|
checked: boolean;
|
|
6
9
|
onChange: (checked: boolean) => void;
|
|
7
10
|
disabled?: boolean;
|
|
8
11
|
};
|
|
9
12
|
|
|
13
|
+
/**
|
|
14
|
+
* On/off control — Figma Switch (3068:65681).
|
|
15
|
+
* Off: grey track, thumb left. On: grey-900 track, thumb right. Raise-2 on thumb.
|
|
16
|
+
*/
|
|
10
17
|
export const Switch = ({
|
|
11
18
|
checked,
|
|
12
19
|
onChange,
|
|
@@ -19,18 +26,21 @@ export const Switch = ({
|
|
|
19
26
|
role="switch"
|
|
20
27
|
aria-checked={checked}
|
|
21
28
|
disabled={disabled}
|
|
22
|
-
onClick={() =>
|
|
29
|
+
onClick={(event) => {
|
|
30
|
+
event.stopPropagation();
|
|
31
|
+
onChange(!checked);
|
|
32
|
+
}}
|
|
23
33
|
className={cn(
|
|
24
|
-
"relative inline-flex h-
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
"relative inline-flex h-4 w-9 shrink-0 items-center rounded-full transition-colors",
|
|
35
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
36
|
+
checked ? "bg-grey-900" : "bg-grey-200",
|
|
27
37
|
className,
|
|
28
38
|
)}
|
|
29
39
|
{...props}
|
|
30
40
|
>
|
|
31
41
|
<span
|
|
32
42
|
className={cn(
|
|
33
|
-
"inline-block size-5 rounded-full bg-white shadow-
|
|
43
|
+
"inline-block size-3.5 rounded-full bg-white shadow-raise2 transition-transform",
|
|
34
44
|
checked ? "translate-x-[18px]" : "translate-x-0.5",
|
|
35
45
|
)}
|
|
36
46
|
/>
|