@flipdish/portal-library 3.0.0 → 3.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.
- package/dist/components/molecules/Modal/index.cjs.js +2 -0
- package/dist/components/molecules/Modal/index.cjs.js.map +1 -0
- package/dist/components/molecules/Modal/index.d.ts +49 -0
- package/dist/components/molecules/Modal/index.js +2 -0
- package/dist/components/molecules/Modal/index.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var e=require("react/jsx-runtime"),t=require("react"),a=require("@mui/material/Box"),i=require("@mui/material/Button"),r=require("@mui/material/Modal"),o=require("@mui/material/Stack"),n=require("@mui/material/styles"),l=require("@mui/material/Typography"),s=require("@mui/material/useMediaQuery"),d=require("../../../themes/tokens/breakpoints/breakpoints.cjs.js"),c=require("../../atoms/IconContainer/index.cjs.js"),u=require("../../Spacer/index.cjs.js");const m={iconContainer:{default:()=>"neutral",destructive:()=>"destructive"},button:{default:()=>"primary",destructive:()=>"error"}},p=e=>m.iconContainer[e](),x=e=>m.button[e](),h=n.styled(a,{shouldForwardProp:e=>!["tone","size"].includes(e)})((({theme:e,size:t})=>({position:"absolute",backgroundColor:e.palette.semantic.background["background-overlay"],border:"none",borderRadius:e.radius["radius-16"],padding:e.spacing(4),boxShadow:"0px 1px 6px 0px rgba(0, 0, 0, 0.10), 0px 8px 16px 0px rgba(0, 0, 0, 0.15)",outline:"none",maxHeight:"90vh",overflowY:"auto",top:"50%",left:"50%",transform:"translate(-50%, -50%)",maxWidth:"large"===t?"700px":"500px",width:`calc(100% - ${e.spacing(4)})`,[e.breakpoints.down(d.breakpointValues.tablet)]:{..."small"===t&&{maxWidth:`calc(100% - ${e.spacing(4)})`,width:`calc(100% - ${e.spacing(4)})`,top:"auto",bottom:e.spacing(2),left:e.spacing(2),right:e.spacing(2),transform:"none",maxHeight:"70vh",overflowY:"auto",display:"flex",flexDirection:"column"},..."large"===t&&{height:`calc(100% - ${e.spacing(4)})`,maxHeight:`calc(100% - ${e.spacing(4)})`,overflowY:"auto",display:"flex",flexDirection:"column"}}}))),g=n.styled("img")((({theme:e})=>({width:"100%",height:"auto",borderRadius:e.radius["radius-8"]}))),b=n.styled(o)({justifyContent:"flex-start",alignItems:"center"}),f=n.styled(a)((({theme:e})=>({[e.breakpoints.down(d.breakpointValues.tablet)]:{flexGrow:1,display:"flex",flexDirection:"column",minHeight:"min-content",paddingBottom:e.spacing(3)}}))),j=t.memo((({open:t,onClose:a,tone:o="default",size:m="large",title:j,description:y,property:k,primaryButtonLabel:v,secondaryButtonLabel:w,onPrimaryButtonClick:q,onSecondaryButtonClick:z,className:C,"data-testid":$,children:B})=>{const S=n.useTheme(),W=s(S.breakpoints.down(d.breakpointValues.tablet)),F="string"==typeof k,H=!!v&&!!q,M=!!w&&!!z,D=H||M;return e.jsx(r,{"aria-describedby":`modal-description-${$??"default"}`,"aria-labelledby":`modal-title-${$??"default"}`,className:C,"data-testid":$,open:t,onClose:a,children:e.jsxs(h,{size:m,tone:o,children:[e.jsxs(f,{children:[k&&e.jsxs(e.Fragment,{children:[F?e.jsx(g,{alt:j??"Modal image",src:k}):e.jsx(c,{icon:k,style:"filled",tone:p(o)}),e.jsx(u,{size:16,variant:"horizontal"})]}),j&&e.jsx(l,{component:"h2",id:`modal-title-${$??"default"}`,variant:"h3Strong",children:j}),j&&y&&e.jsx(u,{size:8,variant:"horizontal"}),y&&e.jsx(l,{color:S.palette.semantic.text["text-weak"],id:`modal-description-${$??"default"}`,variant:"b1Weak",children:y}),B&&e.jsxs(e.Fragment,{children:[e.jsx(u,{size:24,variant:"horizontal"}),B]})]}),D&&e.jsxs(e.Fragment,{children:[e.jsx(u,{size:24,variant:"horizontal"}),e.jsxs(b,{direction:W?"column":"row",spacing:2,children:[H&&e.jsx(i,{color:x(o),fullWidth:W,variant:"contained",onClick:q,children:v}),M&&e.jsx(i,{color:x(o),fullWidth:W,variant:"outlined",onClick:z,children:w})]})]})]})})}));j.displayName="Modal",module.exports=j;
|
|
2
|
+
//# sourceMappingURL=index.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../../../../src/components/molecules/Modal/index.tsx"],"sourcesContent":["/* eslint-disable @typescript-eslint/explicit-function-return-type */\nimport { memo } from 'react';\n\nimport Box from '@mui/material/Box';\nimport Button, { type ButtonProps } from '@mui/material/Button';\nimport MuiModal from '@mui/material/Modal';\nimport Stack from '@mui/material/Stack';\nimport { styled, useTheme } from '@mui/material/styles';\nimport Typography from '@mui/material/Typography';\nimport useMediaQuery from '@mui/material/useMediaQuery';\n\nimport { breakpointValues } from '../../../themes/tokens/breakpoints/breakpoints';\nimport IconContainer, { type IconContainerTones } from '../../atoms/IconContainer';\nimport Spacer from '../../Spacer';\n\n/** Visual tone of the modal */\nexport type ModalTones = 'default' | 'destructive';\n\n/** Size variants for the modal */\nexport type ModalSizes = 'large' | 'small';\n\n/** Props for the Modal component */\nexport interface ModalProps {\n /** Whether the modal is open */\n open: boolean;\n /** Callback function when the modal is closed */\n onClose: () => void;\n /** Visual tone of the modal */\n tone?: ModalTones;\n /** Size variant of the modal */\n size?: ModalSizes;\n /** Main heading text of the modal */\n title?: string;\n /** Detailed description text of the modal */\n description?: string;\n /** Property element to display - can be an image URL (string) or an icon component (ReactNode) */\n property?: React.ReactNode | string;\n /** Primary button label */\n primaryButtonLabel?: string;\n /** Secondary button label */\n secondaryButtonLabel?: string;\n /** Primary button click handler */\n onPrimaryButtonClick?: () => void;\n /** Secondary button click handler */\n onSecondaryButtonClick?: () => void;\n /** Additional CSS class names */\n className?: string;\n /** Test ID for testing and automation */\n 'data-testid'?: string;\n /** Content to display in the modal */\n children?: React.ReactNode;\n}\n\nconst COLOURS = {\n iconContainer: {\n default: (): IconContainerTones => 'neutral',\n destructive: (): IconContainerTones => 'destructive',\n },\n button: {\n default: (): ButtonProps['color'] => 'primary',\n destructive: (): ButtonProps['color'] => 'error',\n },\n};\n\nconst getIconContainerTone = (tone: ModalTones): IconContainerTones => {\n return COLOURS.iconContainer[tone]();\n};\n\nconst getButtonTone = (tone: ModalTones): ButtonProps['color'] => {\n return COLOURS.button[tone]();\n};\n\ninterface StyledModalBoxProps {\n tone: ModalTones;\n size: ModalSizes;\n}\n\nconst StyledModalBox = styled(Box, {\n shouldForwardProp: (prop) => !['tone', 'size'].includes(prop as string),\n})<StyledModalBoxProps>(({ theme, size }) => ({\n position: 'absolute',\n backgroundColor: theme.palette.semantic.background['background-overlay'],\n border: 'none',\n borderRadius: theme.radius['radius-16'],\n padding: theme.spacing(4),\n boxShadow: '0px 1px 6px 0px rgba(0, 0, 0, 0.10), 0px 8px 16px 0px rgba(0, 0, 0, 0.15)', // TODO: Pull shadow from tokens when setup\n outline: 'none',\n maxHeight: '90vh',\n overflowY: 'auto',\n\n // Desktop styles (default)\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n maxWidth: size === 'large' ? '700px' : '500px',\n width: `calc(100% - ${theme.spacing(4)})`,\n\n // Tablet & Mobile styles\n [theme.breakpoints.down(breakpointValues.tablet)]: {\n // Small size at bottom of screen\n ...(size === 'small' && {\n maxWidth: `calc(100% - ${theme.spacing(4)})`,\n width: `calc(100% - ${theme.spacing(4)})`,\n top: 'auto',\n bottom: theme.spacing(2),\n left: theme.spacing(2),\n right: theme.spacing(2),\n transform: 'none',\n maxHeight: '70vh',\n overflowY: 'auto',\n // Flex layout to push buttons to bottom when content is small\n display: 'flex',\n flexDirection: 'column',\n }),\n\n // Large size takes full screen\n ...(size === 'large' && {\n height: `calc(100% - ${theme.spacing(4)})`,\n maxHeight: `calc(100% - ${theme.spacing(4)})`,\n overflowY: 'auto',\n // Flex layout to push buttons to bottom when content is small\n display: 'flex',\n flexDirection: 'column',\n }),\n },\n}));\n\nconst StyledImage = styled('img')(({ theme }) => ({\n width: '100%',\n height: 'auto',\n borderRadius: theme.radius['radius-8'],\n}));\n\nconst StyledButtonStack = styled(Stack)({\n justifyContent: 'flex-start',\n alignItems: 'center',\n});\n\nconst StyledContentContainer = styled(Box)(({ theme }) => ({\n [theme.breakpoints.down(breakpointValues.tablet)]: {\n flexGrow: 1,\n display: 'flex',\n flexDirection: 'column',\n minHeight: 'min-content',\n paddingBottom: theme.spacing(3),\n },\n}));\n\n/**\n * Modal component is used to display content that temporarily blocks interaction with the main view.\n * It creates a focused mode for completing tasks or viewing important information without leaving the current page.\n *\n * The component is wrapped with React.memo to optimize performance by preventing unnecessary\n * re-renders when the component's props haven't changed.\n */\nconst Modal = memo(\n ({\n open,\n onClose,\n tone = 'default',\n size = 'large',\n title,\n description,\n property,\n primaryButtonLabel,\n secondaryButtonLabel,\n onPrimaryButtonClick,\n onSecondaryButtonClick,\n className,\n 'data-testid': dataTestId,\n children,\n }: ModalProps) => {\n const theme = useTheme();\n const isTabletOrMobile = useMediaQuery(theme.breakpoints.down(breakpointValues.tablet));\n\n const isImageUrl = typeof property === 'string';\n\n const shouldShowPrimaryButton = !!primaryButtonLabel && !!onPrimaryButtonClick;\n const shouldShowSecondaryButton = !!secondaryButtonLabel && !!onSecondaryButtonClick;\n const showButtonSection = shouldShowPrimaryButton || shouldShowSecondaryButton;\n\n return (\n <MuiModal\n aria-describedby={`modal-description-${dataTestId ?? 'default'}`}\n aria-labelledby={`modal-title-${dataTestId ?? 'default'}`}\n className={className}\n data-testid={dataTestId}\n open={open}\n onClose={onClose}\n >\n <StyledModalBox size={size} tone={tone}>\n <StyledContentContainer>\n {property && (\n <>\n {isImageUrl ? (\n <StyledImage alt={title ?? 'Modal image'} src={property} />\n ) : (\n <IconContainer icon={property} style=\"filled\" tone={getIconContainerTone(tone)} />\n )}\n <Spacer size={16} variant=\"horizontal\" />\n </>\n )}\n {title && (\n <Typography component=\"h2\" id={`modal-title-${dataTestId ?? 'default'}`} variant=\"h3Strong\">\n {title}\n </Typography>\n )}\n {title && description && <Spacer size={8} variant=\"horizontal\" />}\n {description && (\n <Typography\n color={theme.palette.semantic.text['text-weak']}\n id={`modal-description-${dataTestId ?? 'default'}`}\n variant=\"b1Weak\"\n >\n {description}\n </Typography>\n )}\n {children && (\n <>\n <Spacer size={24} variant=\"horizontal\" />\n {children}\n </>\n )}\n </StyledContentContainer>\n {/* TODO: Use button group when it's ready */}\n {showButtonSection && (\n <>\n <Spacer size={24} variant=\"horizontal\" />\n <StyledButtonStack direction={isTabletOrMobile ? 'column' : 'row'} spacing={2}>\n {shouldShowPrimaryButton && (\n <Button\n color={getButtonTone(tone)}\n fullWidth={isTabletOrMobile}\n variant=\"contained\"\n onClick={onPrimaryButtonClick}\n >\n {primaryButtonLabel}\n </Button>\n )}\n {shouldShowSecondaryButton && (\n <Button\n color={getButtonTone(tone)}\n fullWidth={isTabletOrMobile}\n variant=\"outlined\"\n onClick={onSecondaryButtonClick}\n >\n {secondaryButtonLabel}\n </Button>\n )}\n </StyledButtonStack>\n </>\n )}\n </StyledModalBox>\n </MuiModal>\n );\n },\n);\n\nModal.displayName = 'Modal';\n\nexport default Modal;\n"],"names":["COLOURS","iconContainer","default","destructive","button","getIconContainerTone","tone","getButtonTone","StyledModalBox","styled","Box","shouldForwardProp","prop","includes","theme","size","position","backgroundColor","palette","semantic","background","border","borderRadius","radius","padding","spacing","boxShadow","outline","maxHeight","overflowY","top","left","transform","maxWidth","width","breakpoints","down","breakpointValues","tablet","bottom","right","display","flexDirection","height","StyledImage","StyledButtonStack","Stack","justifyContent","alignItems","StyledContentContainer","flexGrow","minHeight","paddingBottom","Modal","memo","open","onClose","title","description","property","primaryButtonLabel","secondaryButtonLabel","onPrimaryButtonClick","onSecondaryButtonClick","className","dataTestId","children","useTheme","isTabletOrMobile","useMediaQuery","isImageUrl","shouldShowPrimaryButton","shouldShowSecondaryButton","showButtonSection","_jsx","MuiModal","_jsxs","alt","src","IconContainer","icon","style","Spacer","variant","jsx","Typography","component","id","color","text","_Fragment","jsxs","direction","Button","fullWidth","onClick","displayName"],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
/** Visual tone of the modal */
|
|
5
|
+
type ModalTones = 'default' | 'destructive';
|
|
6
|
+
/** Size variants for the modal */
|
|
7
|
+
type ModalSizes = 'large' | 'small';
|
|
8
|
+
/** Props for the Modal component */
|
|
9
|
+
interface ModalProps {
|
|
10
|
+
/** Whether the modal is open */
|
|
11
|
+
open: boolean;
|
|
12
|
+
/** Callback function when the modal is closed */
|
|
13
|
+
onClose: () => void;
|
|
14
|
+
/** Visual tone of the modal */
|
|
15
|
+
tone?: ModalTones;
|
|
16
|
+
/** Size variant of the modal */
|
|
17
|
+
size?: ModalSizes;
|
|
18
|
+
/** Main heading text of the modal */
|
|
19
|
+
title?: string;
|
|
20
|
+
/** Detailed description text of the modal */
|
|
21
|
+
description?: string;
|
|
22
|
+
/** Property element to display - can be an image URL (string) or an icon component (ReactNode) */
|
|
23
|
+
property?: React.ReactNode | string;
|
|
24
|
+
/** Primary button label */
|
|
25
|
+
primaryButtonLabel?: string;
|
|
26
|
+
/** Secondary button label */
|
|
27
|
+
secondaryButtonLabel?: string;
|
|
28
|
+
/** Primary button click handler */
|
|
29
|
+
onPrimaryButtonClick?: () => void;
|
|
30
|
+
/** Secondary button click handler */
|
|
31
|
+
onSecondaryButtonClick?: () => void;
|
|
32
|
+
/** Additional CSS class names */
|
|
33
|
+
className?: string;
|
|
34
|
+
/** Test ID for testing and automation */
|
|
35
|
+
'data-testid'?: string;
|
|
36
|
+
/** Content to display in the modal */
|
|
37
|
+
children?: React.ReactNode;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Modal component is used to display content that temporarily blocks interaction with the main view.
|
|
41
|
+
* It creates a focused mode for completing tasks or viewing important information without leaving the current page.
|
|
42
|
+
*
|
|
43
|
+
* The component is wrapped with React.memo to optimize performance by preventing unnecessary
|
|
44
|
+
* re-renders when the component's props haven't changed.
|
|
45
|
+
*/
|
|
46
|
+
declare const Modal: react.MemoExoticComponent<({ open, onClose, tone, size, title, description, property, primaryButtonLabel, secondaryButtonLabel, onPrimaryButtonClick, onSecondaryButtonClick, className, "data-testid": dataTestId, children, }: ModalProps) => react_jsx_runtime.JSX.Element>;
|
|
47
|
+
|
|
48
|
+
export { Modal as default };
|
|
49
|
+
export type { ModalProps, ModalSizes, ModalTones };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{jsx as t,jsxs as e,Fragment as i}from"react/jsx-runtime";import{memo as a}from"react";import o from"@mui/material/Box";import r from"@mui/material/Button";import n from"@mui/material/Modal";import l from"@mui/material/Stack";import{styled as d,useTheme as c}from"@mui/material/styles";import s from"@mui/material/Typography";import m from"@mui/material/useMediaQuery";import{breakpointValues as p}from"../../../themes/tokens/breakpoints/breakpoints.js";import u from"../../atoms/IconContainer/index.js";import h from"../../Spacer/index.js";const f={iconContainer:{default:()=>"neutral",destructive:()=>"destructive"},button:{default:()=>"primary",destructive:()=>"error"}},g=t=>f.iconContainer[t](),x=t=>f.button[t](),b=d(o,{shouldForwardProp:t=>!["tone","size"].includes(t)})((({theme:t,size:e})=>({position:"absolute",backgroundColor:t.palette.semantic.background["background-overlay"],border:"none",borderRadius:t.radius["radius-16"],padding:t.spacing(4),boxShadow:"0px 1px 6px 0px rgba(0, 0, 0, 0.10), 0px 8px 16px 0px rgba(0, 0, 0, 0.15)",outline:"none",maxHeight:"90vh",overflowY:"auto",top:"50%",left:"50%",transform:"translate(-50%, -50%)",maxWidth:"large"===e?"700px":"500px",width:`calc(100% - ${t.spacing(4)})`,[t.breakpoints.down(p.tablet)]:{..."small"===e&&{maxWidth:`calc(100% - ${t.spacing(4)})`,width:`calc(100% - ${t.spacing(4)})`,top:"auto",bottom:t.spacing(2),left:t.spacing(2),right:t.spacing(2),transform:"none",maxHeight:"70vh",overflowY:"auto",display:"flex",flexDirection:"column"},..."large"===e&&{height:`calc(100% - ${t.spacing(4)})`,maxHeight:`calc(100% - ${t.spacing(4)})`,overflowY:"auto",display:"flex",flexDirection:"column"}}}))),y=d("img")((({theme:t})=>({width:"100%",height:"auto",borderRadius:t.radius["radius-8"]}))),v=d(l)({justifyContent:"flex-start",alignItems:"center"}),k=d(o)((({theme:t})=>({[t.breakpoints.down(p.tablet)]:{flexGrow:1,display:"flex",flexDirection:"column",minHeight:"min-content",paddingBottom:t.spacing(3)}}))),w=a((({open:a,onClose:o,tone:l="default",size:d="large",title:f,description:w,property:z,primaryButtonLabel:C,secondaryButtonLabel:$,onPrimaryButtonClick:B,onSecondaryButtonClick:j,className:S,"data-testid":W,children:H})=>{const M=c(),D=m(M.breakpoints.down(p.tablet)),N=!!C&&!!B,Y=!!$&&!!j,I=N||Y;return t(n,{"aria-describedby":`modal-description-${W??"default"}`,"aria-labelledby":`modal-title-${W??"default"}`,className:S,"data-testid":W,open:a,onClose:o,children:e(b,{size:d,tone:l,children:[e(k,{children:[z&&e(i,{children:["string"==typeof z?t(y,{alt:f??"Modal image",src:z}):t(u,{icon:z,style:"filled",tone:g(l)}),t(h,{size:16,variant:"horizontal"})]}),f&&t(s,{component:"h2",id:`modal-title-${W??"default"}`,variant:"h3Strong",children:f}),f&&w&&t(h,{size:8,variant:"horizontal"}),w&&t(s,{color:M.palette.semantic.text["text-weak"],id:`modal-description-${W??"default"}`,variant:"b1Weak",children:w}),H&&e(i,{children:[t(h,{size:24,variant:"horizontal"}),H]})]}),I&&e(i,{children:[t(h,{size:24,variant:"horizontal"}),e(v,{direction:D?"column":"row",spacing:2,children:[N&&t(r,{color:x(l),fullWidth:D,variant:"contained",onClick:B,children:C}),Y&&t(r,{color:x(l),fullWidth:D,variant:"outlined",onClick:j,children:$})]})]})]})})}));w.displayName="Modal";export{w as default};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/components/molecules/Modal/index.tsx"],"sourcesContent":["/* eslint-disable @typescript-eslint/explicit-function-return-type */\nimport { memo } from 'react';\n\nimport Box from '@mui/material/Box';\nimport Button, { type ButtonProps } from '@mui/material/Button';\nimport MuiModal from '@mui/material/Modal';\nimport Stack from '@mui/material/Stack';\nimport { styled, useTheme } from '@mui/material/styles';\nimport Typography from '@mui/material/Typography';\nimport useMediaQuery from '@mui/material/useMediaQuery';\n\nimport { breakpointValues } from '../../../themes/tokens/breakpoints/breakpoints';\nimport IconContainer, { type IconContainerTones } from '../../atoms/IconContainer';\nimport Spacer from '../../Spacer';\n\n/** Visual tone of the modal */\nexport type ModalTones = 'default' | 'destructive';\n\n/** Size variants for the modal */\nexport type ModalSizes = 'large' | 'small';\n\n/** Props for the Modal component */\nexport interface ModalProps {\n /** Whether the modal is open */\n open: boolean;\n /** Callback function when the modal is closed */\n onClose: () => void;\n /** Visual tone of the modal */\n tone?: ModalTones;\n /** Size variant of the modal */\n size?: ModalSizes;\n /** Main heading text of the modal */\n title?: string;\n /** Detailed description text of the modal */\n description?: string;\n /** Property element to display - can be an image URL (string) or an icon component (ReactNode) */\n property?: React.ReactNode | string;\n /** Primary button label */\n primaryButtonLabel?: string;\n /** Secondary button label */\n secondaryButtonLabel?: string;\n /** Primary button click handler */\n onPrimaryButtonClick?: () => void;\n /** Secondary button click handler */\n onSecondaryButtonClick?: () => void;\n /** Additional CSS class names */\n className?: string;\n /** Test ID for testing and automation */\n 'data-testid'?: string;\n /** Content to display in the modal */\n children?: React.ReactNode;\n}\n\nconst COLOURS = {\n iconContainer: {\n default: (): IconContainerTones => 'neutral',\n destructive: (): IconContainerTones => 'destructive',\n },\n button: {\n default: (): ButtonProps['color'] => 'primary',\n destructive: (): ButtonProps['color'] => 'error',\n },\n};\n\nconst getIconContainerTone = (tone: ModalTones): IconContainerTones => {\n return COLOURS.iconContainer[tone]();\n};\n\nconst getButtonTone = (tone: ModalTones): ButtonProps['color'] => {\n return COLOURS.button[tone]();\n};\n\ninterface StyledModalBoxProps {\n tone: ModalTones;\n size: ModalSizes;\n}\n\nconst StyledModalBox = styled(Box, {\n shouldForwardProp: (prop) => !['tone', 'size'].includes(prop as string),\n})<StyledModalBoxProps>(({ theme, size }) => ({\n position: 'absolute',\n backgroundColor: theme.palette.semantic.background['background-overlay'],\n border: 'none',\n borderRadius: theme.radius['radius-16'],\n padding: theme.spacing(4),\n boxShadow: '0px 1px 6px 0px rgba(0, 0, 0, 0.10), 0px 8px 16px 0px rgba(0, 0, 0, 0.15)', // TODO: Pull shadow from tokens when setup\n outline: 'none',\n maxHeight: '90vh',\n overflowY: 'auto',\n\n // Desktop styles (default)\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n maxWidth: size === 'large' ? '700px' : '500px',\n width: `calc(100% - ${theme.spacing(4)})`,\n\n // Tablet & Mobile styles\n [theme.breakpoints.down(breakpointValues.tablet)]: {\n // Small size at bottom of screen\n ...(size === 'small' && {\n maxWidth: `calc(100% - ${theme.spacing(4)})`,\n width: `calc(100% - ${theme.spacing(4)})`,\n top: 'auto',\n bottom: theme.spacing(2),\n left: theme.spacing(2),\n right: theme.spacing(2),\n transform: 'none',\n maxHeight: '70vh',\n overflowY: 'auto',\n // Flex layout to push buttons to bottom when content is small\n display: 'flex',\n flexDirection: 'column',\n }),\n\n // Large size takes full screen\n ...(size === 'large' && {\n height: `calc(100% - ${theme.spacing(4)})`,\n maxHeight: `calc(100% - ${theme.spacing(4)})`,\n overflowY: 'auto',\n // Flex layout to push buttons to bottom when content is small\n display: 'flex',\n flexDirection: 'column',\n }),\n },\n}));\n\nconst StyledImage = styled('img')(({ theme }) => ({\n width: '100%',\n height: 'auto',\n borderRadius: theme.radius['radius-8'],\n}));\n\nconst StyledButtonStack = styled(Stack)({\n justifyContent: 'flex-start',\n alignItems: 'center',\n});\n\nconst StyledContentContainer = styled(Box)(({ theme }) => ({\n [theme.breakpoints.down(breakpointValues.tablet)]: {\n flexGrow: 1,\n display: 'flex',\n flexDirection: 'column',\n minHeight: 'min-content',\n paddingBottom: theme.spacing(3),\n },\n}));\n\n/**\n * Modal component is used to display content that temporarily blocks interaction with the main view.\n * It creates a focused mode for completing tasks or viewing important information without leaving the current page.\n *\n * The component is wrapped with React.memo to optimize performance by preventing unnecessary\n * re-renders when the component's props haven't changed.\n */\nconst Modal = memo(\n ({\n open,\n onClose,\n tone = 'default',\n size = 'large',\n title,\n description,\n property,\n primaryButtonLabel,\n secondaryButtonLabel,\n onPrimaryButtonClick,\n onSecondaryButtonClick,\n className,\n 'data-testid': dataTestId,\n children,\n }: ModalProps) => {\n const theme = useTheme();\n const isTabletOrMobile = useMediaQuery(theme.breakpoints.down(breakpointValues.tablet));\n\n const isImageUrl = typeof property === 'string';\n\n const shouldShowPrimaryButton = !!primaryButtonLabel && !!onPrimaryButtonClick;\n const shouldShowSecondaryButton = !!secondaryButtonLabel && !!onSecondaryButtonClick;\n const showButtonSection = shouldShowPrimaryButton || shouldShowSecondaryButton;\n\n return (\n <MuiModal\n aria-describedby={`modal-description-${dataTestId ?? 'default'}`}\n aria-labelledby={`modal-title-${dataTestId ?? 'default'}`}\n className={className}\n data-testid={dataTestId}\n open={open}\n onClose={onClose}\n >\n <StyledModalBox size={size} tone={tone}>\n <StyledContentContainer>\n {property && (\n <>\n {isImageUrl ? (\n <StyledImage alt={title ?? 'Modal image'} src={property} />\n ) : (\n <IconContainer icon={property} style=\"filled\" tone={getIconContainerTone(tone)} />\n )}\n <Spacer size={16} variant=\"horizontal\" />\n </>\n )}\n {title && (\n <Typography component=\"h2\" id={`modal-title-${dataTestId ?? 'default'}`} variant=\"h3Strong\">\n {title}\n </Typography>\n )}\n {title && description && <Spacer size={8} variant=\"horizontal\" />}\n {description && (\n <Typography\n color={theme.palette.semantic.text['text-weak']}\n id={`modal-description-${dataTestId ?? 'default'}`}\n variant=\"b1Weak\"\n >\n {description}\n </Typography>\n )}\n {children && (\n <>\n <Spacer size={24} variant=\"horizontal\" />\n {children}\n </>\n )}\n </StyledContentContainer>\n {/* TODO: Use button group when it's ready */}\n {showButtonSection && (\n <>\n <Spacer size={24} variant=\"horizontal\" />\n <StyledButtonStack direction={isTabletOrMobile ? 'column' : 'row'} spacing={2}>\n {shouldShowPrimaryButton && (\n <Button\n color={getButtonTone(tone)}\n fullWidth={isTabletOrMobile}\n variant=\"contained\"\n onClick={onPrimaryButtonClick}\n >\n {primaryButtonLabel}\n </Button>\n )}\n {shouldShowSecondaryButton && (\n <Button\n color={getButtonTone(tone)}\n fullWidth={isTabletOrMobile}\n variant=\"outlined\"\n onClick={onSecondaryButtonClick}\n >\n {secondaryButtonLabel}\n </Button>\n )}\n </StyledButtonStack>\n </>\n )}\n </StyledModalBox>\n </MuiModal>\n );\n },\n);\n\nModal.displayName = 'Modal';\n\nexport default Modal;\n"],"names":["COLOURS","iconContainer","default","destructive","button","getIconContainerTone","tone","getButtonTone","StyledModalBox","styled","Box","shouldForwardProp","prop","includes","theme","size","position","backgroundColor","palette","semantic","background","border","borderRadius","radius","padding","spacing","boxShadow","outline","maxHeight","overflowY","top","left","transform","maxWidth","width","breakpoints","down","breakpointValues","tablet","bottom","right","display","flexDirection","height","StyledImage","StyledButtonStack","Stack","justifyContent","alignItems","StyledContentContainer","flexGrow","minHeight","paddingBottom","Modal","memo","open","onClose","title","description","property","primaryButtonLabel","secondaryButtonLabel","onPrimaryButtonClick","onSecondaryButtonClick","className","dataTestId","children","useTheme","isTabletOrMobile","useMediaQuery","shouldShowPrimaryButton","shouldShowSecondaryButton","showButtonSection","_jsx","MuiModal","_jsxs","alt","src","IconContainer","icon","style","Spacer","variant","Typography","component","id","color","text","_Fragment","direction","Button","fullWidth","onClick","displayName"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flipdish/portal-library",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist"
|
|
6
6
|
],
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"@tanstack/react-query": "^5.62.0",
|
|
79
79
|
"@testing-library/jest-dom": "6.6.3",
|
|
80
80
|
"@testing-library/react": "15.0.7",
|
|
81
|
-
"@types/react": "18.3.
|
|
81
|
+
"@types/react": "18.3.21",
|
|
82
82
|
"@types/react-dom": "18.3.7",
|
|
83
83
|
"@typescript-eslint/eslint-plugin": "7.18.0",
|
|
84
84
|
"@typescript-eslint/parser": "7.18.0",
|