@sproutsocial/seeds-react-menu 1.17.0 → 1.18.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.
- package/.turbo/turbo-build.log +14 -22
- package/CHANGELOG.md +30 -0
- package/dist/esm/index.js +542 -229
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/v3/index.js +793 -1038
- package/dist/esm/v3/index.js.map +1 -1
- package/dist/v3/index.d.mts +64 -38
- package/dist/v3/index.d.ts +64 -38
- package/dist/v3/index.js +813 -1058
- package/dist/v3/index.js.map +1 -1
- package/dist/v3/menu.css +947 -0
- package/package.json +20 -21
- package/src/v3/ActionMenuStyles.tsx +169 -149
- package/src/v3/Common/ComboboxStyles.tsx +396 -511
- package/src/v3/Common/SelectIcons.tsx +17 -8
- package/src/v3/Common/SelectItem.tsx +25 -38
- package/src/v3/Common/SelectStyles.tsx +146 -144
- package/src/v3/Common/cn.ts +38 -0
- package/src/v3/MenuButtonStyles.tsx +55 -34
- package/src/v3/MultiSearchableSelect.tsx +8 -8
- package/src/v3/MultiSelect.tsx +11 -16
- package/src/v3/SingleSelect.tsx +13 -13
- package/src/v3/menu.css +947 -0
- package/tsup.config.ts +0 -1
- package/dist/esm/chunk-ROQATIB7.js +0 -357
- package/dist/esm/chunk-ROQATIB7.js.map +0 -1
- package/dist/esm/v2/index.js +0 -2089
- package/dist/esm/v2/index.js.map +0 -1
- package/dist/v2/index.d.mts +0 -108
- package/dist/v2/index.d.ts +0 -108
- package/dist/v2/index.js +0 -2280
- package/dist/v2/index.js.map +0 -1
- package/src/v2/Autocomplete/Autocomplete.stories.tsx +0 -645
- package/src/v2/Autocomplete/MultiAutocomplete.tsx +0 -487
- package/src/v2/Autocomplete/SingleAutocomplete.tsx +0 -366
- package/src/v2/Autocomplete/index.ts +0 -2
- package/src/v2/Common-V2/ComboboxContent.tsx +0 -457
- package/src/v2/Common-V2/MenuGroup.tsx +0 -60
- package/src/v2/Common-V2/MenuGroupTypes.ts +0 -30
- package/src/v2/Common-V2/MenuHeading.tsx +0 -83
- package/src/v2/Common-V2/MenuItem.tsx +0 -138
- package/src/v2/Common-V2/Popout.tsx +0 -124
- package/src/v2/Common-V2/PopoutTypes.tsx +0 -109
- package/src/v2/Common-V2/SelectToggleButton.tsx +0 -99
- package/src/v2/Common-V2/index.ts +0 -11
- package/src/v2/Common-V2/props.ts +0 -84
- package/src/v2/Common-V2/styles.tsx +0 -41
- package/src/v2/Common-V2/types.ts +0 -16
- package/src/v2/Common-V2/useHighlightedIndex.ts +0 -62
- package/src/v2/Common-V2/utils.ts +0 -238
- package/src/v2/SearchableSelect/AutocompleteContent.tsx +0 -325
- package/src/v2/SearchableSelect/SearchableMultiSelect.tsx +0 -527
- package/src/v2/SearchableSelect/SearchableSelect.tsx +0 -395
- package/src/v2/SearchableSelect/index.ts +0 -12
- package/src/v2/Select/MultiSelect.tsx +0 -417
- package/src/v2/Select/Select.stories.tsx +0 -593
- package/src/v2/Select/SingleSelect.tsx +0 -285
- package/src/v2/Select/index.ts +0 -2
- package/src/v2/index.ts +0 -2
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import React, { useMemo } from "react";
|
|
2
|
-
import type { ReactNode, CSSProperties } from "react";
|
|
3
|
-
import { Icon } from "@sproutsocial/seeds-react-icon";
|
|
4
|
-
import { Checkbox } from "@sproutsocial/seeds-react-checkbox";
|
|
5
|
-
import { Switch } from "@sproutsocial/seeds-react-switch";
|
|
6
|
-
import { Radio } from "@sproutsocial/seeds-react-radio";
|
|
7
|
-
import { useTheme } from "styled-components";
|
|
8
|
-
import { Box } from "@sproutsocial/seeds-react-box";
|
|
9
|
-
import {
|
|
10
|
-
StyledMenuItem,
|
|
11
|
-
MenuItemActionLeft,
|
|
12
|
-
MenuItemRow,
|
|
13
|
-
MenuItemText,
|
|
14
|
-
} from "../../MenuItem/styles";
|
|
15
|
-
import type { InputType } from "../../MenuItem/MenuItemTypes";
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* SelectionIndicator - handles logic for appropriate rendering of inputs
|
|
19
|
-
* @returns Checkbox | Icon | Radio | Switch
|
|
20
|
-
*/
|
|
21
|
-
const SelectionIndicator = ({
|
|
22
|
-
id,
|
|
23
|
-
inputType = "icon",
|
|
24
|
-
"aria-labelledby": labeledBy,
|
|
25
|
-
selected,
|
|
26
|
-
}: {
|
|
27
|
-
id: string;
|
|
28
|
-
"aria-labelledby": string;
|
|
29
|
-
selected: boolean;
|
|
30
|
-
inputType: InputType;
|
|
31
|
-
}) => {
|
|
32
|
-
const { space } = useTheme();
|
|
33
|
-
|
|
34
|
-
const inputStub = (e: React.SyntheticEvent) => {
|
|
35
|
-
e.stopPropagation();
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
switch (inputType) {
|
|
39
|
-
case "checkbox":
|
|
40
|
-
return (
|
|
41
|
-
<Checkbox
|
|
42
|
-
tabIndex={-1}
|
|
43
|
-
onChange={() => {}}
|
|
44
|
-
checked={selected}
|
|
45
|
-
aria-labelledby={labeledBy}
|
|
46
|
-
id={`MenuItem-${inputType}-${id}`}
|
|
47
|
-
/>
|
|
48
|
-
);
|
|
49
|
-
case "switch":
|
|
50
|
-
return (
|
|
51
|
-
<Switch
|
|
52
|
-
tabIndex={-1}
|
|
53
|
-
onClick={inputStub}
|
|
54
|
-
checked={!!selected}
|
|
55
|
-
aria-labelledby={labeledBy}
|
|
56
|
-
id={`MenuItem-${inputType}-${id}`}
|
|
57
|
-
/>
|
|
58
|
-
);
|
|
59
|
-
case "radio":
|
|
60
|
-
return (
|
|
61
|
-
<Radio
|
|
62
|
-
tabIndex={-1}
|
|
63
|
-
checked={selected}
|
|
64
|
-
id={`MenuItem-${inputType}-${id}`}
|
|
65
|
-
name={`MenuItem-${inputType}`}
|
|
66
|
-
aria-labelledby={labeledBy}
|
|
67
|
-
/>
|
|
68
|
-
);
|
|
69
|
-
case "icon":
|
|
70
|
-
if (selected) {
|
|
71
|
-
return <Icon name="check-solid" aria-hidden size="small" />;
|
|
72
|
-
} else {
|
|
73
|
-
return <Box width={space["400"]}></Box>;
|
|
74
|
-
}
|
|
75
|
-
default:
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
export interface MenuItemProps {
|
|
81
|
-
children: ReactNode;
|
|
82
|
-
$highlighted?: boolean;
|
|
83
|
-
$active?: boolean;
|
|
84
|
-
inputType?: InputType;
|
|
85
|
-
selected?: boolean;
|
|
86
|
-
id?: string | number;
|
|
87
|
-
style?: CSSProperties;
|
|
88
|
-
[key: string]: any; // For spreading getItemProps and other props
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export const MenuItem = ({
|
|
92
|
-
children,
|
|
93
|
-
$highlighted = false,
|
|
94
|
-
$active = false,
|
|
95
|
-
inputType,
|
|
96
|
-
selected = false,
|
|
97
|
-
id,
|
|
98
|
-
style,
|
|
99
|
-
...props
|
|
100
|
-
}: MenuItemProps) => {
|
|
101
|
-
// Generate a label id for the menu item text
|
|
102
|
-
const labelId = useMemo(
|
|
103
|
-
() =>
|
|
104
|
-
id
|
|
105
|
-
? `menu-item-child-${id}`
|
|
106
|
-
: `menu-item-child-${Math.random().toString(36).substr(2, 9)}`,
|
|
107
|
-
[id]
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
// If inputType is provided, wrap children with SelectionIndicator
|
|
111
|
-
const content = inputType ? (
|
|
112
|
-
<MenuItemRow>
|
|
113
|
-
<MenuItemActionLeft>
|
|
114
|
-
<SelectionIndicator
|
|
115
|
-
id={String(id || "")}
|
|
116
|
-
aria-labelledby={labelId}
|
|
117
|
-
inputType={inputType}
|
|
118
|
-
selected={selected}
|
|
119
|
-
/>
|
|
120
|
-
</MenuItemActionLeft>
|
|
121
|
-
<MenuItemText id={labelId}>{children}</MenuItemText>
|
|
122
|
-
</MenuItemRow>
|
|
123
|
-
) : (
|
|
124
|
-
children
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
return (
|
|
128
|
-
<StyledMenuItem
|
|
129
|
-
$highlighted={$highlighted}
|
|
130
|
-
$active={$active}
|
|
131
|
-
$selected={selected}
|
|
132
|
-
style={style}
|
|
133
|
-
{...props}
|
|
134
|
-
>
|
|
135
|
-
{content}
|
|
136
|
-
</StyledMenuItem>
|
|
137
|
-
);
|
|
138
|
-
};
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { AnimatePresence, motion } from "motion/react";
|
|
3
|
-
import * as Popover from "@radix-ui/react-popover";
|
|
4
|
-
import { MOTION_DURATION_FAST } from "@sproutsocial/seeds-motion/unitless";
|
|
5
|
-
import type { TypePopoutProps } from "./PopoutTypes";
|
|
6
|
-
import styled from "styled-components";
|
|
7
|
-
|
|
8
|
-
const defaultAnimationConfig = {
|
|
9
|
-
initial: { opacity: 0 },
|
|
10
|
-
animate: { opacity: 1 },
|
|
11
|
-
exit: { opacity: 0 },
|
|
12
|
-
transition: {
|
|
13
|
-
ease: "circOut",
|
|
14
|
-
type: "tween",
|
|
15
|
-
duration: MOTION_DURATION_FAST,
|
|
16
|
-
},
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const StyledContent = styled(Popover.Content)`
|
|
20
|
-
width: var(--radix-popover-trigger-width);
|
|
21
|
-
max-height: var(--radix-popover-content-available-height);
|
|
22
|
-
// This should be a prop on the Popout component
|
|
23
|
-
z-index: 7;
|
|
24
|
-
`;
|
|
25
|
-
|
|
26
|
-
const StyledMotionDiv = styled(motion.div)`
|
|
27
|
-
background-color: ${({ theme }) => theme.colors.container.background.base};
|
|
28
|
-
border: ${({ theme }) => theme.borders[500]};
|
|
29
|
-
border-color: ${({ theme }) => theme.colors.container.border.base};
|
|
30
|
-
border-radius: ${({ theme }) => theme.radii[500]};
|
|
31
|
-
box-shadow: ${({ theme }) => theme.shadows.medium};
|
|
32
|
-
padding: ${({ theme }) => theme.space[400]};
|
|
33
|
-
`;
|
|
34
|
-
|
|
35
|
-
export function Popout({
|
|
36
|
-
children,
|
|
37
|
-
content,
|
|
38
|
-
open,
|
|
39
|
-
defaultOpen,
|
|
40
|
-
onOpenChange,
|
|
41
|
-
animationConfig = defaultAnimationConfig,
|
|
42
|
-
side = "bottom",
|
|
43
|
-
sideOffset = 8,
|
|
44
|
-
align = "center",
|
|
45
|
-
alignOffset = 0,
|
|
46
|
-
onOpenAutoFocus,
|
|
47
|
-
onEscapeKeyDown,
|
|
48
|
-
onCloseAutoFocus,
|
|
49
|
-
onPointerDownOutside,
|
|
50
|
-
onInteractOutside,
|
|
51
|
-
...rest
|
|
52
|
-
}: TypePopoutProps) {
|
|
53
|
-
const [isOpen, setIsOpen] = React.useState(defaultOpen ?? false);
|
|
54
|
-
|
|
55
|
-
const handleOpenChange = React.useCallback(
|
|
56
|
-
(newOpen: boolean) => {
|
|
57
|
-
setIsOpen(newOpen);
|
|
58
|
-
onOpenChange?.(newOpen);
|
|
59
|
-
},
|
|
60
|
-
[onOpenChange]
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
// Use controlled state if open prop is provided
|
|
64
|
-
const isControlled = open !== undefined;
|
|
65
|
-
const popoverOpen = isControlled ? open : isOpen;
|
|
66
|
-
const popoverOnOpenChange = isControlled ? onOpenChange : handleOpenChange;
|
|
67
|
-
|
|
68
|
-
// Handle ref forwarding for components that use innerRef instead of ref
|
|
69
|
-
// (e.g., seeds-react-button)
|
|
70
|
-
// Radix UI's asChild passes ref, but Button uses innerRef
|
|
71
|
-
const radixRef = React.useRef<HTMLButtonElement | null>(null);
|
|
72
|
-
|
|
73
|
-
// The popout needs its role and aria controls props to be overridden.
|
|
74
|
-
// This should not be treated like a dialog when used in a menu.
|
|
75
|
-
// The combobox itself needs aria-expanded and aria-controls
|
|
76
|
-
// We could also likely try to collapse the popout and downshift props by merging them and their onclick
|
|
77
|
-
// At the moment, if the popout content is the combobox the onclick gets overridden by the popout
|
|
78
|
-
return (
|
|
79
|
-
<Popover.Root open={popoverOpen} onOpenChange={popoverOnOpenChange}>
|
|
80
|
-
{/*
|
|
81
|
-
The Anchor provides the positioning reference for the popout content.
|
|
82
|
-
We can't rely on Popover.Trigger's ref for positioning because the children
|
|
83
|
-
(e.g. Input) are class components that don't support forwardRef — Radix's
|
|
84
|
-
asChild would connect the ref to the class instance rather than the DOM node.
|
|
85
|
-
The Anchor is absolutely positioned at the top or bottom edge of the wrapper
|
|
86
|
-
so the popout content appears correctly above or below the trigger.
|
|
87
|
-
*/}
|
|
88
|
-
<div style={{ position: "relative" }}>
|
|
89
|
-
<Popover.Trigger ref={radixRef} asChild>
|
|
90
|
-
{children}
|
|
91
|
-
</Popover.Trigger>
|
|
92
|
-
<Popover.Anchor
|
|
93
|
-
style={{
|
|
94
|
-
position: "absolute",
|
|
95
|
-
top: side === "top" ? 0 : "100%",
|
|
96
|
-
left: 0,
|
|
97
|
-
right: 0,
|
|
98
|
-
height: "1px",
|
|
99
|
-
}}
|
|
100
|
-
/>
|
|
101
|
-
</div>
|
|
102
|
-
<Popover.Portal>
|
|
103
|
-
<StyledContent
|
|
104
|
-
side={side}
|
|
105
|
-
sideOffset={sideOffset}
|
|
106
|
-
align={align}
|
|
107
|
-
alignOffset={alignOffset}
|
|
108
|
-
onOpenAutoFocus={onOpenAutoFocus}
|
|
109
|
-
onEscapeKeyDown={onEscapeKeyDown}
|
|
110
|
-
onCloseAutoFocus={onCloseAutoFocus}
|
|
111
|
-
onPointerDownOutside={onPointerDownOutside}
|
|
112
|
-
onInteractOutside={onInteractOutside}
|
|
113
|
-
// This only needs added to Popouts used in Menus
|
|
114
|
-
// those do not need the dialog role which radix applies automatically
|
|
115
|
-
role="presentation"
|
|
116
|
-
>
|
|
117
|
-
<StyledMotionDiv {...animationConfig} {...rest}>
|
|
118
|
-
{content}
|
|
119
|
-
</StyledMotionDiv>
|
|
120
|
-
</StyledContent>
|
|
121
|
-
</Popover.Portal>
|
|
122
|
-
</Popover.Root>
|
|
123
|
-
);
|
|
124
|
-
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
import type {
|
|
4
|
-
TypeSystemCommonProps,
|
|
5
|
-
TypeSystemLayoutProps,
|
|
6
|
-
TypeStyledComponentsCommonProps,
|
|
7
|
-
} from "@sproutsocial/seeds-react-system-props";
|
|
8
|
-
import type { HTMLMotionProps } from "motion/react";
|
|
9
|
-
|
|
10
|
-
export interface TypePopoutProps
|
|
11
|
-
extends TypeStyledComponentsCommonProps,
|
|
12
|
-
TypeSystemCommonProps,
|
|
13
|
-
TypeSystemLayoutProps,
|
|
14
|
-
Omit<
|
|
15
|
-
React.ComponentPropsWithoutRef<"div">,
|
|
16
|
-
"color" | "children" | "content"
|
|
17
|
-
> {
|
|
18
|
-
/**
|
|
19
|
-
* The content that the popout should be attached to (renders in Popover.Trigger)
|
|
20
|
-
*/
|
|
21
|
-
children: React.ReactElement<any>;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* The content to be shown in the popout (renders in Popover.Content)
|
|
25
|
-
*/
|
|
26
|
-
content: React.ReactNode;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Whether the popout is open (controlled)
|
|
30
|
-
*/
|
|
31
|
-
open?: boolean;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Default open state (uncontrolled)
|
|
35
|
-
*/
|
|
36
|
-
defaultOpen?: boolean;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Callback fired when the open state changes
|
|
40
|
-
*/
|
|
41
|
-
onOpenChange?: (open: boolean) => void;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Used to override the default Popout animations.
|
|
45
|
-
* Any props that are valid for use on a Motion div can be passed here as an object.
|
|
46
|
-
* See https://motion.dev/docs/react-motion-component#props
|
|
47
|
-
*/
|
|
48
|
-
animationConfig?: Omit<HTMLMotionProps<"div">, "children">;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* The preferred side of the trigger to render against when open.
|
|
52
|
-
* @default "bottom"
|
|
53
|
-
*/
|
|
54
|
-
side?: "top" | "right" | "bottom" | "left";
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* The distance in pixels from the trigger.
|
|
58
|
-
* @default 8
|
|
59
|
-
*/
|
|
60
|
-
sideOffset?: number;
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* The preferred alignment against the trigger.
|
|
64
|
-
* @default "center"
|
|
65
|
-
*/
|
|
66
|
-
align?: "start" | "center" | "end";
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* An offset in pixels from the "start" or "end" alignment options.
|
|
70
|
-
* @default 0
|
|
71
|
-
*/
|
|
72
|
-
alignOffset?: number;
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Event handler called when the popout content tries to auto-focus on open.
|
|
76
|
-
* Can be used to prevent the default auto-focus behavior or customize it.
|
|
77
|
-
* If not provided, defaults to allowing auto-focus on the content.
|
|
78
|
-
*/
|
|
79
|
-
onOpenAutoFocus?: (event: Event) => void;
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Event handler called when the Escape key is pressed while the popout is open.
|
|
83
|
-
* Can be used to prevent the default close behavior or customize it.
|
|
84
|
-
*/
|
|
85
|
-
onEscapeKeyDown?: (event: KeyboardEvent) => void;
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Event handler called when the popout tries to return focus to the trigger on close.
|
|
89
|
-
* Can be used to prevent the default focus return behavior or customize it.
|
|
90
|
-
*/
|
|
91
|
-
onCloseAutoFocus?: (event: Event) => void;
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Event handler called when a pointer down event occurs outside the popout content.
|
|
95
|
-
* Call `event.preventDefault()` to prevent the default dismiss behavior.
|
|
96
|
-
* Useful when the Popout is nested inside a Radix Dialog (e.g. ModalV2) to
|
|
97
|
-
* avoid conflicts between nested DismissableLayer instances.
|
|
98
|
-
*/
|
|
99
|
-
onPointerDownOutside?: (
|
|
100
|
-
event: CustomEvent<{ originalEvent: PointerEvent }>
|
|
101
|
-
) => void;
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Event handler called when an interaction (pointer or focus) occurs outside
|
|
105
|
-
* the popout content. Call `event.preventDefault()` to prevent the popout
|
|
106
|
-
* from closing.
|
|
107
|
-
*/
|
|
108
|
-
onInteractOutside?: (event: CustomEvent<{ originalEvent: Event }>) => void;
|
|
109
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import styled, { css } from "styled-components";
|
|
2
|
-
import { Icon } from "@sproutsocial/seeds-react-icon";
|
|
3
|
-
import {
|
|
4
|
-
MenuToggleButton,
|
|
5
|
-
type TypeMenuToggleButtonProps,
|
|
6
|
-
} from "../../MenuToggleButton";
|
|
7
|
-
import Button from "@sproutsocial/seeds-react-button";
|
|
8
|
-
|
|
9
|
-
export interface TypeSelectToggleButtonProps extends TypeMenuToggleButtonProps {
|
|
10
|
-
invalid?: boolean;
|
|
11
|
-
isOpen?: boolean;
|
|
12
|
-
shouldOverflow?: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// I don't think this needs to be a button.
|
|
16
|
-
// Looking at https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/
|
|
17
|
-
// It appears that a div is acceptable.
|
|
18
|
-
export const StyledSelectToggleButton = styled(
|
|
19
|
-
Button
|
|
20
|
-
)<TypeSelectToggleButtonProps>`
|
|
21
|
-
border-color: ${(props) => props.theme.colors.form.border.base};
|
|
22
|
-
width: 100%;
|
|
23
|
-
|
|
24
|
-
${(props) =>
|
|
25
|
-
props.invalid &&
|
|
26
|
-
css`
|
|
27
|
-
border-color: ${(props) => props.theme.colors.form.border.error};
|
|
28
|
-
`}
|
|
29
|
-
`;
|
|
30
|
-
|
|
31
|
-
export interface TypeStyledChevronProps {
|
|
32
|
-
$isOpen?: boolean;
|
|
33
|
-
invalid?: boolean;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const StyledChevron = styled.div<TypeStyledChevronProps>`
|
|
37
|
-
margin-left: ${({ theme }) => theme.space[300]};
|
|
38
|
-
|
|
39
|
-
${({ invalid }) =>
|
|
40
|
-
invalid &&
|
|
41
|
-
css`
|
|
42
|
-
color: ${(props) => props.theme.colors.icon.error};
|
|
43
|
-
`}
|
|
44
|
-
`;
|
|
45
|
-
|
|
46
|
-
const RotatingIcon = styled(Icon)<{ $isOpen?: boolean }>`
|
|
47
|
-
transition: transform 0.15s;
|
|
48
|
-
|
|
49
|
-
${({ $isOpen }) =>
|
|
50
|
-
$isOpen &&
|
|
51
|
-
`
|
|
52
|
-
transform: rotate(-180deg);
|
|
53
|
-
`}
|
|
54
|
-
`;
|
|
55
|
-
|
|
56
|
-
const StyledInnerButton = styled.div`
|
|
57
|
-
display: flex;
|
|
58
|
-
justify-content: space-between;
|
|
59
|
-
`;
|
|
60
|
-
|
|
61
|
-
const EllipsisContent = styled.div`
|
|
62
|
-
overflow: hidden;
|
|
63
|
-
text-overflow: ellipsis;
|
|
64
|
-
white-space: nowrap;
|
|
65
|
-
`;
|
|
66
|
-
|
|
67
|
-
const WrappedContent = styled.div`
|
|
68
|
-
flex: 1;
|
|
69
|
-
width: 100%;
|
|
70
|
-
text-align: left;
|
|
71
|
-
text-wrap: auto;
|
|
72
|
-
`;
|
|
73
|
-
|
|
74
|
-
export const SelectToggleButton = ({
|
|
75
|
-
children,
|
|
76
|
-
isOpen,
|
|
77
|
-
shouldOverflow,
|
|
78
|
-
...buttonProps
|
|
79
|
-
}: TypeSelectToggleButtonProps) => {
|
|
80
|
-
return (
|
|
81
|
-
<StyledSelectToggleButton {...buttonProps}>
|
|
82
|
-
<StyledInnerButton>
|
|
83
|
-
{shouldOverflow ? (
|
|
84
|
-
<WrappedContent>{children}</WrappedContent>
|
|
85
|
-
) : (
|
|
86
|
-
<EllipsisContent>{children}</EllipsisContent>
|
|
87
|
-
)}
|
|
88
|
-
<StyledChevron $isOpen={isOpen} invalid={buttonProps.invalid}>
|
|
89
|
-
<RotatingIcon
|
|
90
|
-
name="chevron-down-outline"
|
|
91
|
-
fixedWidth
|
|
92
|
-
aria-hidden
|
|
93
|
-
$isOpen={isOpen}
|
|
94
|
-
/>
|
|
95
|
-
</StyledChevron>
|
|
96
|
-
</StyledInnerButton>
|
|
97
|
-
</StyledSelectToggleButton>
|
|
98
|
-
);
|
|
99
|
-
};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export * from "./types";
|
|
2
|
-
export * from "./utils";
|
|
3
|
-
export * from "./Popout";
|
|
4
|
-
export * from "./PopoutTypes";
|
|
5
|
-
export * from "./SelectToggleButton";
|
|
6
|
-
export * from "./MenuItem";
|
|
7
|
-
export * from "./MenuHeading";
|
|
8
|
-
export * from "./MenuGroup";
|
|
9
|
-
export * from "./props";
|
|
10
|
-
export * from "./useHighlightedIndex";
|
|
11
|
-
export * from "./ComboboxContent";
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
|
-
import type { DataWithId, ToggleItem, GroupHeading } from "./types";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Base props common to all select/autocomplete components
|
|
6
|
-
*/
|
|
7
|
-
export interface BaseSelectProps<T extends DataWithId> {
|
|
8
|
-
id: string;
|
|
9
|
-
data: T[];
|
|
10
|
-
isOpen?: boolean;
|
|
11
|
-
itemToKey?: (item: T) => string | number;
|
|
12
|
-
itemToString: (item: T | null) => string;
|
|
13
|
-
renderItem: (item: T) => ReactNode;
|
|
14
|
-
groupBy?: (item: T) => string;
|
|
15
|
-
renderGroupHeading?: (label: string) => string;
|
|
16
|
-
isVirtualized?: boolean;
|
|
17
|
-
estimatedItemHeight?: number;
|
|
18
|
-
placeholder: string;
|
|
19
|
-
onIsOpenChange?: (isOpen: boolean) => void;
|
|
20
|
-
stateReducer?: any;
|
|
21
|
-
menuProps?: React.HTMLAttributes<HTMLDivElement>;
|
|
22
|
-
onPointerDownOutside?: (
|
|
23
|
-
event: CustomEvent<{ originalEvent: PointerEvent }>
|
|
24
|
-
) => void;
|
|
25
|
-
onInteractOutside?: (event: CustomEvent<{ originalEvent: Event }>) => void;
|
|
26
|
-
/**
|
|
27
|
-
* Controls which side the popout appears relative to the trigger.
|
|
28
|
-
* @default "bottom"
|
|
29
|
-
*/
|
|
30
|
-
popoutPlacement?: "top" | "bottom";
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Props common to all single-select components
|
|
34
|
-
*/
|
|
35
|
-
export interface BaseSingleSelectProps<T extends DataWithId>
|
|
36
|
-
extends BaseSelectProps<T> {
|
|
37
|
-
selectedItemId?: T["id"] | null;
|
|
38
|
-
onSelectedItemIdChange?: (itemId: T["id"] | null) => void;
|
|
39
|
-
placeholderItem?: ToggleItem;
|
|
40
|
-
inputType?: "radio" | "icon";
|
|
41
|
-
removePlaceholderItem?: boolean;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Props common to all multi-select components
|
|
46
|
-
*/
|
|
47
|
-
export interface BaseMultiSelectProps<T extends DataWithId>
|
|
48
|
-
extends BaseSelectProps<T> {
|
|
49
|
-
selectedItemIds?: T["id"][];
|
|
50
|
-
onSelectedItemIdsChange?: (itemIds: T["id"][]) => void;
|
|
51
|
-
showSelectedItems?: boolean;
|
|
52
|
-
selectHeadings?: boolean;
|
|
53
|
-
selectAllItem?: ToggleItem;
|
|
54
|
-
inputType?: "checkbox" | "icon";
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Props common to all autocomplete/searchable components
|
|
59
|
-
*/
|
|
60
|
-
export interface BaseAutocompleteProps<T extends DataWithId>
|
|
61
|
-
extends BaseSelectProps<T> {
|
|
62
|
-
EmptyState: ReactNode;
|
|
63
|
-
inputValue?: string;
|
|
64
|
-
itemToSearchString?: (item: T) => string;
|
|
65
|
-
onInputValueChange?: (value: string) => void;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Props for single-select autocomplete/searchable components
|
|
70
|
-
*/
|
|
71
|
-
export interface BaseSingleAutocompleteProps<T extends DataWithId>
|
|
72
|
-
extends BaseSingleSelectProps<T>,
|
|
73
|
-
BaseAutocompleteProps<T> {
|
|
74
|
-
inputType?: "radio" | "icon";
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Props for multi-select autocomplete/searchable components
|
|
79
|
-
*/
|
|
80
|
-
export interface BaseMultiAutocompleteProps<T extends DataWithId>
|
|
81
|
-
extends BaseMultiSelectProps<T>,
|
|
82
|
-
BaseAutocompleteProps<T> {
|
|
83
|
-
inputType?: "checkbox" | "icon";
|
|
84
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import styled from "styled-components";
|
|
2
|
-
import Text from "@sproutsocial/seeds-react-text";
|
|
3
|
-
import {
|
|
4
|
-
border,
|
|
5
|
-
layout,
|
|
6
|
-
position,
|
|
7
|
-
flexbox,
|
|
8
|
-
grid,
|
|
9
|
-
color,
|
|
10
|
-
space,
|
|
11
|
-
} from "styled-system";
|
|
12
|
-
import type { TypeStyledMenuGroupProps } from "./MenuGroupTypes";
|
|
13
|
-
|
|
14
|
-
export const StyledMenuGroup = styled.li<TypeStyledMenuGroupProps>`
|
|
15
|
-
font-family: ${({ theme }) => theme.fontFamily};
|
|
16
|
-
&:not(:last-child) {
|
|
17
|
-
border-bottom: 1px solid
|
|
18
|
-
${({ theme }) => theme.colors.container.border.base};
|
|
19
|
-
}
|
|
20
|
-
list-style: none;
|
|
21
|
-
margin: 0;
|
|
22
|
-
|
|
23
|
-
${border}
|
|
24
|
-
${position}
|
|
25
|
-
${color}
|
|
26
|
-
${flexbox}
|
|
27
|
-
${grid}
|
|
28
|
-
${layout}
|
|
29
|
-
${space}
|
|
30
|
-
`;
|
|
31
|
-
|
|
32
|
-
export const StyledTitle = styled(Text.SmallSubHeadline)`
|
|
33
|
-
font-size: ${({ theme }) => theme.typography[100].fontSize};
|
|
34
|
-
margin: ${({ theme }) => theme.space[400]} ${({ theme }) => theme.space[400]}
|
|
35
|
-
0px ${({ theme }) => theme.space[400]};
|
|
36
|
-
`;
|
|
37
|
-
|
|
38
|
-
export const StyledMenuGroupUl = styled.ul`
|
|
39
|
-
margin: 0;
|
|
40
|
-
padding: 0;
|
|
41
|
-
`;
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export interface DataWithId {
|
|
2
|
-
id: string | number;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export interface ToggleItem {
|
|
6
|
-
id: "select-all" | "clear-selection";
|
|
7
|
-
label: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface GroupHeading {
|
|
11
|
-
type: "heading";
|
|
12
|
-
id: string;
|
|
13
|
-
label: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export type GroupedItem<T> = T | GroupHeading | ToggleItem;
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { useMemo } from "react";
|
|
2
|
-
import { isGroupHeading } from "./utils";
|
|
3
|
-
import type { GroupedItem, DataWithId } from "./types";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Hook to map the highlighted index from Downshift (which only knows about filtered items)
|
|
7
|
-
* to the full items array (which includes headings).
|
|
8
|
-
*
|
|
9
|
-
* @param items - The full items array (includes headings, toggle items, and data items)
|
|
10
|
-
* @param itemsForDownshift - The filtered items array passed to Downshift (may exclude headings)
|
|
11
|
-
* @param downshiftHighlightedIndex - The highlighted index from Downshift
|
|
12
|
-
* @param hasGroups - Whether groupBy is provided (determines if we need to map indices)
|
|
13
|
-
* @param selectHeadings - Whether headings are selectable (affects how we map indices)
|
|
14
|
-
* @returns The highlighted index in the full items array, or null if invalid
|
|
15
|
-
*/
|
|
16
|
-
export function useHighlightedIndex<T extends DataWithId>(
|
|
17
|
-
items: GroupedItem<T>[],
|
|
18
|
-
itemsForDownshift: GroupedItem<T>[],
|
|
19
|
-
downshiftHighlightedIndex: number | null | undefined,
|
|
20
|
-
hasGroups: boolean,
|
|
21
|
-
selectHeadings: boolean
|
|
22
|
-
): number | null {
|
|
23
|
-
return useMemo<number | null>(() => {
|
|
24
|
-
if (
|
|
25
|
-
downshiftHighlightedIndex === null ||
|
|
26
|
-
downshiftHighlightedIndex === undefined ||
|
|
27
|
-
downshiftHighlightedIndex < 0
|
|
28
|
-
) {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// If no groups, the indices are the same
|
|
33
|
-
if (!hasGroups) {
|
|
34
|
-
return downshiftHighlightedIndex;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Get the highlighted item from the downshift items array
|
|
38
|
-
const highlightedItem = itemsForDownshift[downshiftHighlightedIndex];
|
|
39
|
-
if (!highlightedItem) {
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Find the index of this item in the full items array
|
|
44
|
-
if (selectHeadings) {
|
|
45
|
-
// When headings are selectable, itemsForDownshift === items,
|
|
46
|
-
// so we can find the index directly in the items array
|
|
47
|
-
return items.findIndex((item) => item === highlightedItem);
|
|
48
|
-
} else {
|
|
49
|
-
// When headings are not selectable, itemsForDownshift excludes headings,
|
|
50
|
-
// so we need to find the item in the full items array, skipping headings
|
|
51
|
-
return items.findIndex(
|
|
52
|
-
(item) => !isGroupHeading(item) && item === highlightedItem
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
}, [
|
|
56
|
-
items,
|
|
57
|
-
itemsForDownshift,
|
|
58
|
-
downshiftHighlightedIndex,
|
|
59
|
-
hasGroups,
|
|
60
|
-
selectHeadings,
|
|
61
|
-
]);
|
|
62
|
-
}
|