Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/app-elements/src/ui/atoms/Icon/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const iconMapping = {
equals: phosphor.EqualsIcon,
eye: phosphor.EyeIcon,
eyeSlash: phosphor.EyeSlashIcon,
fileArrowDown: phosphor.FileArrowDownIcon,
flag: phosphor.FlagIcon,
folderOpen: phosphor.FolderOpenIcon,
funnel: phosphor.FunnelIcon,
Expand Down
18 changes: 15 additions & 3 deletions packages/app-elements/src/ui/atoms/RadialProgress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,16 @@ export interface RadialProgressProps extends SVGAttributes<SVGElement> {
* When missing, default size is 42px, small is 24px
* @default 'large'
*/
size?: "small" | "large"
size?: "small" | "large" | "x-large"
/**
* Optional icon to be rendered in the center of the circle
*/
icon?: StatusIconProps["name"]

/**
* Optional alignment of the component
*/
align?: "center"
}

/**
Expand All @@ -32,9 +37,10 @@ function RadialProgress({
className,
size = "large",
icon,
align,
...rest
}: RadialProgressProps): JSX.Element {
const sizePixels = size === "small" ? 24 : 42
const sizePixels = size === "small" ? 24 : size === "x-large" ? 56 : 42
const viewBox = `0 0 ${sizePixels * 2} ${sizePixels * 2}`
const circumference = sizePixels * 2 * Math.PI
const emptyOffset =
Expand All @@ -46,7 +52,13 @@ function RadialProgress({
data-testid="radial-progress"
viewBox={viewBox}
xmlns="http://www.w3.org/2000/svg"
className={cn("transform -rotate-90 rounded-full", className)}
className={cn(
"transform -rotate-90 rounded-full",
{
"mx-auto": align === "center",
},
className,
)}
width={sizePixels}
height={sizePixels}
{...rest}
Expand Down
26 changes: 23 additions & 3 deletions packages/app-elements/src/ui/atoms/StatusIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,42 @@ export interface StatusIconProps extends React.HTMLAttributes<HTMLDivElement> {
/**
* padding around the icon can bne: 'none' | 'small' | 'large'
*/
gap?: "none" | "small" | "medium" | "large"
gap?: "none" | "small" | "medium" | "large" | "x-large"
/**
* Optional alignment of the component
*/
align?: "center"
}

export const StatusIcon: React.FC<StatusIconProps> = ({
name,
className,
background = "none",
gap = "none",
align,
...rest
}) => {
const iconSize = (gap?: StatusIconProps["gap"]): string | undefined => {
switch (gap) {
case "x-large":
return "1.65rem"
case "large":
return "1.25rem"
default:
return undefined
}
}

return (
<div
className={cn([
"inline-block",
// alignment
{ "mx-auto block": align === "center" },
{ "inline-block": align !== "center" },
"align-middle",
"w-fit",
// padding
{ "p-[14px]": gap === "x-large" },
{ "p-[10px]": gap === "large" },
{ "p-[6px]": gap === "medium" },
{ "p-[3px]": gap === "small" },
Expand All @@ -55,14 +74,15 @@ export const StatusIcon: React.FC<StatusIconProps> = ({
{ "bg-teal border-teal text-white": background === "teal" },
{ "bg-white border-gray-200": background === "white" },
{ "bg-gray-800 border-gray-800 text-white": background === "black" },

// className
className,
])}
{...rest}
>
<Icon
name={name}
size={gap === "large" ? "1.25rem" : undefined}
size={iconSize(gap)}
weight={background !== "none" ? "bold" : undefined}
/>
</div>
Expand Down
11 changes: 5 additions & 6 deletions packages/app-elements/src/ui/composite/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export type ModalProps = {
/** Modal content */
children: React.ReactNode
/** Max width preset */
size?: "large" | "small"
size?: "large" | "small" | "x-small"
}

export const Modal: React.FC<
Expand Down Expand Up @@ -56,7 +56,7 @@ export const Modal: React.FC<
<ModalContext.Provider value={{ onClose, modalId }}>
<div
className={cn(
"bg-white rounded shadow-xl",
"bg-white rounded-md shadow-xl",
"max-h-[90vh] flex flex-col",
)}
role="dialog"
Expand Down Expand Up @@ -98,6 +98,7 @@ export const Modal: React.FC<
"fixed z-70 w-full top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2",
size === "large" && "max-w-155 md:w-155",
size === "small" && "max-w-105 md:w-105",
size === "x-small" && "max-w-80 md:w-80",
)}
data-testid="modal"
>
Expand Down Expand Up @@ -131,14 +132,12 @@ Modal.Header = ({ children }) => {
}

Modal.Body = ({ children }) => {
return (
<div className="px-6 py-4 overflow-y-auto flex-1 min-h-0">{children}</div>
)
return <div className="p-6 overflow-y-auto flex-1 min-h-0">{children}</div>
}

Modal.Footer = ({ children }) => {
return (
<div className="flex-none px-6 py-4" data-testid="modal-footer">
<div className="flex-none p-6 space-y-2" data-testid="modal-footer">
{children}
</div>
)
Expand Down
63 changes: 63 additions & 0 deletions packages/docs/src/stories/composite/Modal.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import {
import type { Meta, StoryFn } from "@storybook/react-vite"
import { useRef, useState } from "react"
import { Button } from "#ui/atoms/Button"
import { Icon } from "#ui/atoms/Icon"
import { Spacer } from "#ui/atoms/Spacer"
import { StatusIcon } from "#ui/atoms/StatusIcon"
import { Text } from "#ui/atoms/Text"
import { Modal } from "#ui/composite/Modal"
import { InputSelect } from "#ui/forms/InputSelect"

Expand Down Expand Up @@ -169,3 +173,62 @@ export const WithInput: StoryFn = () => {
</div>
)
}

/**
* Modal as dialog without header
*/
export const AsDialog: StoryFn = () => {
const [show, setShow] = useState(false)

const handleClose = () => setShow(false)
const handleShow = () => setShow(true)

return (
<div>
<Button onClick={handleShow}>Open modal</Button>
<Modal show={show} onClose={handleClose} size="x-small">
<Modal.Body>
<Spacer bottom="4">
<StatusIcon
name="check"
background="green"
gap="x-large"
align="center"
/>
</Spacer>
<Text weight="semibold" align="center" tag="div">
Your coupons are ready.
</Text>
<Spacer top="1">
<Text align="center" tag="div" size="small" variant="info">
Download now or find them later in Imports.
</Text>
</Spacer>
</Modal.Body>
<Modal.Footer>
<Button
variant="primary"
onClick={() => {
alert("Downloaded!")
handleClose()
}}
fullWidth
alignItems="center"
>
<Icon name="fileArrowDown" />
Download (CSV)
</Button>
<Button
variant="secondary"
onClick={() => {
handleClose()
}}
fullWidth
>
Cancel
</Button>
</Modal.Footer>
</Modal>
</div>
)
}
Loading