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
10 changes: 7 additions & 3 deletions pages/common/heroshot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ interface HeroshotProps {
/** Caption rendered above the frame, outside of the captured area. */
label?: string;
/**
* Vertical placement of the content inside the frame. Content is centered by default; use `start`
* only when it is taller than the frame, so the crop keeps the top instead of cutting both ends.
* Vertical placement of the content inside the frame. Content is centered by default, and content
* taller than the frame is already top-aligned automatically, so `start` is only needed to anchor
* content that fits — for example a trigger whose dropdown hangs below it and needs the room.
*/
align?: 'center' | 'start';
/** Inset between the frame edges and the content. Set to 0 for compositions that bleed to the edges. */
Expand Down Expand Up @@ -100,7 +101,10 @@ export function Heroshot({
outline: `1px dashed ${colorBorderDividerDefault}`,
display: 'flex',
flexDirection: 'column',
justifyContent: align === 'center' ? 'center' : 'flex-start',
// `safe` makes the centering degrade to `start` as soon as the content is taller than the
// frame, so an overflowing preview keeps its top (and the frame's top padding) and is
// cropped at the bottom only, instead of being clipped at both ends.
justifyContent: align === 'center' ? 'safe center' : 'flex-start',
alignItems: stretch ? 'stretch' : 'center',
}}
>
Expand Down
214 changes: 175 additions & 39 deletions pages/heroshot/overview.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
ColumnLayout,
Container,
ContentLayout,
ControlGroup,
CopyToClipboard,
DateInput,
DatePicker,
Expand Down Expand Up @@ -134,10 +135,12 @@ export default function HeroshotOverviewPage() {
</Alert>
</Heroshot>

<Heroshot id="anchor-navigation" label="AnchorNavigation" stretch={true}>
<div style={{ transform: `translateX(100px)` }}>
<AnchorNavigation activeHref="#overview" anchors={anchors} />
</div>
{/*
Not stretched: the navigation sizes to its own labels and the frame centers it, instead of
filling the frame width and needing a hardcoded offset to look placed.
*/}
<Heroshot id="anchor-navigation" label="AnchorNavigation">
<AnchorNavigation activeHref="#overview" anchors={anchors} />
</Heroshot>

<Heroshot id="annotation-context" label="AnnotationContext" padding={0} contentSize={ANNOTATION_CONTENT_SIZE}>
Expand Down Expand Up @@ -180,10 +183,15 @@ export default function HeroshotOverviewPage() {
/>
</Heroshot>

<Heroshot id="attribute-editor" label="AttributeEditor" align="start" stretch={true}>
<Heroshot id="attribute-editor" label="AttributeEditor" stretch={true} contentSize={ATTRIBUTE_EDITOR_SIZE}>
<AttributeEditor
addButtonText="Add tag"
removeButtonText="Remove"
// At the frame width the component would pick its narrowest layout and stack the key over
// the value, which is tall enough that the add button falls outside the frame. This is the
// same side-by-side layout it uses from the `xs` breakpoint up, pinned so that the whole
// row — including both buttons — fits the frame at full size and needs no scaling.
gridLayout={[{ rows: [[1, 1]], removeButton: { ownRow: true } }]}
items={[{ key: 'Environment', value: 'Production' }]}
definition={[
{ label: 'Key', control: item => <Input value={item.key} readOnly={true} onChange={noop} /> },
Expand Down Expand Up @@ -315,7 +323,7 @@ export default function HeroshotOverviewPage() {
</SpaceBetween>
</Heroshot>

<Heroshot id="code-editor" label="CodeEditor" stretch={true} padding={8}>
<Heroshot id="code-editor" label="CodeEditor" stretch={true} padding={8} contentSize={CODE_EDITOR_SIZE}>
<CodeEditorHeroshot />
</Heroshot>

Expand Down Expand Up @@ -351,12 +359,48 @@ export default function HeroshotOverviewPage() {
</Container>
</Heroshot>

<Heroshot id="content-layout" label="ContentLayout" stretch={true}>
<ContentLayout header={<Header variant="h1">Instances</Header>}>
<Container>Content</Container>
<Heroshot id="content-layout" label="ContentLayout" padding={0} contentSize={CONTENT_LAYOUT_SIZE}>
{/*
The high-contrast header bleeds to the edges of its own layout, so the frame contributes no
padding of its own and the layout's `defaultPadding` insets the content instead.
*/}
<ContentLayout
defaultPadding={true}
headerVariant="high-contrast"
header={
<Header
variant="h1"
info={<Link variant="info">Info</Link>}
description="This is a generic description used in the header."
>
Header
</Header>
}
>
<Container header={<Header variant="h2">Content heading</Header>}>Content</Container>
</ContentLayout>
</Heroshot>

{/* Not stretched, so the frame centers the group instead of pinning it to the frame width. */}
<Heroshot id="control-group" label="ControlGroup">
<ControlGroup
inlineLabelText="Metric"
// The frame is narrower than the group's auto-layout threshold, so the direction is pinned
// to keep the controls adjoined on one line, which is what distinguishes the component.
direction="horizontal"
actionButton={{ iconName: 'remove', ariaLabel: 'Remove metric' }}
>
{/* A short statistic: at the frame width a longer one is truncated mid-word. */}
<Select
ariaLabel="Statistic"
selectedOption={{ label: 'Sum', value: 'sum' }}
options={[]}
onChange={noop}
/>
<Input ariaLabel="Metric name" value="CPUUtilization" onChange={noop} />
</ControlGroup>
</Heroshot>

<Heroshot id="copy-to-clipboard" label="CopyToClipboard">
<CopyToClipboard
variant="inline"
Expand Down Expand Up @@ -493,22 +537,33 @@ export default function HeroshotOverviewPage() {
/>
</Heroshot>

<Heroshot id="form" label="Form" align="start" stretch={true}>
<Form
header={<Header variant="h1">Create instance</Header>}
actions={
<SpaceBetween size="xs" direction="horizontal">
<Button>Cancel</Button>
<Button variant="primary">Create</Button>
<Heroshot id="form" label="Form" stretch={true} contentSize={FORM_CONTENT_SIZE}>
{/*
Content only, without the form's header and actions: at the frame size the whole form is
twice as tall as the frame, and shrinking it to fit leaves the fields too small to read.
The fields are shorter than the scaled content box, so they are centered in it to sit in
the middle of the frame instead of at its top.
*/}
<div style={{ blockSize: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
<Form>
<SpaceBetween size="m">
<FormField label="Instance type">
<Select selectedOption={{ label: 't3.medium', value: 't3.medium' }} options={[]} onChange={noop} />
</FormField>
<FormField label="Purchase option">
<Tiles
value="on-demand"
onChange={noop}
columns={2}
items={[
{ value: 'on-demand', label: 'On-demand' },
{ value: 'spot', label: 'Spot' },
]}
/>
</FormField>
</SpaceBetween>
}
>
<Container>
<FormField label="Name">
<Input value="" onChange={noop} />
</FormField>
</Container>
</Form>
</Form>
</div>
</Heroshot>

<Heroshot id="form-field" label="FormField" stretch={true}>
Expand Down Expand Up @@ -650,8 +705,8 @@ export default function HeroshotOverviewPage() {
/>
</Heroshot>

<Heroshot id="modal" label="Modal" padding={0} contentSize={OVERLAY_CONTENT_SIZE}>
<IframeWrapper id="heroshot-modal" size={OVERLAY_CONTENT_SIZE} AppComponent={ModalHeroshot} />
<Heroshot id="modal" label="Modal" padding={0} contentSize={MODAL_CONTENT_SIZE}>
<IframeWrapper id="heroshot-modal" size={MODAL_CONTENT_SIZE} AppComponent={ModalHeroshot} />
</Heroshot>

<Heroshot id="multiselect" label="Multiselect" stretch={true}>
Expand Down Expand Up @@ -861,15 +916,21 @@ export default function HeroshotOverviewPage() {
<IframeWrapper id="heroshot-split-panel" size={LAYOUT_CONTENT_SIZE} AppComponent={SplitPanelHeroshot} />
</Heroshot>

<Heroshot id="status-indicator" label="StatusIndicator">
<SpaceBetween size="xs">
<StatusIndicator type="success">Running</StatusIndicator>
<StatusIndicator type="in-progress">Pending</StatusIndicator>
<StatusIndicator type="error">Failed</StatusIndicator>
</SpaceBetween>
<Heroshot id="status-indicator" label="StatusIndicator" stretch={true}>
<Container>
<SpaceBetween size="m" direction="horizontal">
<StatusIndicator type="success">Running</StatusIndicator>
<StatusIndicator type="warning">Degraded</StatusIndicator>
<StatusIndicator type="error">Failed</StatusIndicator>
</SpaceBetween>
</Container>
</Heroshot>

<Heroshot id="steps" label="Steps" stretch={true}>
{/*
Not stretched: the steps are as wide as their longest label, so letting the list size to its
own content lets the frame center the visible block instead of pinning it to the left edge.
*/}
<Heroshot id="steps" label="Steps">
<Steps
steps={[
{ status: 'success', header: 'Validated template' },
Expand Down Expand Up @@ -996,7 +1057,8 @@ export default function HeroshotOverviewPage() {
/>
</Heroshot>

<Heroshot id="tree-view" label="TreeView" stretch={true}>
{/* Not stretched, for the same reason as the steps heroshot: the frame centers the tree. */}
<Heroshot id="tree-view" label="TreeView">
<TreeView
ariaLabel="Resources"
items={treeItems}
Expand Down Expand Up @@ -1267,6 +1329,37 @@ const previewTutorialPanelStrings = {
const ANNOTATION_CONTENT_SIZE = { width: 350, height: 172 };
const HOTSPOT_CONTENT_SIZE = { width: 300, height: 147 };
const OVERLAY_CONTENT_SIZE = { width: 800, height: 393 };
/**
* Narrower viewport than {@link OVERLAY_CONTENT_SIZE}, so the modal — whose own width is capped by its
* `medium` size rather than by the viewport — is scaled down less and reads larger in the frame. Keeps
* the 346:170 frame ratio so nothing is letterboxed.
*/
const MODAL_CONTENT_SIZE = { width: 700, height: 344 };
/**
* Even in its side-by-side layout the editor is a little taller than the frame, so it is laid out
* slightly larger and scaled down (~0.75) to fit whole — the add button included — rather than having
* its bottom row cropped.
*/
const ATTRIBUTE_EDITOR_SIZE = { width: 420, height: 185 };
/**
* Tiles only lay out side by side from a container width of 465px, so the form is laid out above that
* and scaled down (~0.65). At the frame width the tiles would stack instead, which is tall enough that
* the second one falls outside the frame.
*/
const FORM_CONTENT_SIZE = { width: 480, height: 211 };
/**
* The layout reads as a page, so it is laid out at a page-like width and scaled down (~0.7) — wide
* enough that the header keeps its description on one line and the action beside the title, rather
* than wrapping into the frame-width stack it would fall back to. Matches the 346:170 frame ratio,
* which the frame fills edge to edge because the high-contrast header bleeds to its own edges.
*/
const CONTENT_LAYOUT_SIZE = { width: 420, height: 206 };
/**
* A slight scale-down (~0.88) only, so the code stays legible. Both dimensions are above the padded
* content box (330x154) by the same factor, and the editor is still taller than the box at that scale,
* so the frame keeps cropping its bottom.
*/
const CODE_EDITOR_SIZE = { width: 375, height: 175 };

const customSettingsIcon = (
<svg viewBox="0 0 16 16" focusable={false}>
Expand Down Expand Up @@ -1309,6 +1402,20 @@ function HotspotHeroshot() {
);
}

/**
* Enough lines to fill the taller editor, so the extra height shows code rather than empty gutter
* rows. Kept short enough to fit the frame width, so the numbered lines stay one to a row instead of
* wrapping into unnumbered continuations.
*/
const editorSource = `const region = 'us-east-1';
const instanceType = 't3.medium';
const volumeSize = 20;

const instanceName = 'web-1';
const environment = 'production';

const client = new EC2Client();`;

function CodeEditorHeroshot() {
const [ace, setAce] = React.useState<any>();

Expand All @@ -1334,9 +1441,12 @@ function CodeEditorHeroshot() {
return (
<CodeEditor
ace={ace}
value="const region = 'us-east-1';"
value={editorSource}
language="javascript"
editorContentHeight={80}
// Taller than the frame on purpose: the editor is a page-sized component, so the frame crops its
// bottom — the status bar and the pane below it — and the thumbnail reads as a detail of a real
// editor rather than as a squat one.
editorContentHeight={150}
loading={!ace}
i18nStrings={codeEditorI18nStrings}
themes={codeEditorThemes}
Expand Down Expand Up @@ -1366,17 +1476,43 @@ function useUnlockedGalleryScroll() {
}, []);
}

/**
* Shows the delete-with-simple-confirmation pattern: the consequence spelled out in the body, the
* wider impact in an info alert, and the destructive action as the primary button in the footer.
*/
function ModalHeroshot() {
useUnlockedGalleryScroll();

return (
<Modal
visible={true}
header="Delete instance"
header="Delete distributions"
closeAriaLabel="Close modal"
footer={<Button variant="primary">Delete</Button>}
footer={
<Box float="right">
<SpaceBetween size="xs" direction="horizontal">
<Button variant="link">Cancel</Button>
<Button variant="primary">Delete</Button>
</SpaceBetween>
</Box>
}
>
This action cannot be undone.
<SpaceBetween size="m">
<Box variant="span">
Permanently delete{' '}
<Box variant="span" fontWeight="bold">
5 distributions
</Box>
? You can&apos;t undo this action.
</Box>
<Alert type="info">
Proceeding with this action will delete the distributions with all their content and can affect related
resources.{' '}
<Link external={true} href="#">
Learn more
</Link>
</Alert>
</SpaceBetween>
</Modal>
);
}
Expand Down
Loading
Loading