-
Notifications
You must be signed in to change notification settings - Fork 156
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: Header responsiveness explorations
- Loading branch information
Showing
2 changed files
with
371 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,229 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
/* eslint-disable react/jsx-key */ | ||
import React, { useRef, useState } from 'react'; | ||
import clsx from 'clsx'; | ||
import Box from '~components/box'; | ||
import Button from '~components/button'; | ||
import ButtonDropdown from '~components/button-dropdown'; | ||
import Container from '~components/container'; | ||
import Header, { HeaderProps } from '~components/header'; | ||
import Link from '~components/link'; | ||
import SpaceBetween from '~components/space-between'; | ||
import ScreenshotArea from '../utils/screenshot-area'; | ||
import styles from './styles.scss'; | ||
import { useResizeObserver } from '~components/internal/hooks/container-queries'; | ||
|
||
type Approach = 'current' | 'grid' | 'flex' | 'cquery' | 'resize-observer'; | ||
|
||
type DemoHeaderProps = Pick<HeaderProps, 'children' | 'actions' | 'description' | 'info'>; | ||
|
||
const permutations: Array<Partial<HeaderProps>> = [ | ||
{ | ||
children: 'Simple container header', | ||
description: ( | ||
<span> | ||
Fairly short description for this container. Not much to see here except for{' '} | ||
<Link href="#" variant="secondary"> | ||
a link | ||
</Link> | ||
. | ||
</span> | ||
), | ||
actions: <Button>One action</Button>, | ||
}, | ||
{ | ||
children: 'Tags (4)', | ||
info: <Link variant="info">Info</Link>, | ||
description: | ||
'A tag is a label that you assign to an AWS resource. Each tag consists of a key and an optional value. You can use tags to search and filter your resources or track your AWS costs.', | ||
actions: <Button>Manage tags</Button>, | ||
}, | ||
{ | ||
children: 'Snapshots', | ||
description: 'Select at least one snapshot to perform an action.', | ||
actions: ( | ||
<SpaceBetween direction="horizontal" size="xs"> | ||
<Button iconName="refresh" ariaLabel="Refresh snapshots" /> | ||
<ButtonDropdown items={[]}>Actions</ButtonDropdown> | ||
<Button variant="primary">Create snapshot</Button> | ||
</SpaceBetween> | ||
), | ||
}, | ||
{ | ||
children: 'Workgroups', | ||
description: | ||
'Use workgroups to separate users, teams, applications, workloads, and to set limits on amount of data for each query or the entire workgroup process. You can also view query-related metrics in AWS CloudWatch.', | ||
actions: ( | ||
<SpaceBetween direction="horizontal" size="xs"> | ||
<ButtonDropdown items={[]}>Actions</ButtonDropdown> | ||
<Button variant="primary">Create workgroup</Button> | ||
</SpaceBetween> | ||
), | ||
}, | ||
{ | ||
children: 'Access Points (100)', | ||
info: <Link variant="info">Info</Link>, | ||
description: ( | ||
<span> | ||
Amazon S3 Access Points simplify managing data access at scale for shared datasets in S3. Access points are | ||
named network endpoints that are attached to buckets that you can use to perform S3 object operations. An Access | ||
Point alias provides the same functionality as an Access Point ARN and can be substituted for use anywhere an S3 | ||
bucket name is normally used for data access.{' '} | ||
<Link href="#" variant="secondary" external={true} ariaLabel="Learn more about access points"> | ||
Learn more | ||
</Link> | ||
</span> | ||
), | ||
actions: ( | ||
<SpaceBetween direction="horizontal" size="xs"> | ||
<Button iconName="copy">Copy Access Point alias</Button> | ||
<Button iconName="copy">Copy ARN</Button> | ||
<Button>Edit policy</Button> | ||
<Button>Delete</Button> | ||
<Button variant="primary">Create access point</Button> | ||
</SpaceBetween> | ||
), | ||
}, | ||
]; | ||
|
||
function HeaderGrid({ children, actions, description }: DemoHeaderProps) { | ||
return ( | ||
<div className={styles['header-grid']}> | ||
<SpaceBetween direction="horizontal" size="xs" className={styles['header-grid--title']}> | ||
<Box variant="h2">{children}</Box> | ||
</SpaceBetween> | ||
<Box variant="p" color="text-body-secondary" className={styles['header-grid--description']}> | ||
{description} | ||
</Box> | ||
<div className={styles['header-grid--actions']}>{actions}</div> | ||
</div> | ||
); | ||
} | ||
|
||
function HeaderFlex({ children, actions, description }: DemoHeaderProps) { | ||
return ( | ||
<div className={styles['header-flex']}> | ||
<div className={styles['header-flex--heading']}> | ||
<SpaceBetween direction="horizontal" size="xs" className={styles['header-flex--title']}> | ||
<Box variant="h2">{children}</Box> | ||
</SpaceBetween> | ||
<Box variant="p" color="text-body-secondary" className={styles['header-flex--description']}> | ||
{description} | ||
</Box> | ||
</div> | ||
<div className={styles['header-flex--actions']}>{actions}</div> | ||
</div> | ||
); | ||
} | ||
|
||
function HeaderContainerQuery({ children, actions, description }: DemoHeaderProps) { | ||
return ( | ||
<div className={styles['header-cquery-wrapper']}> | ||
<div className={styles['header-cquery']}> | ||
<Box variant="h2" className={styles['header-cquery--title']}> | ||
{children} | ||
</Box> | ||
<Box variant="p" color="text-body-secondary" className={styles['header-cquery--description']}> | ||
{description} | ||
</Box> | ||
<div className={styles['header-cquery--actions']}>{actions}</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
function HeaderResizeObserver({ children, actions, description }: DemoHeaderProps) { | ||
const ref = useRef(null); | ||
const titleRef = useRef<HTMLDivElement>(null); | ||
const actionsRef = useRef<HTMLDivElement>(null); | ||
const [actionsOverlap, setActionsOverlap] = useState(false); | ||
|
||
useResizeObserver(ref, entry => { | ||
console.log('resize!', entry); | ||
if (titleRef?.current && actionsRef?.current) { | ||
const titleRect = titleRef.current.getBoundingClientRect(); | ||
const actionsRect = actionsRef.current.getBoundingClientRect(); | ||
const distance = actionsRect.x - (titleRect.x + titleRect.width); | ||
|
||
console.log(titleRect, actionsRect); | ||
console.log('distance:', distance); | ||
|
||
setActionsOverlap(distance > 50 || distance <= 0); | ||
} | ||
}); | ||
return ( | ||
<div className={styles['header-grid-wrapper']} ref={ref}> | ||
<div className={clsx(styles['header-grid'], actionsOverlap && styles['header-grid--overlap'])}> | ||
<Box variant="h2" className={styles['header-grid--title']}> | ||
<div ref={titleRef}>{children}</div> | ||
</Box> | ||
<Box variant="p" color="text-body-secondary" className={styles['header-grid--description']}> | ||
{description} | ||
</Box> | ||
<div ref={actionsRef} className={styles['header-grid--actions']}> | ||
{actions} | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
function renderHeader(approach: Approach, props: Partial<HeaderProps>) { | ||
switch (approach) { | ||
case 'grid': | ||
return <HeaderGrid {...props} />; | ||
case 'flex': | ||
return <HeaderFlex {...props} />; | ||
case 'cquery': | ||
return <HeaderContainerQuery {...props} />; | ||
case 'resize-observer': | ||
return <HeaderResizeObserver {...props} />; | ||
case 'current': | ||
default: | ||
return <Header {...props} />; | ||
} | ||
} | ||
|
||
const approachDescription: Record<Approach, string> = { | ||
current: 'Current implementation', | ||
grid: 'CSS Grid that places actions in the top right', | ||
flex: 'Similar to current approach but with different flows', | ||
cquery: 'Like grid, but rearranges actions when container is small', | ||
'resize-observer': 'not working', | ||
}; | ||
|
||
function Showcase({ approach }: { approach: Approach }) { | ||
return ( | ||
<div className={styles.showcase}> | ||
<div> | ||
<h2>Approach: {approach}</h2> | ||
<p>{approachDescription[approach]}</p> | ||
</div> | ||
|
||
{permutations.map((props, i) => { | ||
return ( | ||
<Container key={i} header={renderHeader(approach, props)}> | ||
Container content | ||
</Container> | ||
); | ||
})} | ||
</div> | ||
); | ||
} | ||
|
||
export default function PageHeadersDemo() { | ||
return ( | ||
<ScreenshotArea> | ||
<Box padding="l"> | ||
<div className={styles.playground}> | ||
<Showcase approach="current" /> | ||
{/* <Showcase approach="resize-observer" /> */} | ||
<Showcase approach="grid" /> | ||
<Showcase approach="cquery" /> | ||
<Showcase approach="flex" /> | ||
</div> | ||
</Box> | ||
</ScreenshotArea> | ||
); | ||
} |
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,142 @@ | ||
/* | ||
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
/* stylelint-disable */ | ||
|
||
@use '~design-tokens' as awsui; | ||
|
||
.playground { | ||
display: grid; | ||
// grid-template-columns: repeat(auto-fit, minmax(600px, 1fr)); | ||
grid-template-columns: repeat(1, 1fr); | ||
column-gap: awsui.$space-scaled-l; | ||
} | ||
|
||
.showcase { | ||
display: grid; | ||
|
||
grid-template-columns: 1fr; | ||
grid-template-rows: auto; | ||
grid-row: span 6; | ||
row-gap: awsui.$space-scaled-s; | ||
// grid-row: 1 / 100; | ||
|
||
grid-template-columns: subgrid; | ||
grid-template-rows: subgrid; | ||
} | ||
|
||
///// AUTOFLOW APPROACH (doesnt work well) | ||
// .header-grid { | ||
// display: grid; | ||
// grid-auto-flow: dense; | ||
// grid-auto-columns: 1fr minmax(auto, 75%); | ||
// column-gap: awsui.$space-scaled-xs; | ||
// row-gap: awsui.$space-scaled-xs; | ||
|
||
// > .header-grid--description { | ||
// grid-column-end: span 2; | ||
// } | ||
// } | ||
|
||
////// GRID AREAS APPROACH | ||
.header-grid { | ||
display: grid; | ||
grid-template-areas: | ||
'title actions' | ||
'description description'; | ||
grid-template-columns: max-content 1fr; | ||
column-gap: awsui.$space-scaled-xs; | ||
row-gap: awsui.$space-scaled-xs; | ||
|
||
> .header-grid--title { | ||
grid-area: title; | ||
} | ||
> .header-grid--description { | ||
grid-area: description; | ||
} | ||
> .header-grid--actions { | ||
grid-area: actions; | ||
justify-self: flex-end; | ||
} | ||
&.header-grid--overlap { | ||
// For the resize observer approach | ||
// grid-template-areas: | ||
// 'title' | ||
// 'description' | ||
// 'actions'; | ||
display: block; | ||
grid-template-columns: 1fr; | ||
|
||
> .header-cquery--actions { | ||
justify-self: flex-start; | ||
} | ||
} | ||
} | ||
|
||
////// FLEXBOX APPROACH | ||
.header-flex { | ||
display: flex; | ||
flex-wrap: wrap; | ||
justify-content: space-between; | ||
|
||
column-gap: awsui.$space-scaled-xs; | ||
|
||
container-type: inline-size; | ||
} | ||
.header-flex--heading { | ||
display: flex; | ||
flex-direction: column; | ||
flex-basis: 60%; | ||
flex-grow: 1; | ||
flex-shrink: 1; | ||
} | ||
.header-flex--actions { | ||
display: flex; | ||
align-items: flex-start; | ||
} | ||
@container (max-width: 760px) { | ||
.header-flex--heading { | ||
flex-basis: auto; | ||
} | ||
} | ||
|
||
////// CONTAINER QUERY APPROACH | ||
.header-cquery-wrapper { | ||
container-type: inline-size; | ||
} | ||
.header-cquery { | ||
display: grid; | ||
grid-template-areas: | ||
'title actions' | ||
'description description'; | ||
grid-template-columns: 1fr auto; | ||
column-gap: awsui.$space-scaled-xs; | ||
row-gap: awsui.$space-scaled-xs; | ||
|
||
> .header-cquery--title { | ||
grid-area: title; | ||
} | ||
> .header-cquery--description { | ||
grid-area: description; | ||
} | ||
> .header-cquery--actions { | ||
grid-area: actions; | ||
justify-self: flex-end; | ||
} | ||
} | ||
@container (max-width: 900px) { | ||
.header-cquery { | ||
grid-template-areas: | ||
'title' | ||
'description' | ||
'actions'; | ||
|
||
> .header-cquery--actions { | ||
justify-self: flex-start; | ||
} | ||
} | ||
} | ||
|
||
/* stylelint-enable */ |