Skip to content
Open
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
335 changes: 335 additions & 0 deletions pages/theming/themed-stroke-width.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,335 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useLayoutEffect, useState } from 'react';

import {
Alert,
Box,
Button,
Flashbar,
FormField,
IconProvider,
Input,
KeyValuePairs,
Link,
Select,
SelectProps,
SpaceBetween,
StatusIndicator,
} from '~components';
import Icon, { IconProps } from '~components/icon';
import icons from '~components/icon/generated/icons';
import { applyTheme, Theme } from '~components/theming';

import ScreenshotArea from '../utils/screenshot-area';

import styles from '../icon/icons-list.scss';

export default function () {
const [themed, setThemed] = useState<boolean>(false);
const [strokeSmall, setStrokeSmall] = useState<string>('1');
const [strokeNormal, setStrokeNormal] = useState<string>('1');
const [strokeMedium, setStrokeMedium] = useState<string>('1');
const [strokeBig, setStrokeBig] = useState<string>('1.5');
const [strokeLarge, setStrokeLarge] = useState<string>('2');
Comment on lines +30 to +34
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does values are not aligned with the default values like 2px, 3px and 4px. Is this on purpose?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. I will refine the UIs overall in the example page

const [selectedOption, setSelectedOption] = useState<SelectProps.Option>({ label: 'Option 1', value: '1' });

// Reload page once after initial load to fix theme application
useLayoutEffect(() => {
const hasReloaded = sessionStorage.getItem('themed-stroke-width-reloaded');
if (!hasReloaded) {
sessionStorage.setItem('themed-stroke-width-reloaded', 'true');
window.location.reload();
}
}, []);

useLayoutEffect(() => {
let reset: () => void = () => {};
if (themed) {
const theme: Theme = {
tokens: {
borderWidthIconSmall: strokeSmall ? `${strokeSmall}px` : '1px',
borderWidthIconNormal: strokeNormal ? `${strokeNormal}px` : '1px',
borderWidthIconMedium: strokeMedium ? `${strokeMedium}px` : '1px',
borderWidthIconBig: strokeBig ? `${strokeBig}px` : '1.5px',
borderWidthIconLarge: strokeLarge ? `${strokeLarge}px` : '2px',
},
};

const result = applyTheme({
theme,
baseThemeId: 'visual-refresh',
});
reset = result.reset;
}
return reset;
}, [themed, strokeSmall, strokeNormal, strokeMedium, strokeBig, strokeLarge]);

return (
<div style={{ padding: 15 }}>
<h1>Themed icon stroke width</h1>
<SpaceBetween size="m" direction="vertical">
<label>
<input
type="checkbox"
data-testid="apply-theme"
checked={themed}
onChange={evt => setThemed(evt.currentTarget.checked)}
/>
<span style={{ marginInlineStart: 5 }}>Apply custom stroke width themes</span>
</label>

<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: '10px', maxWidth: '800px' }}>
<FormField label="Small (16px)" description="Default: 2px">
<Input
type="number"
value={strokeSmall}
onChange={evt => {
const numValue = parseFloat(evt.detail.value);
if (!isNaN(numValue) && numValue >= 1) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe to much but we could add here min/max constraints

setStrokeSmall(evt.detail.value);
} else if (evt.detail.value === '') {
setStrokeSmall(evt.detail.value);
}
}}
placeholder="1px"
step={0.5}
inputMode="decimal"
/>
</FormField>

<FormField label="Normal (16px)" description="Default: 2px">
<Input
type="number"
value={strokeNormal}
onChange={evt => {
const numValue = parseFloat(evt.detail.value);
if (!isNaN(numValue) && numValue >= 1) {
setStrokeNormal(evt.detail.value);
} else if (evt.detail.value === '') {
setStrokeNormal(evt.detail.value);
}
}}
placeholder="1px"
step={0.5}
inputMode="decimal"
/>
</FormField>

<FormField label="Medium (20px)" description="Default: 2px">
<Input
type="number"
value={strokeMedium}
onChange={evt => {
const numValue = parseFloat(evt.detail.value);
if (!isNaN(numValue) && numValue >= 1) {
setStrokeMedium(evt.detail.value);
} else if (evt.detail.value === '') {
setStrokeMedium(evt.detail.value);
}
}}
placeholder="1px"
step={0.5}
inputMode="decimal"
/>
</FormField>

<FormField label="Big (32px)" description="Default: 3px">
<Input
type="number"
value={strokeBig}
onChange={evt => {
const numValue = parseFloat(evt.detail.value);
if (!isNaN(numValue) && numValue >= 1) {
setStrokeBig(evt.detail.value);
} else if (evt.detail.value === '') {
setStrokeBig(evt.detail.value);
}
}}
placeholder="1.5px"
step={0.5}
inputMode="decimal"
/>
</FormField>

<FormField label="Large (48px)" description="Default: 4px">
<Input
type="number"
value={strokeLarge}
onChange={evt => {
const numValue = parseFloat(evt.detail.value);
if (!isNaN(numValue) && numValue >= 1) {
setStrokeLarge(evt.detail.value);
} else if (evt.detail.value === '') {
setStrokeLarge(evt.detail.value);
}
}}
placeholder="2px"
step={0.5}
inputMode="decimal"
/>
</FormField>
</div>
</SpaceBetween>

<ScreenshotArea
key={`themed-${themed}-${strokeSmall}-${strokeNormal}-${strokeMedium}-${strokeBig}-${strokeLarge}`}
>
<SpaceBetween size="xl">
<Box variant="h2" padding={{ top: 'l' }}>
Icon sizes
</Box>

<SpaceBetween size="xs">
<Box variant="h3">Small (16px with shorter wrapper size)</Box>
<div className={styles.wrapper}>
{Object.keys(icons).map(icon => (
<Icon key={icon} name={icon as IconProps['name']} variant="normal" size="small" />
))}
</div>
</SpaceBetween>

<SpaceBetween size="xs">
<Box variant="h3">Normal (16px)</Box>
<div className={styles.wrapper}>
{Object.keys(icons).map(icon => (
<Icon key={icon} name={icon as IconProps['name']} variant="normal" size="normal" />
))}
</div>
</SpaceBetween>

<SpaceBetween size="xs">
<Box variant="h3">Medium (20px)</Box>
<div className={styles.wrapper}>
{Object.keys(icons).map(icon => (
<Icon key={icon} name={icon as IconProps['name']} variant="normal" size="medium" />
))}
</div>
</SpaceBetween>

<SpaceBetween size="xs">
<Box variant="h3">Big (32px)</Box>
<div className={styles.wrapper}>
{Object.keys(icons).map(icon => (
<Icon key={icon} name={icon as IconProps['name']} variant="normal" size="big" />
))}
</div>
</SpaceBetween>

<SpaceBetween size="xs">
<Box variant="h3">Large (48px)</Box>
<div className={styles.wrapper}>
{Object.keys(icons).map(icon => (
<Icon key={icon} name={icon as IconProps['name']} variant="normal" size="large" />
))}
</div>
</SpaceBetween>

<Box variant="h2" padding={{ top: 'l' }}>
Inline Context Examples
</Box>

<SpaceBetween size="m">
<div>
<Button iconName="call" variant="primary">
Button
</Button>
</div>

<div>
<Link external={true} href="https://example.com/" variant="primary">
Learn more
</Link>
</div>

<div style={{ maxWidth: '300px' }}>
<Select
selectedOption={selectedOption}
onChange={({ detail }) => setSelectedOption(detail.selectedOption)}
options={[
{ label: 'Option 1', value: '1', iconName: 'settings' },
{ label: 'Option 2', value: '2', iconName: 'unlocked' },
{ label: 'Option 3', value: '3', iconName: 'share' },
]}
/>
</div>

<div style={{ maxWidth: '300px' }}>
<FormField
label="Email address"
errorText="Please enter a valid email address"
constraintText="Use your company email"
>
<Input value="invalid-email" type="email" readOnly={true} />
</FormField>
</div>

<SpaceBetween size="xs">
<StatusIndicator type="error">Error</StatusIndicator>
<StatusIndicator type="success">Success</StatusIndicator>
<StatusIndicator type="warning">Warning</StatusIndicator>
<StatusIndicator type="info">Info</StatusIndicator>
</SpaceBetween>
<IconProvider
icons={{
'status-positive': (
<svg viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 9L7 11L11 7" />
<path d="M8 2C6.29 3.53 4.13 4.32 2 4.48V7.96C2 10.01 2.76 11.84 3.71 13.1C4.63 14.32 6.03 15.31 8 16C9.97 15.31 11.37 14.32 12.29 13.1C13.3935 11.6128 13.9926 9.81183 14 7.96V4.48C11.87 4.32 9.71 3.52 8 2Z" />
</svg>
),
}}
>
<SpaceBetween size="xs">
<Box variant="h2" padding={{ top: 'l' }}>
Custom icon using icon-provider component
</Box>
<KeyValuePairs
columns={1}
items={[
{
label: 'Custom icon in a status indicator',
value: <StatusIndicator type="success">Two-factor authentication enabled</StatusIndicator>,
},
{
label: 'Custom icon in an alert',
value: (
<Alert type="success" statusIconAriaLabel="Success">
Two-factor authentication enabled successfully.
</Alert>
),
},
{
label: 'Custom icon in a flashbar',
value: (
<Flashbar
items={[
{
type: 'success',
content: 'Two-factor authentication enabled successfully.',
statusIconAriaLabel: 'Success',
},
]}
/>
),
},
]}
/>
</SpaceBetween>
</IconProvider>
<p style={{ lineHeight: '28px', fontSize: '18px' }}>
Lorem ipsum dolor sit amet,{' '}
<Link external={true} href="#">
Learn more
</Link>{' '}
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute
irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum
</p>
</SpaceBetween>
</SpaceBetween>
</ScreenshotArea>
</div>
);
}
Loading
Loading