-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1770a51
commit 80bc685
Showing
8 changed files
with
472 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
.uw-Alert { | ||
flex-direction: row; | ||
gap: var(--space-100); | ||
padding: var(--space-200); | ||
border-radius: 8px; | ||
border-width: 1px; | ||
border-style: solid; | ||
border-color: var(--alert-border-color); | ||
background-color: var(--alert-background-color); | ||
color: var(--alert-text-color); | ||
|
||
&:where(:focus-visible) { | ||
outline: none; | ||
border-radius: 4px; | ||
box-shadow: 0 0 0 2px var(--alert-focus-color); | ||
} | ||
|
||
> :where(svg, [data-icon]) { | ||
color: var(--alert-icon-color); | ||
} | ||
|
||
&:where([data-colorscheme='cyan']) { | ||
--alert-text-color: var(--color-cyan900); | ||
--alert-background-color: var(--color-cyan50); | ||
--alert-icon-color: var(--color-cyan700); | ||
--alert-border-color: var(--color-cyan500); | ||
} | ||
|
||
&:where([data-colorscheme='green']) { | ||
--alert-text-color: var(--color-green900); | ||
--alert-background-color: var(--color-green50); | ||
--alert-icon-color: var(--color-green700); | ||
--alert-border-color: var(--color-green500); | ||
} | ||
|
||
&:where([data-colorscheme='gold']) { | ||
--alert-text-color: var(--color-gold900); | ||
--alert-background-color: var(--color-gold50); | ||
--alert-icon-color: var(--color-gold700); | ||
--alert-border-color: var(--color-gold500); | ||
} | ||
|
||
&:where([data-colorscheme='red']) { | ||
--alert-text-color: var(--color-red900); | ||
--alert-background-color: var(--color-red50); | ||
--alert-icon-color: var(--color-red700); | ||
--alert-border-color: var(--color-red500); | ||
} | ||
|
||
.uw-IconButton { | ||
height: auto; | ||
width: auto; | ||
|
||
&:where(:active) { | ||
background-color: transparent; | ||
} | ||
} | ||
} | ||
|
||
.uw-AlertLink { | ||
font-weight: var(--font-weight-semibold); | ||
color: var(--alert-link-color); | ||
text-decoration-color: var(--alert-link-color); | ||
|
||
&:where(:visited) { | ||
color: var(--alert-link-color); | ||
text-decoration-color: var(--alert-link-color); /* TODO: do we need this? */ | ||
} | ||
|
||
&:where([data-colorscheme='cyan'] &) { | ||
--alert-link-color: var(--color-cyan700); | ||
--alert-focus-color: var(--color-cyan700); | ||
} | ||
|
||
&:where([data-colorscheme='green'] &) { | ||
--alert-link-color: var(--color-green700); | ||
--alert-focus-color: var(--color-green700); | ||
} | ||
|
||
&:where([data-colorscheme='gold'] &) { | ||
--alert-link-color: var(--color-gold700); | ||
--alert-focus-color: var(--color-gold700); | ||
} | ||
|
||
&:where([data-colorscheme='red'] &) { | ||
--alert-link-color: var(--color-red700); | ||
--alert-focus-color: var(--color-red700); | ||
} | ||
} | ||
|
||
.uw-AlertButton { | ||
color: var(--alert-button-color); | ||
|
||
@media (hover: hover) { | ||
&:where(:hover) { | ||
color: var(--alert-button-color-hover); | ||
} | ||
} | ||
} | ||
|
||
.uw-AlertTitle { | ||
color: var(--alert-text-color); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { ComponentPropsWithout, RemovedProps } from '../../types/component-props'; | ||
|
||
export interface AlertProps extends ComponentPropsWithout<'div', RemovedProps> { | ||
/** | ||
* Sets the colour scheme. | ||
* @default cyan | ||
*/ | ||
colorScheme?: 'cyan' | 'green' | 'gold' | 'red'; | ||
/** | ||
* Sets the function to be called when the alert is closed. | ||
*/ | ||
onClose?: () => void; | ||
/** | ||
* Sets the direction of the alert content. | ||
* @default column | ||
*/ | ||
direction?: 'row' | 'column'; | ||
/** | ||
* Sets the title of the alert. | ||
*/ | ||
title?: string; | ||
/** | ||
* Sets the text of the alert. | ||
*/ | ||
text?: React.ReactNode; | ||
/** | ||
* Sets the link text of the alert. | ||
*/ | ||
linkText?: string; | ||
/** | ||
* Sets the link href of the alert. | ||
*/ | ||
linkHref?: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import * as React from 'react'; | ||
|
||
import type { Meta, StoryObj } from '@storybook/react'; | ||
|
||
import { Alert } from './Alert'; | ||
import { Flex } from '../Flex/Flex'; | ||
import { Button } from '../Button/Button'; | ||
import { AlertText } from './AlertText'; | ||
import { Strong } from '../Strong/Strong'; | ||
|
||
const colorSchemes = ['cyan', 'red', 'green', 'gold'] as const; | ||
|
||
const meta: Meta<typeof Alert> = { | ||
title: 'Stories / Alert', | ||
component: Alert, | ||
argTypes: { | ||
children: { control: { type: 'text' } }, | ||
colorScheme: { options: colorSchemes, control: { type: 'radio' } }, | ||
direction: { options: ['column', 'row'], control: { type: 'radio' } }, | ||
}, | ||
args: { | ||
title: 'Did you know?', | ||
text: 'The quick brown fox jumps over the lazy dog.', | ||
linkText: 'Learn more', | ||
linkHref: '#', | ||
}, | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof Alert>; | ||
|
||
export const KitchenSink: Story = { | ||
parameters: { controls: { hideNoControlsWarning: true } }, | ||
render: args => { | ||
return ( | ||
<Flex direction="column" gap="800" width="800px" padding="400"> | ||
{colorSchemes.map(colorScheme => ( | ||
<Flex key={colorScheme} direction="column" gap="200"> | ||
<Alert {...args} colorScheme={colorScheme} direction="row" /> | ||
<Alert {...args} colorScheme={colorScheme} direction="column" /> | ||
<Alert | ||
{...args} | ||
colorScheme={colorScheme} | ||
direction="row" | ||
onClose={() => alert('closed')} | ||
/> | ||
<Alert | ||
{...args} | ||
colorScheme={colorScheme} | ||
direction="column" | ||
onClose={() => alert('closed')} | ||
/> | ||
<Alert {...args} colorScheme={colorScheme} direction="row" linkText={undefined} /> | ||
<Alert {...args} colorScheme={colorScheme} direction="column" linkText={undefined} /> | ||
</Flex> | ||
))} | ||
</Flex> | ||
); | ||
}, | ||
}; | ||
|
||
export const Workshop: Story = {}; | ||
|
||
export const ToggleAlert: Story = { | ||
parameters: { layout: 'none' }, | ||
render: () => { | ||
const [open, setOpen] = React.useState(false); | ||
const handleOpen = () => setOpen(true); | ||
const handleClose = () => setOpen(false); | ||
|
||
return ( | ||
<Flex direction="column" align="start" gap="200" width="fit-content" padding="400"> | ||
<Button size="small" variant="outline" onClick={handleOpen}> | ||
Open alert | ||
</Button> | ||
{open ? ( | ||
<Alert direction="row" text="This is for your information." onClose={handleClose} /> | ||
) : null} | ||
</Flex> | ||
); | ||
}, | ||
}; | ||
|
||
export const AlertColorSchemes: Story = { | ||
name: 'Alert ColorSchemes', | ||
render: () => { | ||
return ( | ||
<Flex direction="column" gap="200" width="fit-content"> | ||
<Alert | ||
colorScheme="cyan" | ||
direction="row" | ||
text="Cyan colour scheme for informational messages" | ||
/> | ||
<Alert | ||
colorScheme="green" | ||
direction="row" | ||
text="Green colour scheme for positive or success messages" | ||
/> | ||
<Alert colorScheme="gold" direction="row" text="Gold colour scheme for warning messages" /> | ||
<Alert | ||
colorScheme="red" | ||
direction="row" | ||
text="Red colour scheme for errors and higher warnings" | ||
/> | ||
</Flex> | ||
); | ||
}, | ||
}; | ||
|
||
export const AlertAdvancedUsage: Story = { | ||
render: () => { | ||
return ( | ||
<Flex align="start"> | ||
<Alert | ||
colorScheme="red" | ||
title="Delete account" | ||
text={ | ||
<AlertText> | ||
Are you <Strong>sure</Strong> you want to do this? | ||
</AlertText> | ||
} | ||
linkHref="#" | ||
linkText="Delete" | ||
onClose={() => {}} | ||
direction="row" | ||
/> | ||
</Flex> | ||
); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import * as React from 'react'; | ||
|
||
import clsx from 'clsx'; | ||
|
||
import { | ||
ChevronRightMediumIcon, | ||
CloseMediumIcon, | ||
InformationMediumContainedIcon, | ||
TickMediumContainedIcon, | ||
WarningMediumContainedIcon, | ||
} from '@utilitywarehouse/react-icons'; | ||
|
||
import { AlertProps } from './Alert.props'; | ||
import { AlertLink } from './AlertLink'; | ||
import { AlertText } from './AlertText'; | ||
import { AlertTitle } from './AlertTitle'; | ||
import { withGlobalPrefix } from '../../helpers/with-global-prefix'; | ||
import { ElementRef } from 'react'; | ||
import { DATA_ATTRIBUTES } from '../../helpers/data-attributes'; | ||
import { Flex } from '../Flex/Flex'; | ||
import { IconButton } from '../IconButton/IconButton'; | ||
|
||
const componentName = 'Alert'; | ||
const componentClassName = withGlobalPrefix(componentName); | ||
|
||
type AlertElement = ElementRef<'div'>; | ||
|
||
/** | ||
* Provide feedback messages to users. Alerts are dynamic content that is | ||
* injected into the page when it changes and should be used sparingly. | ||
*/ | ||
export const Alert = React.forwardRef<AlertElement, AlertProps>( | ||
( | ||
{ | ||
className, | ||
colorScheme = 'cyan', | ||
direction = 'column', | ||
children, | ||
onClose, | ||
title, | ||
text, | ||
linkText, | ||
linkHref, | ||
...props | ||
}, | ||
ref | ||
) => { | ||
const icons = { | ||
cyan: InformationMediumContainedIcon, | ||
green: TickMediumContainedIcon, | ||
gold: WarningMediumContainedIcon, | ||
red: WarningMediumContainedIcon, | ||
}; | ||
const AlertIcon = icons[colorScheme]; | ||
const dataAttributeProps = { | ||
[DATA_ATTRIBUTES.colorscheme]: colorScheme, | ||
'data-direction': direction, | ||
}; | ||
return ( | ||
<Flex | ||
align={direction === 'row' ? 'center' : 'start'} | ||
ref={ref} | ||
className={clsx(componentClassName, className)} | ||
role="alert" // Adding role for dynamic alerts | ||
aria-live="assertive" // Making it announced immediately | ||
aria-atomic="true" // Ensuring the entire alert is read as a whole | ||
{...dataAttributeProps} | ||
{...props} | ||
> | ||
<AlertIcon /> | ||
|
||
<Flex | ||
direction={direction} | ||
gap="100" | ||
style={{ flex: 1 }} | ||
align={direction === 'row' ? 'center' : 'start'} | ||
> | ||
{children ?? ( | ||
<> | ||
{title ? <AlertTitle>{title}</AlertTitle> : null} | ||
{text ? <AlertText>{text}</AlertText> : null} | ||
{linkText && linkHref ? <AlertLink href={linkHref}>{linkText}</AlertLink> : null} | ||
</> | ||
)} | ||
</Flex> | ||
{linkHref && !linkText ? ( | ||
<IconButton | ||
variant="ghost" | ||
colorScheme={colorScheme} | ||
asChild | ||
title="Alert action" | ||
label="Alert action" | ||
> | ||
<a href={linkHref}> | ||
<ChevronRightMediumIcon /> | ||
</a> | ||
</IconButton> | ||
) : null} | ||
{onClose ? ( | ||
<IconButton | ||
variant="ghost" | ||
colorScheme={colorScheme} | ||
onClick={onClose} | ||
title="Close" | ||
label="Close alert" | ||
> | ||
<CloseMediumIcon /> | ||
</IconButton> | ||
) : null} | ||
</Flex> | ||
); | ||
} | ||
); | ||
|
||
Alert.displayName = componentName; |
Oops, something went wrong.