@sproutsocial/seeds-react-drawer 1.0.2 → 1.1.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.
@@ -8,14 +8,14 @@ $ tsup --dts
8
8
  CLI Cleaning output folder
9
9
  CJS Build start
10
10
  ESM Build start
11
- CJS dist/index.js 9.40 KB
12
- CJS dist/index.js.map 15.31 KB
13
- CJS ⚡️ Build success in 229ms
14
- ESM dist/esm/index.js 6.90 KB
15
- ESM dist/esm/index.js.map 15.15 KB
16
- ESM ⚡️ Build success in 212ms
11
+ CJS dist/index.js 9.66 KB
12
+ CJS dist/index.js.map 15.98 KB
13
+ CJS ⚡️ Build success in 260ms
14
+ ESM dist/esm/index.js 7.10 KB
15
+ ESM dist/esm/index.js.map 15.82 KB
16
+ ESM ⚡️ Build success in 269ms
17
17
  DTS Build start
18
- DTS ⚡️ Build success in 45262ms
19
- DTS dist/index.d.ts 3.35 KB
20
- DTS dist/index.d.mts 3.35 KB
21
- Done in 54.99s.
18
+ DTS ⚡️ Build success in 44452ms
19
+ DTS dist/index.d.ts 3.76 KB
20
+ DTS dist/index.d.mts 3.76 KB
21
+ Done in 52.62s.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @sproutsocial/seeds-react-drawer
2
2
 
3
+ ## 1.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - b84d0aa: Adds Context for managing a lock state of the Drawer.
8
+
9
+ This lock state can used when a Drawer contains other components (like Menu) that also listen for Escape key events.
10
+ Locking the Drawer prevents it from closing in response to an Escape keydown.
11
+
12
+ Example usage:
13
+
14
+ ```tsx
15
+ const { setIsLocked } = useContext(DrawerContext);
16
+
17
+ // Lock the drawer to prevent event conflicts
18
+ setIsLocked(true);
19
+ ```
20
+
21
+ ## 1.0.3
22
+
23
+ ### Patch Changes
24
+
25
+ - Updated dependencies [47580c4]
26
+ - @sproutsocial/seeds-react-theme@3.1.0
27
+ - @sproutsocial/seeds-react-box@1.1.4
28
+ - @sproutsocial/seeds-react-icon@1.1.4
29
+ - @sproutsocial/seeds-react-button@1.2.3
30
+
3
31
  ## 1.0.2
4
32
 
5
33
  ### Patch Changes
package/dist/esm/index.js CHANGED
@@ -66,21 +66,22 @@ var AnimatedDrawer = animated(styles_default);
66
66
  var doesRefContainEventTarget = (ref, event) => {
67
67
  return ref.current && event.target instanceof Node && ref.current.contains(event.target);
68
68
  };
69
- var DrawerContext = React.createContext({});
69
+ var DrawerContext = React.createContext({
70
+ isLocked: false,
71
+ setIsLocked: () => {
72
+ }
73
+ });
70
74
  var DrawerCloseButton = (props) => {
71
- const { onClose, closeButtonLabel } = useContext(DrawerContext);
75
+ const drawerContext = useContext(DrawerContext);
72
76
  if (props.render) {
73
- return props.render({
74
- onClose,
75
- closeButtonLabel
76
- }) ?? null;
77
+ return props.render(drawerContext) ?? null;
77
78
  }
78
79
  return /* @__PURE__ */ jsx(
79
80
  Button,
80
81
  {
81
82
  appearance: "pill",
82
- "aria-label": closeButtonLabel,
83
- onClick: onClose,
83
+ "aria-label": drawerContext.closeButtonLabel,
84
+ onClick: drawerContext.onClose,
84
85
  ...props,
85
86
  children: props.children || /* @__PURE__ */ jsx(Icon, { "aria-hidden": true, name: "x-outline" })
86
87
  }
@@ -131,13 +132,14 @@ var useCloseOnBodyClick = ({
131
132
  onClose,
132
133
  closeTargets
133
134
  }) => {
135
+ const { isLocked } = useContext(DrawerContext);
134
136
  useEffect(() => {
135
137
  const documentBody = document.body;
136
138
  if (!documentBody) {
137
139
  return;
138
140
  }
139
141
  const onEsc = (event) => {
140
- if (event.key === "Escape") {
142
+ if (!isLocked && event.key === "Escape") {
141
143
  onClose();
142
144
  }
143
145
  };
@@ -175,7 +177,7 @@ var useCloseOnBodyClick = ({
175
177
  );
176
178
  }
177
179
  };
178
- }, [onClose, disableCloseOnClickOutside, closeTargets, ref]);
180
+ }, [onClose, disableCloseOnClickOutside, closeTargets, ref, isLocked]);
179
181
  };
180
182
  var Drawer = ({
181
183
  id,
@@ -243,12 +245,15 @@ var DrawerContainer = ({
243
245
  width = 600,
244
246
  ...rest
245
247
  }) => {
248
+ const [isLocked, setIsLocked] = React.useState(false);
246
249
  return /* @__PURE__ */ jsx(Portal, { id, children: /* @__PURE__ */ jsx(
247
250
  DrawerContext.Provider,
248
251
  {
249
252
  value: {
250
253
  onClose,
251
- closeButtonLabel
254
+ closeButtonLabel,
255
+ isLocked,
256
+ setIsLocked
252
257
  },
253
258
  children: /* @__PURE__ */ jsx(
254
259
  Drawer,
@@ -286,6 +291,7 @@ import "react";
286
291
  var index_default = Drawer_default;
287
292
  export {
288
293
  Drawer_default as Drawer,
294
+ DrawerContext,
289
295
  index_default as default
290
296
  };
291
297
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/Drawer.tsx","../../src/styles.ts","../../src/DrawerTypes.ts","../../src/index.ts"],"sourcesContent":["import * as React from \"react\";\nimport { useContext, useEffect, useRef } from \"react\";\nimport FocusLock from \"react-focus-lock\";\nimport { animated, useTransition } from \"@react-spring/web\";\nimport { MOTION_DURATION_MEDIUM } from \"@sproutsocial/seeds-motion/unitless\";\nimport Box from \"@sproutsocial/seeds-react-box\";\nimport Button from \"@sproutsocial/seeds-react-button\";\nimport Icon from \"@sproutsocial/seeds-react-icon\";\n// eslint-disable-next-line import/no-deprecated\nimport Text from \"@sproutsocial/seeds-react-text\";\nimport Portal from \"@sproutsocial/seeds-react-portal\";\nimport Container, { Content } from \"./styles\";\nimport type {\n TypeDrawerContext,\n TypeDrawerCloseButtonProps,\n TypeDrawerHeaderProps,\n TypeDrawerProps,\n TypeInnerDrawerProps,\n TypeDrawerContentProps,\n TypeUseCloseOnBodyClickProps,\n} from \"./DrawerTypes\";\n\nconst useSlideTransition = ({\n isVisible,\n width,\n direction,\n}: {\n isVisible: boolean;\n width: number;\n direction: \"left\" | \"right\";\n}) => {\n const offset = width * (direction === \"left\" ? -1 : 1);\n\n return useTransition(isVisible, {\n from: {\n opacity: 0,\n x: offset,\n },\n enter: {\n opacity: 1,\n x: 0,\n },\n leave: {\n opacity: 0,\n x: offset,\n },\n config: {\n duration: MOTION_DURATION_MEDIUM * 1000,\n },\n });\n};\n\nconst AnimatedDrawer = animated(Container);\n\nconst doesRefContainEventTarget = (\n ref: { current: { contains: (arg0: any) => any } },\n event: Event\n) => {\n return (\n ref.current &&\n event.target instanceof Node &&\n ref.current.contains(event.target)\n );\n};\n\nconst DrawerContext = React.createContext<TypeDrawerContext>({});\n\nconst DrawerCloseButton = (props: TypeDrawerCloseButtonProps) => {\n const { onClose, closeButtonLabel } = useContext(DrawerContext);\n\n if (props.render) {\n return (\n props.render({\n onClose,\n closeButtonLabel,\n }) ?? null\n );\n }\n\n return (\n <Button\n appearance=\"pill\"\n aria-label={closeButtonLabel}\n onClick={onClose}\n {...props}\n >\n {props.children || <Icon aria-hidden name=\"x-outline\" />}\n </Button>\n );\n};\n\nconst DrawerHeader = ({\n title = \"\",\n id = undefined,\n children,\n render,\n ...rest\n}: TypeDrawerHeaderProps) => {\n const drawerContext = useContext(DrawerContext);\n\n if (render) {\n return render(drawerContext);\n }\n\n return (\n <Box\n display=\"flex\"\n flex=\"0 0 auto\"\n justifyContent=\"space-between\"\n alignItems=\"center\"\n pt={400}\n px={450}\n {...rest}\n >\n {children || (\n <React.Fragment>\n <Text\n as=\"h2\"\n fontSize={400}\n fontWeight=\"semibold\"\n color=\"text.headline\"\n id={id}\n >\n {title}\n </Text>\n <DrawerCloseButton />\n </React.Fragment>\n )}\n </Box>\n );\n};\n\nconst DrawerContent = ({ children, ...rest }: TypeDrawerContentProps) => (\n <Content height=\"100%\" p={450} color=\"text.body\" {...rest}>\n {children}\n </Content>\n);\n\nconst useCloseOnBodyClick = ({\n ref,\n disableCloseOnClickOutside,\n onClose,\n closeTargets,\n}: TypeUseCloseOnBodyClickProps) => {\n useEffect(() => {\n const documentBody = document.body;\n\n if (!documentBody) {\n return;\n }\n\n const onEsc = (event: KeyboardEvent): void => {\n if (event.key === \"Escape\") {\n onClose();\n }\n };\n\n const bodyClick = (event: Event): void => {\n if (\n // @ts-ignore I'm not sure how to type this ref properly\n !doesRefContainEventTarget(ref, event) &&\n !disableCloseOnClickOutside\n ) {\n onClose();\n }\n };\n\n documentBody?.addEventListener(\"keydown\", onEsc, { capture: true });\n\n if (closeTargets) {\n closeTargets.forEach((targetElement) =>\n targetElement?.addEventListener(\"click\", bodyClick, { capture: true })\n );\n } else {\n documentBody.firstElementChild?.addEventListener(\"click\", bodyClick, {\n capture: true,\n });\n }\n\n return () => {\n documentBody?.removeEventListener(\"keydown\", onEsc, { capture: true });\n\n if (closeTargets) {\n closeTargets.forEach((targetElement) =>\n targetElement?.removeEventListener(\"click\", bodyClick, {\n capture: true,\n })\n );\n } else {\n documentBody.firstElementChild?.removeEventListener(\n \"click\",\n bodyClick,\n { capture: true }\n );\n }\n };\n }, [onClose, disableCloseOnClickOutside, closeTargets, ref]);\n};\n\nconst Drawer = ({\n id,\n offset,\n direction,\n children,\n disableCloseOnClickOutside,\n onClose,\n zIndex,\n closeTargets,\n width,\n focusLockExemptCheck,\n isOpen,\n ...rest\n}: TypeInnerDrawerProps) => {\n const ref = useRef(null);\n useCloseOnBodyClick({\n ref,\n disableCloseOnClickOutside,\n onClose,\n closeTargets,\n });\n\n const transition = useSlideTransition({\n isVisible: isOpen,\n width,\n direction,\n });\n\n return (\n <FocusLock\n key={id}\n autoFocus={true}\n returnFocus\n whiteList={\n focusLockExemptCheck ? (e) => !focusLockExemptCheck(e) : undefined\n }\n >\n {transition((style, isVisible) =>\n isVisible ? (\n <AnimatedDrawer\n ref={ref}\n style={{ ...style, zIndex }}\n width={width}\n offset={offset}\n direction={direction}\n data-qa-drawer={id}\n role=\"dialog\"\n {...rest}\n >\n {children}\n </AnimatedDrawer>\n ) : null\n )}\n </FocusLock>\n );\n};\n\nconst DrawerContainer = ({\n children,\n closeButtonLabel,\n direction = \"right\",\n disableCloseOnClickOutside = false,\n id,\n isOpen,\n offset = 0,\n onClose,\n zIndex = 7,\n closeTargets = [],\n width = 600,\n ...rest\n}: TypeDrawerProps) => {\n return (\n <Portal id={id}>\n <DrawerContext.Provider\n value={{\n onClose,\n closeButtonLabel,\n }}\n >\n <Drawer\n isOpen={isOpen}\n id={id}\n offset={offset}\n direction={direction}\n disableCloseOnClickOutside={disableCloseOnClickOutside}\n onClose={onClose}\n zIndex={zIndex}\n closeTargets={closeTargets}\n width={width}\n data-qa-drawer={id || \"\"}\n data-qa-drawer-isopen={isOpen}\n {...rest}\n >\n {children}\n </Drawer>\n </DrawerContext.Provider>\n </Portal>\n );\n};\n\nDrawerHeader.displayName = \"Drawer.Header\";\nDrawerContent.displayName = \"Drawer.Content\";\nDrawerCloseButton.displayName = \"Drawer.CloseButton\";\n\nDrawerContainer.Header = DrawerHeader;\nDrawerContainer.Content = DrawerContent;\nDrawerContainer.CloseButton = DrawerCloseButton;\n\nexport default DrawerContainer;\n","import type { TypeDrawerProps } from \"./DrawerTypes\";\nimport styled, { css } from \"styled-components\";\nimport { COMMON } from \"@sproutsocial/seeds-react-system-props\";\nimport type { TypeSystemCommonProps } from \"@sproutsocial/seeds-react-system-props\";\n\nimport Box from \"@sproutsocial/seeds-react-box\";\n\nexport const Content = styled(Box)`\n overflow-y: auto;\n`;\n\ninterface ContainerType\n extends Pick<TypeDrawerProps, \"offset\" | \"direction\">,\n TypeSystemCommonProps {\n width: number;\n}\n\nconst Container = styled.div<ContainerType>`\n display: flex;\n flex-direction: column;\n position: fixed;\n top: 0;\n height: 100%;\n width: ${(props) => props.width}px;\n background-color: ${(props) => props.theme.colors.container.background.base};\n box-shadow: ${(props) => props.theme.shadows.high};\n filter: blur(0);\n\n ${(props) => css`\n ${props.direction}: ${props.offset}px;\n `}\n\n ${COMMON}\n`;\nexport default Container;\n","import * as React from \"react\";\nimport type {\n TypeSystemCommonProps,\n TypeStyledComponentsCommonProps,\n} from \"@sproutsocial/seeds-react-system-props\";\nimport type { TypeBoxProps } from \"@sproutsocial/seeds-react-box\";\nimport type { TypeButtonProps } from \"@sproutsocial/seeds-react-button\";\n\ntype DrawerAnimationDirection = \"left\" | \"right\";\n\nexport interface TypeDrawerContext {\n /** Callback for close button */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onClose?: () => any;\n\n /** aria-label for drawer close button */\n closeButtonLabel?: string;\n}\n// TODO: Should the render prop be a React.FC?\nexport interface TypeDrawerCloseButtonProps\n extends Omit<TypeButtonProps, \"children\"> {\n /** An optional function that receives the context of the parent drawer as an argument. Can be used to customize the on-close behavior. */\n render?: React.FC<TypeDrawerContext>;\n children?: React.ReactNode;\n}\n\nexport interface TypeDrawerHeaderProps extends TypeBoxProps {\n title?: string;\n children?: React.ReactNode;\n\n /** An optional function that receives the context of the parent drawer as an argument. Can be used to customize the appearance of the header. */\n render?: React.FC<TypeDrawerContext>;\n}\n\nexport interface TypeInnerDrawerProps\n extends Omit<TypeDrawerProps, \"closeButtonLabel\"> {\n width: number;\n direction: DrawerAnimationDirection;\n}\n\ntype useBodyClicksProps = Pick<\n TypeDrawerProps,\n \"closeTargets\" | \"onClose\" | \"disableCloseOnClickOutside\"\n>;\n\nexport interface TypeUseCloseOnBodyClickProps\n extends Pick<\n TypeDrawerProps,\n \"closeTargets\" | \"onClose\" | \"disableCloseOnClickOutside\"\n > {\n ref?: React.RefObject<HTMLElement | null>;\n}\n\nexport interface TypeDrawerProps\n extends TypeStyledComponentsCommonProps,\n TypeSystemCommonProps,\n Omit<React.ComponentPropsWithoutRef<\"nav\">, \"color\"> {\n children: React.ReactNode;\n\n /** Label for the close button. Usually this should be \"Close\" */\n closeButtonLabel: string;\n\n /** Whether the drawer slides in from the left or right side of the screen */\n direction?: DrawerAnimationDirection;\n\n /** In some cases, you may not want the user to be able to click outside of the drawer to close it. You can disable that with this prop. */\n disableCloseOnClickOutside?: boolean;\n id: string;\n isOpen: boolean;\n offset?: number;\n onClose: () => void;\n zIndex?: number;\n closeTargets?: Array<Element>;\n width?: number;\n focusLockExemptCheck?: (element: HTMLElement) => boolean;\n}\n\nexport interface TypeDrawerContentProps extends TypeBoxProps {\n children?: React.ReactNode;\n}\n","import Drawer from \"./Drawer\";\n\nexport default Drawer;\nexport { Drawer };\nexport * from \"./DrawerTypes\";\n"],"mappings":"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","names":["Box","Box"]}
1
+ {"version":3,"sources":["../../src/Drawer.tsx","../../src/styles.ts","../../src/DrawerTypes.ts","../../src/index.ts"],"sourcesContent":["import * as React from \"react\";\nimport { useContext, useEffect, useRef } from \"react\";\nimport FocusLock from \"react-focus-lock\";\nimport { animated, useTransition } from \"@react-spring/web\";\nimport { MOTION_DURATION_MEDIUM } from \"@sproutsocial/seeds-motion/unitless\";\nimport Box from \"@sproutsocial/seeds-react-box\";\nimport Button from \"@sproutsocial/seeds-react-button\";\nimport Icon from \"@sproutsocial/seeds-react-icon\";\n// eslint-disable-next-line import/no-deprecated\nimport Text from \"@sproutsocial/seeds-react-text\";\nimport Portal from \"@sproutsocial/seeds-react-portal\";\nimport Container, { Content } from \"./styles\";\nimport type {\n TypeDrawerContext,\n TypeDrawerCloseButtonProps,\n TypeDrawerHeaderProps,\n TypeDrawerProps,\n TypeInnerDrawerProps,\n TypeDrawerContentProps,\n TypeUseCloseOnBodyClickProps,\n} from \"./DrawerTypes\";\n\nconst useSlideTransition = ({\n isVisible,\n width,\n direction,\n}: {\n isVisible: boolean;\n width: number;\n direction: \"left\" | \"right\";\n}) => {\n const offset = width * (direction === \"left\" ? -1 : 1);\n\n return useTransition(isVisible, {\n from: {\n opacity: 0,\n x: offset,\n },\n enter: {\n opacity: 1,\n x: 0,\n },\n leave: {\n opacity: 0,\n x: offset,\n },\n config: {\n duration: MOTION_DURATION_MEDIUM * 1000,\n },\n });\n};\n\nconst AnimatedDrawer = animated(Container);\n\nconst doesRefContainEventTarget = (\n ref: { current: { contains: (arg0: any) => any } },\n event: Event\n) => {\n return (\n ref.current &&\n event.target instanceof Node &&\n ref.current.contains(event.target)\n );\n};\n\nexport const DrawerContext = React.createContext<TypeDrawerContext>({\n isLocked: false,\n setIsLocked: () => {},\n});\n\nconst DrawerCloseButton = (props: TypeDrawerCloseButtonProps) => {\n const drawerContext = useContext(DrawerContext);\n\n if (props.render) {\n return props.render(drawerContext) ?? null;\n }\n\n return (\n <Button\n appearance=\"pill\"\n aria-label={drawerContext.closeButtonLabel}\n onClick={drawerContext.onClose}\n {...props}\n >\n {props.children || <Icon aria-hidden name=\"x-outline\" />}\n </Button>\n );\n};\n\nconst DrawerHeader = ({\n title = \"\",\n id = undefined,\n children,\n render,\n ...rest\n}: TypeDrawerHeaderProps) => {\n const drawerContext = useContext(DrawerContext);\n\n if (render) {\n return render(drawerContext);\n }\n\n return (\n <Box\n display=\"flex\"\n flex=\"0 0 auto\"\n justifyContent=\"space-between\"\n alignItems=\"center\"\n pt={400}\n px={450}\n {...rest}\n >\n {children || (\n <React.Fragment>\n <Text\n as=\"h2\"\n fontSize={400}\n fontWeight=\"semibold\"\n color=\"text.headline\"\n id={id}\n >\n {title}\n </Text>\n <DrawerCloseButton />\n </React.Fragment>\n )}\n </Box>\n );\n};\n\nconst DrawerContent = ({ children, ...rest }: TypeDrawerContentProps) => (\n <Content height=\"100%\" p={450} color=\"text.body\" {...rest}>\n {children}\n </Content>\n);\n\nconst useCloseOnBodyClick = ({\n ref,\n disableCloseOnClickOutside,\n onClose,\n closeTargets,\n}: TypeUseCloseOnBodyClickProps) => {\n const { isLocked } = useContext(DrawerContext);\n\n useEffect(() => {\n const documentBody = document.body;\n\n if (!documentBody) {\n return;\n }\n\n const onEsc = (event: KeyboardEvent): void => {\n if (!isLocked && event.key === \"Escape\") {\n onClose();\n }\n };\n\n const bodyClick = (event: Event): void => {\n if (\n // @ts-ignore I'm not sure how to type this ref properly\n !doesRefContainEventTarget(ref, event) &&\n !disableCloseOnClickOutside\n ) {\n onClose();\n }\n };\n\n documentBody?.addEventListener(\"keydown\", onEsc, { capture: true });\n\n if (closeTargets) {\n closeTargets.forEach((targetElement) =>\n targetElement?.addEventListener(\"click\", bodyClick, { capture: true })\n );\n } else {\n documentBody.firstElementChild?.addEventListener(\"click\", bodyClick, {\n capture: true,\n });\n }\n\n return () => {\n documentBody?.removeEventListener(\"keydown\", onEsc, { capture: true });\n\n if (closeTargets) {\n closeTargets.forEach((targetElement) =>\n targetElement?.removeEventListener(\"click\", bodyClick, {\n capture: true,\n })\n );\n } else {\n documentBody.firstElementChild?.removeEventListener(\n \"click\",\n bodyClick,\n { capture: true }\n );\n }\n };\n }, [onClose, disableCloseOnClickOutside, closeTargets, ref, isLocked]);\n};\n\nconst Drawer = ({\n id,\n offset,\n direction,\n children,\n disableCloseOnClickOutside,\n onClose,\n zIndex,\n closeTargets,\n width,\n focusLockExemptCheck,\n isOpen,\n ...rest\n}: TypeInnerDrawerProps) => {\n const ref = useRef(null);\n useCloseOnBodyClick({\n ref,\n disableCloseOnClickOutside,\n onClose,\n closeTargets,\n });\n\n const transition = useSlideTransition({\n isVisible: isOpen,\n width,\n direction,\n });\n\n return (\n <FocusLock\n key={id}\n autoFocus={true}\n returnFocus\n whiteList={\n focusLockExemptCheck ? (e) => !focusLockExemptCheck(e) : undefined\n }\n >\n {transition((style, isVisible) =>\n isVisible ? (\n <AnimatedDrawer\n ref={ref}\n style={{ ...style, zIndex }}\n width={width}\n offset={offset}\n direction={direction}\n data-qa-drawer={id}\n role=\"dialog\"\n {...rest}\n >\n {children}\n </AnimatedDrawer>\n ) : null\n )}\n </FocusLock>\n );\n};\n\nconst DrawerContainer = ({\n children,\n closeButtonLabel,\n direction = \"right\",\n disableCloseOnClickOutside = false,\n id,\n isOpen,\n offset = 0,\n onClose,\n zIndex = 7,\n closeTargets = [],\n width = 600,\n ...rest\n}: TypeDrawerProps) => {\n const [isLocked, setIsLocked] = React.useState(false);\n return (\n <Portal id={id}>\n <DrawerContext.Provider\n value={{\n onClose,\n closeButtonLabel,\n isLocked,\n setIsLocked,\n }}\n >\n <Drawer\n isOpen={isOpen}\n id={id}\n offset={offset}\n direction={direction}\n disableCloseOnClickOutside={disableCloseOnClickOutside}\n onClose={onClose}\n zIndex={zIndex}\n closeTargets={closeTargets}\n width={width}\n data-qa-drawer={id || \"\"}\n data-qa-drawer-isopen={isOpen}\n {...rest}\n >\n {children}\n </Drawer>\n </DrawerContext.Provider>\n </Portal>\n );\n};\n\nDrawerHeader.displayName = \"Drawer.Header\";\nDrawerContent.displayName = \"Drawer.Content\";\nDrawerCloseButton.displayName = \"Drawer.CloseButton\";\n\nDrawerContainer.Header = DrawerHeader;\nDrawerContainer.Content = DrawerContent;\nDrawerContainer.CloseButton = DrawerCloseButton;\n\nexport default DrawerContainer;\n","import type { TypeDrawerProps } from \"./DrawerTypes\";\nimport styled, { css } from \"styled-components\";\nimport { COMMON } from \"@sproutsocial/seeds-react-system-props\";\nimport type { TypeSystemCommonProps } from \"@sproutsocial/seeds-react-system-props\";\n\nimport Box from \"@sproutsocial/seeds-react-box\";\n\nexport const Content = styled(Box)`\n overflow-y: auto;\n`;\n\ninterface ContainerType\n extends Pick<TypeDrawerProps, \"offset\" | \"direction\">,\n TypeSystemCommonProps {\n width: number;\n}\n\nconst Container = styled.div<ContainerType>`\n display: flex;\n flex-direction: column;\n position: fixed;\n top: 0;\n height: 100%;\n width: ${(props) => props.width}px;\n background-color: ${(props) => props.theme.colors.container.background.base};\n box-shadow: ${(props) => props.theme.shadows.high};\n filter: blur(0);\n\n ${(props) => css`\n ${props.direction}: ${props.offset}px;\n `}\n\n ${COMMON}\n`;\nexport default Container;\n","import * as React from \"react\";\nimport type {\n TypeSystemCommonProps,\n TypeStyledComponentsCommonProps,\n} from \"@sproutsocial/seeds-react-system-props\";\nimport type { TypeBoxProps } from \"@sproutsocial/seeds-react-box\";\nimport type { TypeButtonProps } from \"@sproutsocial/seeds-react-button\";\n\ntype DrawerAnimationDirection = \"left\" | \"right\";\n\nexport interface TypeDrawerContext {\n /** Callback for close button */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onClose?: () => any;\n\n /** aria-label for drawer close button */\n closeButtonLabel?: string;\n /**\n * isLocked and setIsLocked are used when a Drawer contains other components (like Menu) that also\n * listen for Escape key events. By locking the Drawer, we can prevent conflicts\n * where multiple components try to handle the same keydown event.\n */\n isLocked: boolean;\n setIsLocked: (locked: boolean) => void;\n}\n// TODO: Should the render prop be a React.FC?\nexport interface TypeDrawerCloseButtonProps\n extends Omit<TypeButtonProps, \"children\"> {\n /** An optional function that receives the context of the parent drawer as an argument. Can be used to customize the on-close behavior. */\n render?: React.FC<TypeDrawerContext>;\n children?: React.ReactNode;\n}\n\nexport interface TypeDrawerHeaderProps extends TypeBoxProps {\n title?: string;\n children?: React.ReactNode;\n\n /** An optional function that receives the context of the parent drawer as an argument. Can be used to customize the appearance of the header. */\n render?: React.FC<TypeDrawerContext>;\n}\n\nexport interface TypeInnerDrawerProps\n extends Omit<TypeDrawerProps, \"closeButtonLabel\"> {\n width: number;\n direction: DrawerAnimationDirection;\n}\n\ntype useBodyClicksProps = Pick<\n TypeDrawerProps,\n \"closeTargets\" | \"onClose\" | \"disableCloseOnClickOutside\"\n>;\n\nexport interface TypeUseCloseOnBodyClickProps\n extends Pick<\n TypeDrawerProps,\n \"closeTargets\" | \"onClose\" | \"disableCloseOnClickOutside\"\n > {\n ref?: React.RefObject<HTMLElement | null>;\n}\n\nexport interface TypeDrawerProps\n extends TypeStyledComponentsCommonProps,\n TypeSystemCommonProps,\n Omit<React.ComponentPropsWithoutRef<\"nav\">, \"color\"> {\n children: React.ReactNode;\n\n /** Label for the close button. Usually this should be \"Close\" */\n closeButtonLabel: string;\n\n /** Whether the drawer slides in from the left or right side of the screen */\n direction?: DrawerAnimationDirection;\n\n /** In some cases, you may not want the user to be able to click outside of the drawer to close it. You can disable that with this prop. */\n disableCloseOnClickOutside?: boolean;\n id: string;\n isOpen: boolean;\n offset?: number;\n onClose: () => void;\n zIndex?: number;\n closeTargets?: Array<Element>;\n width?: number;\n focusLockExemptCheck?: (element: HTMLElement) => boolean;\n}\n\nexport interface TypeDrawerContentProps extends TypeBoxProps {\n children?: React.ReactNode;\n}\n","import Drawer, { DrawerContext } from \"./Drawer\";\n\nexport default Drawer;\nexport { Drawer, DrawerContext };\nexport * from \"./DrawerTypes\";\n"],"mappings":"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","names":["Box","Box"]}
package/dist/index.d.mts CHANGED
@@ -10,6 +10,13 @@ interface TypeDrawerContext {
10
10
  onClose?: () => any;
11
11
  /** aria-label for drawer close button */
12
12
  closeButtonLabel?: string;
13
+ /**
14
+ * isLocked and setIsLocked are used when a Drawer contains other components (like Menu) that also
15
+ * listen for Escape key events. By locking the Drawer, we can prevent conflicts
16
+ * where multiple components try to handle the same keydown event.
17
+ */
18
+ isLocked: boolean;
19
+ setIsLocked: (locked: boolean) => void;
13
20
  }
14
21
  interface TypeDrawerCloseButtonProps extends Omit<TypeButtonProps, "children"> {
15
22
  /** An optional function that receives the context of the parent drawer as an argument. Can be used to customize the on-close behavior. */
@@ -50,6 +57,7 @@ interface TypeDrawerContentProps extends TypeBoxProps {
50
57
  children?: React.ReactNode;
51
58
  }
52
59
 
60
+ declare const DrawerContext: React.Context<TypeDrawerContext>;
53
61
  declare const DrawerContainer: {
54
62
  ({ children, closeButtonLabel, direction, disableCloseOnClickOutside, id, isOpen, offset, onClose, zIndex, closeTargets, width, ...rest }: TypeDrawerProps): react_jsx_runtime.JSX.Element;
55
63
  Header: {
@@ -66,4 +74,4 @@ declare const DrawerContainer: {
66
74
  };
67
75
  };
68
76
 
69
- export { DrawerContainer as Drawer, type TypeDrawerCloseButtonProps, type TypeDrawerContentProps, type TypeDrawerContext, type TypeDrawerHeaderProps, type TypeDrawerProps, type TypeInnerDrawerProps, type TypeUseCloseOnBodyClickProps, DrawerContainer as default };
77
+ export { DrawerContainer as Drawer, DrawerContext, type TypeDrawerCloseButtonProps, type TypeDrawerContentProps, type TypeDrawerContext, type TypeDrawerHeaderProps, type TypeDrawerProps, type TypeInnerDrawerProps, type TypeUseCloseOnBodyClickProps, DrawerContainer as default };
package/dist/index.d.ts CHANGED
@@ -10,6 +10,13 @@ interface TypeDrawerContext {
10
10
  onClose?: () => any;
11
11
  /** aria-label for drawer close button */
12
12
  closeButtonLabel?: string;
13
+ /**
14
+ * isLocked and setIsLocked are used when a Drawer contains other components (like Menu) that also
15
+ * listen for Escape key events. By locking the Drawer, we can prevent conflicts
16
+ * where multiple components try to handle the same keydown event.
17
+ */
18
+ isLocked: boolean;
19
+ setIsLocked: (locked: boolean) => void;
13
20
  }
14
21
  interface TypeDrawerCloseButtonProps extends Omit<TypeButtonProps, "children"> {
15
22
  /** An optional function that receives the context of the parent drawer as an argument. Can be used to customize the on-close behavior. */
@@ -50,6 +57,7 @@ interface TypeDrawerContentProps extends TypeBoxProps {
50
57
  children?: React.ReactNode;
51
58
  }
52
59
 
60
+ declare const DrawerContext: React.Context<TypeDrawerContext>;
53
61
  declare const DrawerContainer: {
54
62
  ({ children, closeButtonLabel, direction, disableCloseOnClickOutside, id, isOpen, offset, onClose, zIndex, closeTargets, width, ...rest }: TypeDrawerProps): react_jsx_runtime.JSX.Element;
55
63
  Header: {
@@ -66,4 +74,4 @@ declare const DrawerContainer: {
66
74
  };
67
75
  };
68
76
 
69
- export { DrawerContainer as Drawer, type TypeDrawerCloseButtonProps, type TypeDrawerContentProps, type TypeDrawerContext, type TypeDrawerHeaderProps, type TypeDrawerProps, type TypeInnerDrawerProps, type TypeUseCloseOnBodyClickProps, DrawerContainer as default };
77
+ export { DrawerContainer as Drawer, DrawerContext, type TypeDrawerCloseButtonProps, type TypeDrawerContentProps, type TypeDrawerContext, type TypeDrawerHeaderProps, type TypeDrawerProps, type TypeInnerDrawerProps, type TypeUseCloseOnBodyClickProps, DrawerContainer as default };
package/dist/index.js CHANGED
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  Drawer: () => Drawer_default,
34
+ DrawerContext: () => DrawerContext,
34
35
  default: () => index_default
35
36
  });
36
37
  module.exports = __toCommonJS(index_exports);
@@ -103,21 +104,22 @@ var AnimatedDrawer = (0, import_web.animated)(styles_default);
103
104
  var doesRefContainEventTarget = (ref, event) => {
104
105
  return ref.current && event.target instanceof Node && ref.current.contains(event.target);
105
106
  };
106
- var DrawerContext = React.createContext({});
107
+ var DrawerContext = React.createContext({
108
+ isLocked: false,
109
+ setIsLocked: () => {
110
+ }
111
+ });
107
112
  var DrawerCloseButton = (props) => {
108
- const { onClose, closeButtonLabel } = (0, import_react.useContext)(DrawerContext);
113
+ const drawerContext = (0, import_react.useContext)(DrawerContext);
109
114
  if (props.render) {
110
- return props.render({
111
- onClose,
112
- closeButtonLabel
113
- }) ?? null;
115
+ return props.render(drawerContext) ?? null;
114
116
  }
115
117
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
116
118
  import_seeds_react_button.default,
117
119
  {
118
120
  appearance: "pill",
119
- "aria-label": closeButtonLabel,
120
- onClick: onClose,
121
+ "aria-label": drawerContext.closeButtonLabel,
122
+ onClick: drawerContext.onClose,
121
123
  ...props,
122
124
  children: props.children || /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_seeds_react_icon.default, { "aria-hidden": true, name: "x-outline" })
123
125
  }
@@ -168,13 +170,14 @@ var useCloseOnBodyClick = ({
168
170
  onClose,
169
171
  closeTargets
170
172
  }) => {
173
+ const { isLocked } = (0, import_react.useContext)(DrawerContext);
171
174
  (0, import_react.useEffect)(() => {
172
175
  const documentBody = document.body;
173
176
  if (!documentBody) {
174
177
  return;
175
178
  }
176
179
  const onEsc = (event) => {
177
- if (event.key === "Escape") {
180
+ if (!isLocked && event.key === "Escape") {
178
181
  onClose();
179
182
  }
180
183
  };
@@ -212,7 +215,7 @@ var useCloseOnBodyClick = ({
212
215
  );
213
216
  }
214
217
  };
215
- }, [onClose, disableCloseOnClickOutside, closeTargets, ref]);
218
+ }, [onClose, disableCloseOnClickOutside, closeTargets, ref, isLocked]);
216
219
  };
217
220
  var Drawer = ({
218
221
  id,
@@ -280,12 +283,15 @@ var DrawerContainer = ({
280
283
  width = 600,
281
284
  ...rest
282
285
  }) => {
286
+ const [isLocked, setIsLocked] = React.useState(false);
283
287
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_seeds_react_portal.default, { id, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
284
288
  DrawerContext.Provider,
285
289
  {
286
290
  value: {
287
291
  onClose,
288
- closeButtonLabel
292
+ closeButtonLabel,
293
+ isLocked,
294
+ setIsLocked
289
295
  },
290
296
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
291
297
  Drawer,
@@ -323,6 +329,7 @@ var React2 = require("react");
323
329
  var index_default = Drawer_default;
324
330
  // Annotate the CommonJS export names for ESM import in node:
325
331
  0 && (module.exports = {
326
- Drawer
332
+ Drawer,
333
+ DrawerContext
327
334
  });
328
335
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/Drawer.tsx","../src/styles.ts","../src/DrawerTypes.ts"],"sourcesContent":["import Drawer from \"./Drawer\";\n\nexport default Drawer;\nexport { Drawer };\nexport * from \"./DrawerTypes\";\n","import * as React from \"react\";\nimport { useContext, useEffect, useRef } from \"react\";\nimport FocusLock from \"react-focus-lock\";\nimport { animated, useTransition } from \"@react-spring/web\";\nimport { MOTION_DURATION_MEDIUM } from \"@sproutsocial/seeds-motion/unitless\";\nimport Box from \"@sproutsocial/seeds-react-box\";\nimport Button from \"@sproutsocial/seeds-react-button\";\nimport Icon from \"@sproutsocial/seeds-react-icon\";\n// eslint-disable-next-line import/no-deprecated\nimport Text from \"@sproutsocial/seeds-react-text\";\nimport Portal from \"@sproutsocial/seeds-react-portal\";\nimport Container, { Content } from \"./styles\";\nimport type {\n TypeDrawerContext,\n TypeDrawerCloseButtonProps,\n TypeDrawerHeaderProps,\n TypeDrawerProps,\n TypeInnerDrawerProps,\n TypeDrawerContentProps,\n TypeUseCloseOnBodyClickProps,\n} from \"./DrawerTypes\";\n\nconst useSlideTransition = ({\n isVisible,\n width,\n direction,\n}: {\n isVisible: boolean;\n width: number;\n direction: \"left\" | \"right\";\n}) => {\n const offset = width * (direction === \"left\" ? -1 : 1);\n\n return useTransition(isVisible, {\n from: {\n opacity: 0,\n x: offset,\n },\n enter: {\n opacity: 1,\n x: 0,\n },\n leave: {\n opacity: 0,\n x: offset,\n },\n config: {\n duration: MOTION_DURATION_MEDIUM * 1000,\n },\n });\n};\n\nconst AnimatedDrawer = animated(Container);\n\nconst doesRefContainEventTarget = (\n ref: { current: { contains: (arg0: any) => any } },\n event: Event\n) => {\n return (\n ref.current &&\n event.target instanceof Node &&\n ref.current.contains(event.target)\n );\n};\n\nconst DrawerContext = React.createContext<TypeDrawerContext>({});\n\nconst DrawerCloseButton = (props: TypeDrawerCloseButtonProps) => {\n const { onClose, closeButtonLabel } = useContext(DrawerContext);\n\n if (props.render) {\n return (\n props.render({\n onClose,\n closeButtonLabel,\n }) ?? null\n );\n }\n\n return (\n <Button\n appearance=\"pill\"\n aria-label={closeButtonLabel}\n onClick={onClose}\n {...props}\n >\n {props.children || <Icon aria-hidden name=\"x-outline\" />}\n </Button>\n );\n};\n\nconst DrawerHeader = ({\n title = \"\",\n id = undefined,\n children,\n render,\n ...rest\n}: TypeDrawerHeaderProps) => {\n const drawerContext = useContext(DrawerContext);\n\n if (render) {\n return render(drawerContext);\n }\n\n return (\n <Box\n display=\"flex\"\n flex=\"0 0 auto\"\n justifyContent=\"space-between\"\n alignItems=\"center\"\n pt={400}\n px={450}\n {...rest}\n >\n {children || (\n <React.Fragment>\n <Text\n as=\"h2\"\n fontSize={400}\n fontWeight=\"semibold\"\n color=\"text.headline\"\n id={id}\n >\n {title}\n </Text>\n <DrawerCloseButton />\n </React.Fragment>\n )}\n </Box>\n );\n};\n\nconst DrawerContent = ({ children, ...rest }: TypeDrawerContentProps) => (\n <Content height=\"100%\" p={450} color=\"text.body\" {...rest}>\n {children}\n </Content>\n);\n\nconst useCloseOnBodyClick = ({\n ref,\n disableCloseOnClickOutside,\n onClose,\n closeTargets,\n}: TypeUseCloseOnBodyClickProps) => {\n useEffect(() => {\n const documentBody = document.body;\n\n if (!documentBody) {\n return;\n }\n\n const onEsc = (event: KeyboardEvent): void => {\n if (event.key === \"Escape\") {\n onClose();\n }\n };\n\n const bodyClick = (event: Event): void => {\n if (\n // @ts-ignore I'm not sure how to type this ref properly\n !doesRefContainEventTarget(ref, event) &&\n !disableCloseOnClickOutside\n ) {\n onClose();\n }\n };\n\n documentBody?.addEventListener(\"keydown\", onEsc, { capture: true });\n\n if (closeTargets) {\n closeTargets.forEach((targetElement) =>\n targetElement?.addEventListener(\"click\", bodyClick, { capture: true })\n );\n } else {\n documentBody.firstElementChild?.addEventListener(\"click\", bodyClick, {\n capture: true,\n });\n }\n\n return () => {\n documentBody?.removeEventListener(\"keydown\", onEsc, { capture: true });\n\n if (closeTargets) {\n closeTargets.forEach((targetElement) =>\n targetElement?.removeEventListener(\"click\", bodyClick, {\n capture: true,\n })\n );\n } else {\n documentBody.firstElementChild?.removeEventListener(\n \"click\",\n bodyClick,\n { capture: true }\n );\n }\n };\n }, [onClose, disableCloseOnClickOutside, closeTargets, ref]);\n};\n\nconst Drawer = ({\n id,\n offset,\n direction,\n children,\n disableCloseOnClickOutside,\n onClose,\n zIndex,\n closeTargets,\n width,\n focusLockExemptCheck,\n isOpen,\n ...rest\n}: TypeInnerDrawerProps) => {\n const ref = useRef(null);\n useCloseOnBodyClick({\n ref,\n disableCloseOnClickOutside,\n onClose,\n closeTargets,\n });\n\n const transition = useSlideTransition({\n isVisible: isOpen,\n width,\n direction,\n });\n\n return (\n <FocusLock\n key={id}\n autoFocus={true}\n returnFocus\n whiteList={\n focusLockExemptCheck ? (e) => !focusLockExemptCheck(e) : undefined\n }\n >\n {transition((style, isVisible) =>\n isVisible ? (\n <AnimatedDrawer\n ref={ref}\n style={{ ...style, zIndex }}\n width={width}\n offset={offset}\n direction={direction}\n data-qa-drawer={id}\n role=\"dialog\"\n {...rest}\n >\n {children}\n </AnimatedDrawer>\n ) : null\n )}\n </FocusLock>\n );\n};\n\nconst DrawerContainer = ({\n children,\n closeButtonLabel,\n direction = \"right\",\n disableCloseOnClickOutside = false,\n id,\n isOpen,\n offset = 0,\n onClose,\n zIndex = 7,\n closeTargets = [],\n width = 600,\n ...rest\n}: TypeDrawerProps) => {\n return (\n <Portal id={id}>\n <DrawerContext.Provider\n value={{\n onClose,\n closeButtonLabel,\n }}\n >\n <Drawer\n isOpen={isOpen}\n id={id}\n offset={offset}\n direction={direction}\n disableCloseOnClickOutside={disableCloseOnClickOutside}\n onClose={onClose}\n zIndex={zIndex}\n closeTargets={closeTargets}\n width={width}\n data-qa-drawer={id || \"\"}\n data-qa-drawer-isopen={isOpen}\n {...rest}\n >\n {children}\n </Drawer>\n </DrawerContext.Provider>\n </Portal>\n );\n};\n\nDrawerHeader.displayName = \"Drawer.Header\";\nDrawerContent.displayName = \"Drawer.Content\";\nDrawerCloseButton.displayName = \"Drawer.CloseButton\";\n\nDrawerContainer.Header = DrawerHeader;\nDrawerContainer.Content = DrawerContent;\nDrawerContainer.CloseButton = DrawerCloseButton;\n\nexport default DrawerContainer;\n","import type { TypeDrawerProps } from \"./DrawerTypes\";\nimport styled, { css } from \"styled-components\";\nimport { COMMON } from \"@sproutsocial/seeds-react-system-props\";\nimport type { TypeSystemCommonProps } from \"@sproutsocial/seeds-react-system-props\";\n\nimport Box from \"@sproutsocial/seeds-react-box\";\n\nexport const Content = styled(Box)`\n overflow-y: auto;\n`;\n\ninterface ContainerType\n extends Pick<TypeDrawerProps, \"offset\" | \"direction\">,\n TypeSystemCommonProps {\n width: number;\n}\n\nconst Container = styled.div<ContainerType>`\n display: flex;\n flex-direction: column;\n position: fixed;\n top: 0;\n height: 100%;\n width: ${(props) => props.width}px;\n background-color: ${(props) => props.theme.colors.container.background.base};\n box-shadow: ${(props) => props.theme.shadows.high};\n filter: blur(0);\n\n ${(props) => css`\n ${props.direction}: ${props.offset}px;\n `}\n\n ${COMMON}\n`;\nexport default Container;\n","import * as React from \"react\";\nimport type {\n TypeSystemCommonProps,\n TypeStyledComponentsCommonProps,\n} from \"@sproutsocial/seeds-react-system-props\";\nimport type { TypeBoxProps } from \"@sproutsocial/seeds-react-box\";\nimport type { TypeButtonProps } from \"@sproutsocial/seeds-react-button\";\n\ntype DrawerAnimationDirection = \"left\" | \"right\";\n\nexport interface TypeDrawerContext {\n /** Callback for close button */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onClose?: () => any;\n\n /** aria-label for drawer close button */\n closeButtonLabel?: string;\n}\n// TODO: Should the render prop be a React.FC?\nexport interface TypeDrawerCloseButtonProps\n extends Omit<TypeButtonProps, \"children\"> {\n /** An optional function that receives the context of the parent drawer as an argument. Can be used to customize the on-close behavior. */\n render?: React.FC<TypeDrawerContext>;\n children?: React.ReactNode;\n}\n\nexport interface TypeDrawerHeaderProps extends TypeBoxProps {\n title?: string;\n children?: React.ReactNode;\n\n /** An optional function that receives the context of the parent drawer as an argument. Can be used to customize the appearance of the header. */\n render?: React.FC<TypeDrawerContext>;\n}\n\nexport interface TypeInnerDrawerProps\n extends Omit<TypeDrawerProps, \"closeButtonLabel\"> {\n width: number;\n direction: DrawerAnimationDirection;\n}\n\ntype useBodyClicksProps = Pick<\n TypeDrawerProps,\n \"closeTargets\" | \"onClose\" | \"disableCloseOnClickOutside\"\n>;\n\nexport interface TypeUseCloseOnBodyClickProps\n extends Pick<\n TypeDrawerProps,\n \"closeTargets\" | \"onClose\" | \"disableCloseOnClickOutside\"\n > {\n ref?: React.RefObject<HTMLElement | null>;\n}\n\nexport interface TypeDrawerProps\n extends TypeStyledComponentsCommonProps,\n TypeSystemCommonProps,\n Omit<React.ComponentPropsWithoutRef<\"nav\">, \"color\"> {\n children: React.ReactNode;\n\n /** Label for the close button. Usually this should be \"Close\" */\n closeButtonLabel: string;\n\n /** Whether the drawer slides in from the left or right side of the screen */\n direction?: DrawerAnimationDirection;\n\n /** In some cases, you may not want the user to be able to click outside of the drawer to close it. You can disable that with this prop. */\n disableCloseOnClickOutside?: boolean;\n id: string;\n isOpen: boolean;\n offset?: number;\n onClose: () => void;\n zIndex?: number;\n closeTargets?: Array<Element>;\n width?: number;\n focusLockExemptCheck?: (element: HTMLElement) => boolean;\n}\n\nexport interface TypeDrawerContentProps extends TypeBoxProps {\n children?: React.ReactNode;\n}\n"],"mappings":"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","names":["import_seeds_react_box","styled","Box","Button","Icon","Box","Text","FocusLock","Portal","React"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/Drawer.tsx","../src/styles.ts","../src/DrawerTypes.ts"],"sourcesContent":["import Drawer, { DrawerContext } from \"./Drawer\";\n\nexport default Drawer;\nexport { Drawer, DrawerContext };\nexport * from \"./DrawerTypes\";\n","import * as React from \"react\";\nimport { useContext, useEffect, useRef } from \"react\";\nimport FocusLock from \"react-focus-lock\";\nimport { animated, useTransition } from \"@react-spring/web\";\nimport { MOTION_DURATION_MEDIUM } from \"@sproutsocial/seeds-motion/unitless\";\nimport Box from \"@sproutsocial/seeds-react-box\";\nimport Button from \"@sproutsocial/seeds-react-button\";\nimport Icon from \"@sproutsocial/seeds-react-icon\";\n// eslint-disable-next-line import/no-deprecated\nimport Text from \"@sproutsocial/seeds-react-text\";\nimport Portal from \"@sproutsocial/seeds-react-portal\";\nimport Container, { Content } from \"./styles\";\nimport type {\n TypeDrawerContext,\n TypeDrawerCloseButtonProps,\n TypeDrawerHeaderProps,\n TypeDrawerProps,\n TypeInnerDrawerProps,\n TypeDrawerContentProps,\n TypeUseCloseOnBodyClickProps,\n} from \"./DrawerTypes\";\n\nconst useSlideTransition = ({\n isVisible,\n width,\n direction,\n}: {\n isVisible: boolean;\n width: number;\n direction: \"left\" | \"right\";\n}) => {\n const offset = width * (direction === \"left\" ? -1 : 1);\n\n return useTransition(isVisible, {\n from: {\n opacity: 0,\n x: offset,\n },\n enter: {\n opacity: 1,\n x: 0,\n },\n leave: {\n opacity: 0,\n x: offset,\n },\n config: {\n duration: MOTION_DURATION_MEDIUM * 1000,\n },\n });\n};\n\nconst AnimatedDrawer = animated(Container);\n\nconst doesRefContainEventTarget = (\n ref: { current: { contains: (arg0: any) => any } },\n event: Event\n) => {\n return (\n ref.current &&\n event.target instanceof Node &&\n ref.current.contains(event.target)\n );\n};\n\nexport const DrawerContext = React.createContext<TypeDrawerContext>({\n isLocked: false,\n setIsLocked: () => {},\n});\n\nconst DrawerCloseButton = (props: TypeDrawerCloseButtonProps) => {\n const drawerContext = useContext(DrawerContext);\n\n if (props.render) {\n return props.render(drawerContext) ?? null;\n }\n\n return (\n <Button\n appearance=\"pill\"\n aria-label={drawerContext.closeButtonLabel}\n onClick={drawerContext.onClose}\n {...props}\n >\n {props.children || <Icon aria-hidden name=\"x-outline\" />}\n </Button>\n );\n};\n\nconst DrawerHeader = ({\n title = \"\",\n id = undefined,\n children,\n render,\n ...rest\n}: TypeDrawerHeaderProps) => {\n const drawerContext = useContext(DrawerContext);\n\n if (render) {\n return render(drawerContext);\n }\n\n return (\n <Box\n display=\"flex\"\n flex=\"0 0 auto\"\n justifyContent=\"space-between\"\n alignItems=\"center\"\n pt={400}\n px={450}\n {...rest}\n >\n {children || (\n <React.Fragment>\n <Text\n as=\"h2\"\n fontSize={400}\n fontWeight=\"semibold\"\n color=\"text.headline\"\n id={id}\n >\n {title}\n </Text>\n <DrawerCloseButton />\n </React.Fragment>\n )}\n </Box>\n );\n};\n\nconst DrawerContent = ({ children, ...rest }: TypeDrawerContentProps) => (\n <Content height=\"100%\" p={450} color=\"text.body\" {...rest}>\n {children}\n </Content>\n);\n\nconst useCloseOnBodyClick = ({\n ref,\n disableCloseOnClickOutside,\n onClose,\n closeTargets,\n}: TypeUseCloseOnBodyClickProps) => {\n const { isLocked } = useContext(DrawerContext);\n\n useEffect(() => {\n const documentBody = document.body;\n\n if (!documentBody) {\n return;\n }\n\n const onEsc = (event: KeyboardEvent): void => {\n if (!isLocked && event.key === \"Escape\") {\n onClose();\n }\n };\n\n const bodyClick = (event: Event): void => {\n if (\n // @ts-ignore I'm not sure how to type this ref properly\n !doesRefContainEventTarget(ref, event) &&\n !disableCloseOnClickOutside\n ) {\n onClose();\n }\n };\n\n documentBody?.addEventListener(\"keydown\", onEsc, { capture: true });\n\n if (closeTargets) {\n closeTargets.forEach((targetElement) =>\n targetElement?.addEventListener(\"click\", bodyClick, { capture: true })\n );\n } else {\n documentBody.firstElementChild?.addEventListener(\"click\", bodyClick, {\n capture: true,\n });\n }\n\n return () => {\n documentBody?.removeEventListener(\"keydown\", onEsc, { capture: true });\n\n if (closeTargets) {\n closeTargets.forEach((targetElement) =>\n targetElement?.removeEventListener(\"click\", bodyClick, {\n capture: true,\n })\n );\n } else {\n documentBody.firstElementChild?.removeEventListener(\n \"click\",\n bodyClick,\n { capture: true }\n );\n }\n };\n }, [onClose, disableCloseOnClickOutside, closeTargets, ref, isLocked]);\n};\n\nconst Drawer = ({\n id,\n offset,\n direction,\n children,\n disableCloseOnClickOutside,\n onClose,\n zIndex,\n closeTargets,\n width,\n focusLockExemptCheck,\n isOpen,\n ...rest\n}: TypeInnerDrawerProps) => {\n const ref = useRef(null);\n useCloseOnBodyClick({\n ref,\n disableCloseOnClickOutside,\n onClose,\n closeTargets,\n });\n\n const transition = useSlideTransition({\n isVisible: isOpen,\n width,\n direction,\n });\n\n return (\n <FocusLock\n key={id}\n autoFocus={true}\n returnFocus\n whiteList={\n focusLockExemptCheck ? (e) => !focusLockExemptCheck(e) : undefined\n }\n >\n {transition((style, isVisible) =>\n isVisible ? (\n <AnimatedDrawer\n ref={ref}\n style={{ ...style, zIndex }}\n width={width}\n offset={offset}\n direction={direction}\n data-qa-drawer={id}\n role=\"dialog\"\n {...rest}\n >\n {children}\n </AnimatedDrawer>\n ) : null\n )}\n </FocusLock>\n );\n};\n\nconst DrawerContainer = ({\n children,\n closeButtonLabel,\n direction = \"right\",\n disableCloseOnClickOutside = false,\n id,\n isOpen,\n offset = 0,\n onClose,\n zIndex = 7,\n closeTargets = [],\n width = 600,\n ...rest\n}: TypeDrawerProps) => {\n const [isLocked, setIsLocked] = React.useState(false);\n return (\n <Portal id={id}>\n <DrawerContext.Provider\n value={{\n onClose,\n closeButtonLabel,\n isLocked,\n setIsLocked,\n }}\n >\n <Drawer\n isOpen={isOpen}\n id={id}\n offset={offset}\n direction={direction}\n disableCloseOnClickOutside={disableCloseOnClickOutside}\n onClose={onClose}\n zIndex={zIndex}\n closeTargets={closeTargets}\n width={width}\n data-qa-drawer={id || \"\"}\n data-qa-drawer-isopen={isOpen}\n {...rest}\n >\n {children}\n </Drawer>\n </DrawerContext.Provider>\n </Portal>\n );\n};\n\nDrawerHeader.displayName = \"Drawer.Header\";\nDrawerContent.displayName = \"Drawer.Content\";\nDrawerCloseButton.displayName = \"Drawer.CloseButton\";\n\nDrawerContainer.Header = DrawerHeader;\nDrawerContainer.Content = DrawerContent;\nDrawerContainer.CloseButton = DrawerCloseButton;\n\nexport default DrawerContainer;\n","import type { TypeDrawerProps } from \"./DrawerTypes\";\nimport styled, { css } from \"styled-components\";\nimport { COMMON } from \"@sproutsocial/seeds-react-system-props\";\nimport type { TypeSystemCommonProps } from \"@sproutsocial/seeds-react-system-props\";\n\nimport Box from \"@sproutsocial/seeds-react-box\";\n\nexport const Content = styled(Box)`\n overflow-y: auto;\n`;\n\ninterface ContainerType\n extends Pick<TypeDrawerProps, \"offset\" | \"direction\">,\n TypeSystemCommonProps {\n width: number;\n}\n\nconst Container = styled.div<ContainerType>`\n display: flex;\n flex-direction: column;\n position: fixed;\n top: 0;\n height: 100%;\n width: ${(props) => props.width}px;\n background-color: ${(props) => props.theme.colors.container.background.base};\n box-shadow: ${(props) => props.theme.shadows.high};\n filter: blur(0);\n\n ${(props) => css`\n ${props.direction}: ${props.offset}px;\n `}\n\n ${COMMON}\n`;\nexport default Container;\n","import * as React from \"react\";\nimport type {\n TypeSystemCommonProps,\n TypeStyledComponentsCommonProps,\n} from \"@sproutsocial/seeds-react-system-props\";\nimport type { TypeBoxProps } from \"@sproutsocial/seeds-react-box\";\nimport type { TypeButtonProps } from \"@sproutsocial/seeds-react-button\";\n\ntype DrawerAnimationDirection = \"left\" | \"right\";\n\nexport interface TypeDrawerContext {\n /** Callback for close button */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onClose?: () => any;\n\n /** aria-label for drawer close button */\n closeButtonLabel?: string;\n /**\n * isLocked and setIsLocked are used when a Drawer contains other components (like Menu) that also\n * listen for Escape key events. By locking the Drawer, we can prevent conflicts\n * where multiple components try to handle the same keydown event.\n */\n isLocked: boolean;\n setIsLocked: (locked: boolean) => void;\n}\n// TODO: Should the render prop be a React.FC?\nexport interface TypeDrawerCloseButtonProps\n extends Omit<TypeButtonProps, \"children\"> {\n /** An optional function that receives the context of the parent drawer as an argument. Can be used to customize the on-close behavior. */\n render?: React.FC<TypeDrawerContext>;\n children?: React.ReactNode;\n}\n\nexport interface TypeDrawerHeaderProps extends TypeBoxProps {\n title?: string;\n children?: React.ReactNode;\n\n /** An optional function that receives the context of the parent drawer as an argument. Can be used to customize the appearance of the header. */\n render?: React.FC<TypeDrawerContext>;\n}\n\nexport interface TypeInnerDrawerProps\n extends Omit<TypeDrawerProps, \"closeButtonLabel\"> {\n width: number;\n direction: DrawerAnimationDirection;\n}\n\ntype useBodyClicksProps = Pick<\n TypeDrawerProps,\n \"closeTargets\" | \"onClose\" | \"disableCloseOnClickOutside\"\n>;\n\nexport interface TypeUseCloseOnBodyClickProps\n extends Pick<\n TypeDrawerProps,\n \"closeTargets\" | \"onClose\" | \"disableCloseOnClickOutside\"\n > {\n ref?: React.RefObject<HTMLElement | null>;\n}\n\nexport interface TypeDrawerProps\n extends TypeStyledComponentsCommonProps,\n TypeSystemCommonProps,\n Omit<React.ComponentPropsWithoutRef<\"nav\">, \"color\"> {\n children: React.ReactNode;\n\n /** Label for the close button. Usually this should be \"Close\" */\n closeButtonLabel: string;\n\n /** Whether the drawer slides in from the left or right side of the screen */\n direction?: DrawerAnimationDirection;\n\n /** In some cases, you may not want the user to be able to click outside of the drawer to close it. You can disable that with this prop. */\n disableCloseOnClickOutside?: boolean;\n id: string;\n isOpen: boolean;\n offset?: number;\n onClose: () => void;\n zIndex?: number;\n closeTargets?: Array<Element>;\n width?: number;\n focusLockExemptCheck?: (element: HTMLElement) => boolean;\n}\n\nexport interface TypeDrawerContentProps extends TypeBoxProps {\n children?: React.ReactNode;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,YAAuB;AACvB,mBAA8C;AAC9C,8BAAsB;AACtB,iBAAwC;AACxC,sBAAuC;AACvC,IAAAA,0BAAgB;AAChB,gCAAmB;AACnB,8BAAiB;AAEjB,8BAAiB;AACjB,gCAAmB;;;ACTnB,+BAA4B;AAC5B,sCAAuB;AAGvB,6BAAgB;AAET,IAAM,cAAU,yBAAAC,SAAO,uBAAAC,OAAG;AAAA;AAAA;AAUjC,IAAM,YAAY,yBAAAD,QAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAMd,CAAC,UAAU,MAAM,KAAK;AAAA,sBACX,CAAC,UAAU,MAAM,MAAM,OAAO,UAAU,WAAW,IAAI;AAAA,gBAC7D,CAAC,UAAU,MAAM,MAAM,QAAQ,IAAI;AAAA;AAAA;AAAA,IAG/C,CAAC,UAAU;AAAA,MACT,MAAM,SAAS,KAAK,MAAM,MAAM;AAAA,GACnC;AAAA;AAAA,IAEC,sCAAM;AAAA;AAEV,IAAO,iBAAQ;;;ADkDU;AA9DzB,IAAM,qBAAqB,CAAC;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AACF,MAIM;AACJ,QAAM,SAAS,SAAS,cAAc,SAAS,KAAK;AAEpD,aAAO,0BAAc,WAAW;AAAA,IAC9B,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,GAAG;AAAA,IACL;AAAA,IACA,OAAO;AAAA,MACL,SAAS;AAAA,MACT,GAAG;AAAA,IACL;AAAA,IACA,OAAO;AAAA,MACL,SAAS;AAAA,MACT,GAAG;AAAA,IACL;AAAA,IACA,QAAQ;AAAA,MACN,UAAU,yCAAyB;AAAA,IACrC;AAAA,EACF,CAAC;AACH;AAEA,IAAM,qBAAiB,qBAAS,cAAS;AAEzC,IAAM,4BAA4B,CAChC,KACA,UACG;AACH,SACE,IAAI,WACJ,MAAM,kBAAkB,QACxB,IAAI,QAAQ,SAAS,MAAM,MAAM;AAErC;AAEO,IAAM,gBAAsB,oBAAiC;AAAA,EAClE,UAAU;AAAA,EACV,aAAa,MAAM;AAAA,EAAC;AACtB,CAAC;AAED,IAAM,oBAAoB,CAAC,UAAsC;AAC/D,QAAM,oBAAgB,yBAAW,aAAa;AAE9C,MAAI,MAAM,QAAQ;AAChB,WAAO,MAAM,OAAO,aAAa,KAAK;AAAA,EACxC;AAEA,SACE;AAAA,IAAC,0BAAAE;AAAA,IAAA;AAAA,MACC,YAAW;AAAA,MACX,cAAY,cAAc;AAAA,MAC1B,SAAS,cAAc;AAAA,MACtB,GAAG;AAAA,MAEH,gBAAM,YAAY,4CAAC,wBAAAC,SAAA,EAAK,eAAW,MAAC,MAAK,aAAY;AAAA;AAAA,EACxD;AAEJ;AAEA,IAAM,eAAe,CAAC;AAAA,EACpB,QAAQ;AAAA,EACR,KAAK;AAAA,EACL;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAA6B;AAC3B,QAAM,oBAAgB,yBAAW,aAAa;AAE9C,MAAI,QAAQ;AACV,WAAO,OAAO,aAAa;AAAA,EAC7B;AAEA,SACE;AAAA,IAAC,wBAAAC;AAAA,IAAA;AAAA,MACC,SAAQ;AAAA,MACR,MAAK;AAAA,MACL,gBAAe;AAAA,MACf,YAAW;AAAA,MACX,IAAI;AAAA,MACJ,IAAI;AAAA,MACH,GAAG;AAAA,MAEH,sBACC,6CAAO,gBAAN,EACC;AAAA;AAAA,UAAC,wBAAAC;AAAA,UAAA;AAAA,YACC,IAAG;AAAA,YACH,UAAU;AAAA,YACV,YAAW;AAAA,YACX,OAAM;AAAA,YACN;AAAA,YAEC;AAAA;AAAA,QACH;AAAA,QACA,4CAAC,qBAAkB;AAAA,SACrB;AAAA;AAAA,EAEJ;AAEJ;AAEA,IAAM,gBAAgB,CAAC,EAAE,UAAU,GAAG,KAAK,MACzC,4CAAC,WAAQ,QAAO,QAAO,GAAG,KAAK,OAAM,aAAa,GAAG,MAClD,UACH;AAGF,IAAM,sBAAsB,CAAC;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAoC;AAClC,QAAM,EAAE,SAAS,QAAI,yBAAW,aAAa;AAE7C,8BAAU,MAAM;AACd,UAAM,eAAe,SAAS;AAE9B,QAAI,CAAC,cAAc;AACjB;AAAA,IACF;AAEA,UAAM,QAAQ,CAAC,UAA+B;AAC5C,UAAI,CAAC,YAAY,MAAM,QAAQ,UAAU;AACvC,gBAAQ;AAAA,MACV;AAAA,IACF;AAEA,UAAM,YAAY,CAAC,UAAuB;AACxC;AAAA;AAAA,QAEE,CAAC,0BAA0B,KAAK,KAAK,KACrC,CAAC;AAAA,QACD;AACA,gBAAQ;AAAA,MACV;AAAA,IACF;AAEA,kBAAc,iBAAiB,WAAW,OAAO,EAAE,SAAS,KAAK,CAAC;AAElE,QAAI,cAAc;AAChB,mBAAa;AAAA,QAAQ,CAAC,kBACpB,eAAe,iBAAiB,SAAS,WAAW,EAAE,SAAS,KAAK,CAAC;AAAA,MACvE;AAAA,IACF,OAAO;AACL,mBAAa,mBAAmB,iBAAiB,SAAS,WAAW;AAAA,QACnE,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAEA,WAAO,MAAM;AACX,oBAAc,oBAAoB,WAAW,OAAO,EAAE,SAAS,KAAK,CAAC;AAErE,UAAI,cAAc;AAChB,qBAAa;AAAA,UAAQ,CAAC,kBACpB,eAAe,oBAAoB,SAAS,WAAW;AAAA,YACrD,SAAS;AAAA,UACX,CAAC;AAAA,QACH;AAAA,MACF,OAAO;AACL,qBAAa,mBAAmB;AAAA,UAC9B;AAAA,UACA;AAAA,UACA,EAAE,SAAS,KAAK;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAAA,EACF,GAAG,CAAC,SAAS,4BAA4B,cAAc,KAAK,QAAQ,CAAC;AACvE;AAEA,IAAM,SAAS,CAAC;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAA4B;AAC1B,QAAM,UAAM,qBAAO,IAAI;AACvB,sBAAoB;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,aAAa,mBAAmB;AAAA,IACpC,WAAW;AAAA,IACX;AAAA,IACA;AAAA,EACF,CAAC;AAED,SACE;AAAA,IAAC,wBAAAC;AAAA,IAAA;AAAA,MAEC,WAAW;AAAA,MACX,aAAW;AAAA,MACX,WACE,uBAAuB,CAAC,MAAM,CAAC,qBAAqB,CAAC,IAAI;AAAA,MAG1D;AAAA,QAAW,CAAC,OAAO,cAClB,YACE;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA,OAAO,EAAE,GAAG,OAAO,OAAO;AAAA,YAC1B;AAAA,YACA;AAAA,YACA;AAAA,YACA,kBAAgB;AAAA,YAChB,MAAK;AAAA,YACJ,GAAG;AAAA,YAEH;AAAA;AAAA,QACH,IACE;AAAA,MACN;AAAA;AAAA,IAtBK;AAAA,EAuBP;AAEJ;AAEA,IAAM,kBAAkB,CAAC;AAAA,EACvB;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,6BAA6B;AAAA,EAC7B;AAAA,EACA;AAAA,EACA,SAAS;AAAA,EACT;AAAA,EACA,SAAS;AAAA,EACT,eAAe,CAAC;AAAA,EAChB,QAAQ;AAAA,EACR,GAAG;AACL,MAAuB;AACrB,QAAM,CAAC,UAAU,WAAW,IAAU,eAAS,KAAK;AACpD,SACE,4CAAC,0BAAAC,SAAA,EAAO,IACN;AAAA,IAAC,cAAc;AAAA,IAAd;AAAA,MACC,OAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MAEA;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,kBAAgB,MAAM;AAAA,UACtB,yBAAuB;AAAA,UACtB,GAAG;AAAA,UAEH;AAAA;AAAA,MACH;AAAA;AAAA,EACF,GACF;AAEJ;AAEA,aAAa,cAAc;AAC3B,cAAc,cAAc;AAC5B,kBAAkB,cAAc;AAEhC,gBAAgB,SAAS;AACzB,gBAAgB,UAAU;AAC1B,gBAAgB,cAAc;AAE9B,IAAO,iBAAQ;;;AEtTf,IAAAC,SAAuB;;;AHEvB,IAAO,gBAAQ;","names":["import_seeds_react_box","styled","Box","Button","Icon","Box","Text","FocusLock","Portal","React"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sproutsocial/seeds-react-drawer",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Seeds React Drawer",
5
5
  "author": "Sprout Social, Inc.",
6
6
  "license": "MIT",
@@ -18,11 +18,11 @@
18
18
  "test:watch": "jest --watch --coverage=false"
19
19
  },
20
20
  "dependencies": {
21
- "@sproutsocial/seeds-react-theme": "^3.0.1",
21
+ "@sproutsocial/seeds-react-theme": "^3.1.0",
22
22
  "@sproutsocial/seeds-react-system-props": "^3.0.1",
23
- "@sproutsocial/seeds-react-box": "^1.1.3",
24
- "@sproutsocial/seeds-react-button": "^1.2.2",
25
- "@sproutsocial/seeds-react-icon": "^1.1.3",
23
+ "@sproutsocial/seeds-react-box": "^1.1.4",
24
+ "@sproutsocial/seeds-react-button": "^1.2.3",
25
+ "@sproutsocial/seeds-react-icon": "^1.1.4",
26
26
  "@sproutsocial/seeds-react-text": "^1.3.2",
27
27
  "@sproutsocial/seeds-react-portal": "^1.1.4",
28
28
  "@sproutsocial/seeds-motion": "^1.8.0",
@@ -1,12 +1,12 @@
1
1
  /* eslint-disable @typescript-eslint/no-empty-function */
2
- import React, { useState } from "react";
2
+ import React, { useContext, useEffect, useState } from "react";
3
3
  import type { Meta, StoryObj } from "@storybook/react";
4
4
  import { Box } from "@sproutsocial/seeds-react-box";
5
5
  import { Button } from "@sproutsocial/seeds-react-button";
6
6
  import { Icon } from "@sproutsocial/seeds-react-icon";
7
7
  // import { Tabs } from "@sproutsocial/seeds-react-tabs";
8
8
  import { Text } from "@sproutsocial/seeds-react-text";
9
- import Drawer from "./Drawer";
9
+ import Drawer, { DrawerContext } from "./Drawer";
10
10
 
11
11
  // @ts-ignore We'll come back to this later
12
12
  const StatefulDrawer = ({ isOpen = true, onClose, children, ...rest }) => {
@@ -376,3 +376,62 @@ export const fromLeft: Story = {
376
376
  // @ts-ignore We'll come back to this later
377
377
  render: (args) => <DrawerComponent {...args} />,
378
378
  };
379
+
380
+ export const LockableDrawer: Story = {
381
+ name: "Lockable Drawer",
382
+ render: (args) => (
383
+ <StatefulDrawer
384
+ direction={args.direction}
385
+ offset={args.offset}
386
+ isOpen={args.isOpen}
387
+ width={args.width}
388
+ onClose={() => {}}
389
+ id="drawer"
390
+ closeButtonLabel="close drawer"
391
+ aria-labeledby="drawer-1-header"
392
+ aria-describedby="drawer-1-content"
393
+ >
394
+ <Drawer.Header title="Drawer" id="drawer-1-header" />
395
+ <Drawer.Content id="drawer-1-content">
396
+ <DrawerContentWithButton />
397
+ </Drawer.Content>
398
+ </StatefulDrawer>
399
+ ),
400
+ };
401
+
402
+ const DrawerContentWithButton = () => {
403
+ const [isActive, setIsActive] = useState(false);
404
+ const { setIsLocked } = useContext(DrawerContext);
405
+
406
+ const onClick = () => {
407
+ setIsActive(true);
408
+ setIsLocked(true);
409
+ };
410
+
411
+ useEffect(() => {
412
+ const documentBody = document.body;
413
+
414
+ if (isActive && documentBody) {
415
+ const onEsc = (e: KeyboardEvent): void => {
416
+ if (["Escape", "Esc"].includes(e.key)) {
417
+ e.stopPropagation();
418
+ setIsActive(false);
419
+ setIsLocked(false);
420
+ }
421
+ };
422
+
423
+ documentBody.addEventListener("keydown", onEsc, { capture: true });
424
+ return () => {
425
+ documentBody.removeEventListener("keydown", onEsc, { capture: true });
426
+ };
427
+ }
428
+ }, [isActive, setIsActive]);
429
+
430
+ return (
431
+ <Box p={500}>
432
+ <Button appearance="primary" onClick={onClick}>
433
+ Click to lock Escape key ({isActive ? "Active" : "Inactive"})
434
+ </Button>
435
+ </Box>
436
+ );
437
+ };
package/src/Drawer.tsx CHANGED
@@ -63,25 +63,23 @@ const doesRefContainEventTarget = (
63
63
  );
64
64
  };
65
65
 
66
- const DrawerContext = React.createContext<TypeDrawerContext>({});
66
+ export const DrawerContext = React.createContext<TypeDrawerContext>({
67
+ isLocked: false,
68
+ setIsLocked: () => {},
69
+ });
67
70
 
68
71
  const DrawerCloseButton = (props: TypeDrawerCloseButtonProps) => {
69
- const { onClose, closeButtonLabel } = useContext(DrawerContext);
72
+ const drawerContext = useContext(DrawerContext);
70
73
 
71
74
  if (props.render) {
72
- return (
73
- props.render({
74
- onClose,
75
- closeButtonLabel,
76
- }) ?? null
77
- );
75
+ return props.render(drawerContext) ?? null;
78
76
  }
79
77
 
80
78
  return (
81
79
  <Button
82
80
  appearance="pill"
83
- aria-label={closeButtonLabel}
84
- onClick={onClose}
81
+ aria-label={drawerContext.closeButtonLabel}
82
+ onClick={drawerContext.onClose}
85
83
  {...props}
86
84
  >
87
85
  {props.children || <Icon aria-hidden name="x-outline" />}
@@ -142,6 +140,8 @@ const useCloseOnBodyClick = ({
142
140
  onClose,
143
141
  closeTargets,
144
142
  }: TypeUseCloseOnBodyClickProps) => {
143
+ const { isLocked } = useContext(DrawerContext);
144
+
145
145
  useEffect(() => {
146
146
  const documentBody = document.body;
147
147
 
@@ -150,7 +150,7 @@ const useCloseOnBodyClick = ({
150
150
  }
151
151
 
152
152
  const onEsc = (event: KeyboardEvent): void => {
153
- if (event.key === "Escape") {
153
+ if (!isLocked && event.key === "Escape") {
154
154
  onClose();
155
155
  }
156
156
  };
@@ -194,7 +194,7 @@ const useCloseOnBodyClick = ({
194
194
  );
195
195
  }
196
196
  };
197
- }, [onClose, disableCloseOnClickOutside, closeTargets, ref]);
197
+ }, [onClose, disableCloseOnClickOutside, closeTargets, ref, isLocked]);
198
198
  };
199
199
 
200
200
  const Drawer = ({
@@ -268,12 +268,15 @@ const DrawerContainer = ({
268
268
  width = 600,
269
269
  ...rest
270
270
  }: TypeDrawerProps) => {
271
+ const [isLocked, setIsLocked] = React.useState(false);
271
272
  return (
272
273
  <Portal id={id}>
273
274
  <DrawerContext.Provider
274
275
  value={{
275
276
  onClose,
276
277
  closeButtonLabel,
278
+ isLocked,
279
+ setIsLocked,
277
280
  }}
278
281
  >
279
282
  <Drawer
@@ -15,6 +15,13 @@ export interface TypeDrawerContext {
15
15
 
16
16
  /** aria-label for drawer close button */
17
17
  closeButtonLabel?: string;
18
+ /**
19
+ * isLocked and setIsLocked are used when a Drawer contains other components (like Menu) that also
20
+ * listen for Escape key events. By locking the Drawer, we can prevent conflicts
21
+ * where multiple components try to handle the same keydown event.
22
+ */
23
+ isLocked: boolean;
24
+ setIsLocked: (locked: boolean) => void;
18
25
  }
19
26
  // TODO: Should the render prop be a React.FC?
20
27
  export interface TypeDrawerCloseButtonProps
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable testing-library/prefer-screen-queries */
2
2
 
3
- import React, { useState } from "react";
3
+ import React, { useState, useContext } from "react";
4
4
  import {
5
5
  render as testRender,
6
6
  waitFor,
@@ -8,7 +8,7 @@ import {
8
8
  act,
9
9
  } from "@sproutsocial/seeds-react-testing-library";
10
10
  import type { TypeDrawerProps } from "../DrawerTypes";
11
- import Drawer from "../Drawer";
11
+ import Drawer, { DrawerContext } from "../Drawer";
12
12
 
13
13
  const StatefulDrawer = ({
14
14
  isOpen,
@@ -207,3 +207,108 @@ describe("Drawer", () => {
207
207
  expect(screen.getByRole("dialog")).toHaveStyle("width: 400px");
208
208
  });
209
209
  });
210
+
211
+ const LockButton = () => {
212
+ const [isActive, setIsActive] = useState(false);
213
+ const { setIsLocked } = useContext(DrawerContext);
214
+
215
+ const onClick = () => {
216
+ setIsActive(true);
217
+ setIsLocked(true);
218
+ };
219
+
220
+ React.useEffect(() => {
221
+ const documentBody = document.body;
222
+
223
+ if (isActive && documentBody) {
224
+ const onEsc = (e: KeyboardEvent): void => {
225
+ if (["Escape", "Esc"].includes(e.key)) {
226
+ e.stopPropagation();
227
+ setIsActive(false);
228
+ setIsLocked(false);
229
+ }
230
+ };
231
+
232
+ documentBody.addEventListener("keydown", onEsc, { capture: true });
233
+ return () => {
234
+ documentBody.removeEventListener("keydown", onEsc, { capture: true });
235
+ };
236
+ }
237
+ }, [isActive, setIsActive]);
238
+
239
+ return <button onClick={onClick}>Click to lock Escape key</button>;
240
+ };
241
+
242
+ const ChildrenWithLockButton = () => (
243
+ <React.Fragment>
244
+ <Drawer.Header title="Drawer Header" id="drawer-1-header" />
245
+ <Drawer.Content>
246
+ <p>Drawer Content</p>
247
+ <LockButton />
248
+ </Drawer.Content>
249
+ </React.Fragment>
250
+ );
251
+
252
+ describe("Drawer with Lock", () => {
253
+ describe("Unlocked", () => {
254
+ it("should close drawer on esc key", async () => {
255
+ const { user } = render({
256
+ isOpen: true,
257
+ children: <ChildrenWithLockButton />,
258
+ });
259
+ expect(screen.getByText(/drawer content/i)).toBeInTheDocument();
260
+ await act(async () => {
261
+ await user.keyboard("{Escape}");
262
+ });
263
+ await waitFor(() => {
264
+ expect(screen.queryByText(/drawer content/i)).not.toBeInTheDocument();
265
+ });
266
+ });
267
+ });
268
+
269
+ describe("Locked", () => {
270
+ it("should not close drawer on first esc key", async () => {
271
+ const { user } = render({
272
+ isOpen: true,
273
+ children: <ChildrenWithLockButton />,
274
+ });
275
+ const lockButton = screen.getByText("Click to lock Escape key");
276
+
277
+ await act(async () => {
278
+ await user.click(lockButton);
279
+ });
280
+ await act(async () => {
281
+ await user.keyboard("{Escape}");
282
+ });
283
+ await waitFor(() => {
284
+ expect(screen.queryByText(/drawer content/i)).toBeInTheDocument();
285
+ });
286
+ // lock is reset, handle escape as usual
287
+ await act(async () => {
288
+ await user.keyboard("{Escape}");
289
+ });
290
+ await waitFor(() => {
291
+ expect(screen.queryByText(/drawer content/i)).not.toBeInTheDocument();
292
+ });
293
+ });
294
+ it("should close drawer on outside click", async () => {
295
+ const { user, baseElement } = render({
296
+ isOpen: true,
297
+ children: <ChildrenWithLockButton />,
298
+ });
299
+ const lockButton = screen.getByText("Click to lock Escape key");
300
+
301
+ await act(async () => {
302
+ await user.click(lockButton);
303
+ });
304
+
305
+ expect(screen.getByText(/drawer content/i)).toBeInTheDocument();
306
+ await act(async () => {
307
+ await user.click(baseElement.querySelector("#main-content") as Element);
308
+ });
309
+ await waitFor(() => {
310
+ expect(screen.queryByText(/drawer content/i)).not.toBeInTheDocument();
311
+ });
312
+ });
313
+ });
314
+ });
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
- import Drawer from "./Drawer";
1
+ import Drawer, { DrawerContext } from "./Drawer";
2
2
 
3
3
  export default Drawer;
4
- export { Drawer };
4
+ export { Drawer, DrawerContext };
5
5
  export * from "./DrawerTypes";