({
+ tokens: [],
+ operation: 'and',
+ });
+ const [modalVisible, setModalVisible] = React.useState(false);
+ return (
+
+ Buttons, inputs, and dropdowns
+
+
+
+
+
+
+
+ setQuery(detail)}
+ countText="5 matches"
+ enableTokenGroups={true}
+ expandToViewport={true}
+ filteringAriaLabel="Find distributions"
+ filteringOptions={[
+ {
+ propertyKey: 'instanceid',
+ value: 'i-2dc5ce28a0328391',
+ },
+ {
+ propertyKey: 'instanceid',
+ value: 'i-d0312e022392efa0',
+ },
+ {
+ propertyKey: 'instanceid',
+ value: 'i-070eef935c1301e6',
+ },
+ {
+ propertyKey: 'instanceid',
+ value: 'i-3b44795b1fea36ac',
+ },
+ { propertyKey: 'state', value: 'Stopped' },
+ { propertyKey: 'state', value: 'Stopping' },
+ { propertyKey: 'state', value: 'Pending' },
+ { propertyKey: 'state', value: 'Running' },
+ {
+ propertyKey: 'instancetype',
+ value: 't3.small',
+ },
+ {
+ propertyKey: 'instancetype',
+ value: 't2.small',
+ },
+ { propertyKey: 'instancetype', value: 't3.nano' },
+ {
+ propertyKey: 'instancetype',
+ value: 't2.medium',
+ },
+ {
+ propertyKey: 'instancetype',
+ value: 't3.medium',
+ },
+ {
+ propertyKey: 'instancetype',
+ value: 't2.large',
+ },
+ { propertyKey: 'instancetype', value: 't2.nano' },
+ {
+ propertyKey: 'instancetype',
+ value: 't2.micro',
+ },
+ {
+ propertyKey: 'instancetype',
+ value: 't3.large',
+ },
+ {
+ propertyKey: 'instancetype',
+ value: 't3.micro',
+ },
+ { propertyKey: 'averagelatency', value: '17' },
+ { propertyKey: 'averagelatency', value: '53' },
+ { propertyKey: 'averagelatency', value: '73' },
+ { propertyKey: 'averagelatency', value: '74' },
+ { propertyKey: 'averagelatency', value: '107' },
+ { propertyKey: 'averagelatency', value: '236' },
+ { propertyKey: 'averagelatency', value: '242' },
+ { propertyKey: 'averagelatency', value: '375' },
+ { propertyKey: 'averagelatency', value: '402' },
+ { propertyKey: 'averagelatency', value: '636' },
+ { propertyKey: 'averagelatency', value: '639' },
+ { propertyKey: 'averagelatency', value: '743' },
+ { propertyKey: 'averagelatency', value: '835' },
+ { propertyKey: 'averagelatency', value: '981' },
+ { propertyKey: 'averagelatency', value: '995' },
+ ]}
+ filteringPlaceholder="Find distributions"
+ filteringProperties={[
+ {
+ key: 'instanceid',
+ operators: ['=', '!=', ':', '!:', '^', '!^'],
+ propertyLabel: 'Instance ID',
+ groupValuesLabel: 'Instance ID values',
+ },
+ {
+ key: 'state',
+ operators: [
+ { operator: '=', tokenType: 'enum' },
+ { operator: '!=', tokenType: 'enum' },
+ ':',
+ '!:',
+ '^',
+ '!^',
+ ],
+ propertyLabel: 'State',
+ groupValuesLabel: 'State values',
+ },
+ {
+ key: 'instancetype',
+ operators: [
+ { operator: '=', tokenType: 'enum' },
+ { operator: '!=', tokenType: 'enum' },
+ ':',
+ '!:',
+ '^',
+ '!^',
+ ],
+ propertyLabel: 'Instance type',
+ groupValuesLabel: 'Instance type values',
+ },
+ {
+ key: 'averagelatency',
+ operators: ['=', '!=', '>', '<', '<=', '>='],
+ propertyLabel: 'Average latency',
+ groupValuesLabel: 'Average latency values',
+ },
+ ]}
+ />
+
+
+
+ To further customize spaces/sizes for specific components, the Core’s StyleAPI can be used. Refer to
+ the{' '}
+
+ implementation guidelines
+
+ >
+ }
+ >
+ Extra small size for key atomic components using StyleAPI
+
+ }
+ variant="stacked"
+ >
+
+
+
+
+ Secondary button
+
+
+ Tertiary button
+
+
+
+ {/*
+ ["like", "dislike"].includes(detail.id) &&
+ setFeedback(detail.pressed ? detail.id : "")
+ }
+ style={{
+ root: {
+ paddingBlock: "0px",
+ paddingInline: "2px",
+ },
+ item: {
+
+ }
+ }}
+ ariaLabel="Main navigation"
+ items={[
+ {
+ type: "icon-button",
+ id: "view-full",
+ text: "View",
+ iconName: "view-full"
+ },
+ {
+ type: "icon-button",
+ id: "folder",
+ text: "Folder",
+ iconName: "folder"
+ },
+ {
+ type: "icon-button",
+ id: "status-positive",
+ text: "Analytics",
+ iconName: "status-positive"
+ }
+ ]}
+ variant="icon"
+ /> */}
+
+ {/*
+ setValue(detail.value)}
+ value={value}
+ placeholder="Enter resource policy"
+ />
+ setSelectedOption(detail.selectedOption)}
+ options={[
+ { label: 'US East (N. Virginia)', value: 'us-east-1', description: 'us-east-1', tags: ['Recommended'] },
+ { label: 'US West (Oregon)', value: 'us-west-2', description: 'us-west-2' },
+ { label: 'Europe (Ireland)', value: 'eu-west-1', description: 'eu-west-1', labelTag: 'New' },
+ ]}
+ placeholder="Select a region"
+ renderOption={
+ ({ item, filterText }) => {
+ if (
+ item.type === "group" ||
+ item.type === "item"
+ ) {
+ return (
+
+
+
+ {item.option.label}
+
+
+ {item.option.description}
+
+
+
+ {item.option.tags?.map(tag => (
+ {tag}
+ ))}
+
+
+ );
+ } else if (item.type === "trigger") {
+ return (
+
+
+
+ {item.option.label}
+
+
+ {item.option.description}
+
+
+
+
+
+
+ );
+ }
+ return null;
+ }
+ }
+ />
+ */}
+
+
+
+
+
+
+
+ Actions
+
+
+ Primary dropdown
+
+
+ Disabled dropdown
+
+
+
+
+
+ High memory
+
+
+ Normal latency
+
+
+
+
+
+
+
+ setModalVisible(true)}>Open modal
+ setModalVisible(false)}
+ header="Delete resource"
+ footer={
+
+
+ setModalVisible(false)}>
+ Cancel
+
+ setModalVisible(false)}>
+ Delete
+
+
+
+ }
+ >
+ Are you sure you want to delete this resource? This action cannot be undone.
+
+
+
+
+
+
+ );
+}
diff --git a/pages/demos/pages/components-overview/charts.tsx b/pages/demos/pages/components-overview/charts.tsx
new file mode 100644
index 0000000000..bc973726b2
--- /dev/null
+++ b/pages/demos/pages/components-overview/charts.tsx
@@ -0,0 +1,220 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import BarChart from '@cloudscape-design/components/bar-chart';
+import Box from '@cloudscape-design/components/box';
+import Button from '@cloudscape-design/components/button';
+import ColumnLayout from '@cloudscape-design/components/column-layout';
+import LineChart from '@cloudscape-design/components/line-chart';
+import PieChart from '@cloudscape-design/components/pie-chart';
+
+import { Section, SubSection } from './utils';
+
+const formatLargeNumber = (value: number): string => {
+ if (Math.abs(value) >= 1e9) {
+ return (value / 1e9).toFixed(1).replace(/\.0$/, '') + 'G';
+ }
+ if (Math.abs(value) >= 1e6) {
+ return (value / 1e6).toFixed(1).replace(/\.0$/, '') + 'M';
+ }
+ if (Math.abs(value) >= 1e3) {
+ return (value / 1e3).toFixed(1).replace(/\.0$/, '') + 'K';
+ }
+ return value.toFixed(2);
+};
+
+const formatDateTick = (date: Date): string =>
+ date
+ .toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', hour12: false })
+ .split(',')
+ .join('\n');
+
+const emptyState = (
+
+ No data available
+
+ There is no data available
+
+
+);
+
+const noMatchState = (
+
+ No matching data
+
+ There is no matching data to display
+
+ Clear filter
+
+);
+
+const barChartDates = [
+ new Date(1601071200000),
+ new Date(1601078400000),
+ new Date(1601085600000),
+ new Date(1601092800000),
+ new Date(1601100000000),
+];
+
+const lineChartDomain: [Date, Date] = [new Date(1600984800000), new Date(1601013600000)];
+
+const lineChartSite1 = [
+ { x: new Date(1600984800000), y: 58020 },
+ { x: new Date(1600985700000), y: 102402 },
+ { x: new Date(1600986600000), y: 104920 },
+ { x: new Date(1600987500000), y: 94031 },
+ { x: new Date(1600988400000), y: 125021 },
+ { x: new Date(1600989300000), y: 159219 },
+ { x: new Date(1600990200000), y: 193082 },
+ { x: new Date(1600991100000), y: 162592 },
+ { x: new Date(1600992000000), y: 274021 },
+ { x: new Date(1600992900000), y: 264286 },
+ { x: new Date(1600993800000), y: 289210 },
+ { x: new Date(1600994700000), y: 256362 },
+ { x: new Date(1600995600000), y: 257306 },
+ { x: new Date(1600996500000), y: 186776 },
+ { x: new Date(1600997400000), y: 294020 },
+ { x: new Date(1600998300000), y: 385975 },
+ { x: new Date(1600999200000), y: 486039 },
+ { x: new Date(1601000100000), y: 490447 },
+ { x: new Date(1601001000000), y: 361845 },
+ { x: new Date(1601001900000), y: 339058 },
+ { x: new Date(1601002800000), y: 298028 },
+ { x: new Date(1601003700000), y: 231902 },
+ { x: new Date(1601004600000), y: 224558 },
+ { x: new Date(1601005500000), y: 253901 },
+ { x: new Date(1601006400000), y: 102839 },
+ { x: new Date(1601007300000), y: 234943 },
+ { x: new Date(1601008200000), y: 204405 },
+ { x: new Date(1601009100000), y: 190391 },
+ { x: new Date(1601010000000), y: 183570 },
+ { x: new Date(1601010900000), y: 162592 },
+ { x: new Date(1601011800000), y: 148910 },
+ { x: new Date(1601012700000), y: 229492 },
+ { x: new Date(1601013600000), y: 293910 },
+];
+
+const lineChartSite2 = [
+ { x: new Date(1600984800000), y: 151023 },
+ { x: new Date(1600985700000), y: 169975 },
+ { x: new Date(1600986600000), y: 176980 },
+ { x: new Date(1600987500000), y: 168852 },
+ { x: new Date(1600988400000), y: 149130 },
+ { x: new Date(1600989300000), y: 147299 },
+ { x: new Date(1600990200000), y: 169552 },
+ { x: new Date(1600991100000), y: 163401 },
+ { x: new Date(1600992000000), y: 154091 },
+ { x: new Date(1600992900000), y: 199516 },
+ { x: new Date(1600993800000), y: 195503 },
+ { x: new Date(1600994700000), y: 189953 },
+ { x: new Date(1600995600000), y: 181635 },
+ { x: new Date(1600996500000), y: 192975 },
+ { x: new Date(1600997400000), y: 205951 },
+ { x: new Date(1600998300000), y: 218958 },
+ { x: new Date(1600999200000), y: 220516 },
+ { x: new Date(1601000100000), y: 213557 },
+ { x: new Date(1601001000000), y: 165899 },
+ { x: new Date(1601001900000), y: 173557 },
+ { x: new Date(1601002800000), y: 172331 },
+ { x: new Date(1601003700000), y: 186492 },
+ { x: new Date(1601004600000), y: 131541 },
+ { x: new Date(1601005500000), y: 142262 },
+ { x: new Date(1601006400000), y: 194091 },
+ { x: new Date(1601007300000), y: 185899 },
+ { x: new Date(1601008200000), y: 173401 },
+ { x: new Date(1601009100000), y: 171635 },
+ { x: new Date(1601010000000), y: 179130 },
+ { x: new Date(1601010900000), y: 185951 },
+ { x: new Date(1601011800000), y: 144091 },
+ { x: new Date(1601012700000), y: 152975 },
+ { x: new Date(1601013600000), y: 157299 },
+];
+
+export default function Charts() {
+ return (
+
+ <>
+
+
+ ({ x, y: [12, 18, 15, 9, 18][i] })),
+ },
+ {
+ title: 'Moderate',
+ type: 'bar',
+ data: barChartDates.map((x, i) => ({ x, y: [8, 11, 12, 11, 13][i] })),
+ },
+ {
+ title: 'Low',
+ type: 'bar',
+ data: barChartDates.map((x, i) => ({ x, y: [7, 9, 8, 7, 5][i] })),
+ },
+ {
+ title: 'Unclassified',
+ type: 'bar',
+ data: barChartDates.map((x, i) => ({ x, y: [14, 8, 6, 4, 6][i] })),
+ },
+ ]}
+ xDomain={barChartDates}
+ yDomain={[0, 50]}
+ i18nStrings={{ xTickFormatter: formatDateTick }}
+ ariaLabel="Stacked bar chart"
+ height={300}
+ stackedBars={true}
+ xTitle="Time (UTC)"
+ yTitle="Error count"
+ empty={emptyState}
+ noMatch={noMatchState}
+ />
+
+
+
+
+
+
+
+ [
+ { key: 'Resource count', value: datum.value },
+ { key: 'Percentage', value: `${((datum.value / sum) * 100).toFixed(0)}%` },
+ { key: 'Last update on', value: datum.lastUpdate },
+ ]}
+ segmentDescription={(datum, sum) => `${datum.value} units, ${((datum.value / sum) * 100).toFixed(0)}%`}
+ ariaDescription="Pie chart showing how many resources are currently in which state."
+ ariaLabel="Pie chart"
+ empty={emptyState}
+ noMatch={noMatchState}
+ />
+
+
+ >
+
+ );
+}
diff --git a/pages/demos/pages/components-overview/chat.tsx b/pages/demos/pages/components-overview/chat.tsx
new file mode 100644
index 0000000000..64c6b954c7
--- /dev/null
+++ b/pages/demos/pages/components-overview/chat.tsx
@@ -0,0 +1,10 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+// chat-components not available in this environment — stubbed out
+import React from 'react';
+
+import Box from '@cloudscape-design/components/box';
+
+export default function Chat() {
+ return Chat demo not available (requires @cloudscape-design/chat-components) ;
+}
diff --git a/pages/demos/pages/components-overview/component-data.tsx b/pages/demos/pages/components-overview/component-data.tsx
new file mode 100644
index 0000000000..21e8a662ad
--- /dev/null
+++ b/pages/demos/pages/components-overview/component-data.tsx
@@ -0,0 +1,289 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+import padStart from 'lodash/padStart';
+import range from 'lodash/range';
+
+import { BoxProps } from '@cloudscape-design/components/box';
+import { FlashbarProps } from '@cloudscape-design/components/flashbar';
+import { MultiselectProps } from '@cloudscape-design/components/multiselect';
+import ProgressBar from '@cloudscape-design/components/progress-bar';
+import { SelectProps } from '@cloudscape-design/components/select';
+import { StatusIndicatorProps } from '@cloudscape-design/components/status-indicator';
+
+let seed = 1;
+export default function pseudoRandom() {
+ const x = Math.sin(seed++) * 10000;
+ return x - Math.floor(x);
+}
+
+export const items = [
+ { id: '1', title: 'Item 1', description: 'Description 1' },
+ { id: '2', title: 'Item 2', description: 'Description 2' },
+ { id: '3', title: 'Item 3', description: 'Description 3' },
+];
+
+export type InstanceState = 'PENDING' | 'RUNNING' | 'STOPPING' | 'STOPPED' | 'TERMINATING' | 'TERMINATED';
+
+export interface Instance {
+ id: string;
+ name: string;
+ description: string;
+ state: InstanceState;
+ type: string;
+ imageId: string;
+ dnsName?: string;
+}
+
+export function id() {
+ const id = Math.ceil(pseudoRandom() * Math.pow(16, 8)).toString(16);
+ return padStart(id, 8, '0');
+}
+
+function state() {
+ const states = [
+ 'PENDING',
+ 'RUNNING',
+ 'RUNNING',
+ 'RUNNING',
+ 'STOPPING',
+ 'STOPPED',
+ 'STOPPED',
+ 'TERMINATED',
+ 'TERMINATING',
+ ] as const;
+ return states[Math.floor(pseudoRandom() * states.length)];
+}
+
+function number() {
+ return 1 + Math.floor(pseudoRandom() * 256);
+}
+
+function dnsName() {
+ return `ec2-${number()}-${number()}-${number()}-${number()}.eu-west-1.compute.amazonaws.com`;
+}
+
+export const flashbarItems: FlashbarProps.MessageDefinition[] = [
+ {
+ header: 'Success',
+ type: 'success',
+ content: 'This is a success message -- check it out!',
+ dismissible: true,
+ dismissLabel: 'Dismiss success message',
+ id: 'success',
+ },
+ {
+ header: 'Warning',
+ type: 'warning',
+ content: 'This is a warning message -- check it out!',
+ dismissible: true,
+ dismissLabel: 'Dismiss warning message',
+ id: 'warning',
+ },
+ {
+ header: 'Error',
+ type: 'error',
+ content: 'This is an error message -- check it out!',
+ dismissible: true,
+ dismissLabel: 'Dismiss error message',
+ id: 'error',
+ },
+ {
+ header: 'Info',
+ type: 'info',
+ content: 'This is an info message -- check it out!',
+ dismissible: true,
+ dismissLabel: 'Dismiss info message',
+ id: 'info',
+ },
+ {
+ header: 'In-progress',
+ type: 'in-progress',
+ content: (
+ <>
+ This is an in-progress flash -- check it out!
+
+ >
+ ),
+ dismissible: true,
+ dismissLabel: 'Dismiss in-progress message',
+ id: 'in-progress',
+ },
+ {
+ header: 'Loading',
+ type: 'in-progress',
+ loading: true,
+ content: 'This is a loading flash -- check it out!',
+ dismissible: true,
+ dismissLabel: 'Dismiss loading message',
+ id: 'loading',
+ },
+];
+
+export interface RandomData {
+ description: string;
+ name: string;
+ amount: string;
+ increase: boolean;
+}
+
+const collectionData: RandomData[] = [
+ {
+ description: 'volutpat. Nulla dignissim. Maecenas ornare egestas ligula. Nullam feugiat placerat',
+ name: 'Velit Egestas LLP',
+ amount: '$68.54',
+ increase: true,
+ },
+ {
+ description: 'vestibulum lorem, sit amet ultricies sem magna nec quam. Curabitur',
+ name: 'Mattis Velit Justo Company',
+ amount: '$80.38',
+ increase: true,
+ },
+ {
+ description: 'aliquet odio. Etiam ligula tortor, dictum eu, placerat eget, venenatis',
+ name: 'Tempor LLP',
+ amount: '$1.66',
+ increase: false,
+ },
+ {
+ description: 'ridiculus mus. Donec dignissim magna a tortor. Nunc commodo auctor',
+ name: 'Egestas Hendrerit Neque Corporation',
+ amount: '$31.74',
+ increase: true,
+ },
+ {
+ description: 'Vivamus molestie dapibus ligula. Aliquam erat volutpat. Nulla dignissim. Maecenas',
+ name: 'Aenean Incorporated',
+ amount: '$53.61',
+ increase: true,
+ },
+ {
+ description: 'Cras sed leo. Cras vehicula aliquet libero. Integer in magna.',
+ name: 'Proin Ltd',
+ amount: '$42.19',
+ increase: false,
+ },
+ {
+ description: 'Phasellus at augue id ante dictum cursus. Nunc mauris elit,',
+ name: 'Nulla Facilisi Foundation',
+ amount: '$97.03',
+ increase: true,
+ },
+ {
+ description: 'Sed nec metus facilisis lorem tristique aliquet. Phasellus fermentum convallis',
+ name: 'Donec Vitae Corp',
+ amount: '$15.88',
+ increase: false,
+ },
+];
+
+export const cardItems = collectionData.slice(0, 2);
+export const tableItems = collectionData;
+
+export const fontSizes: BoxProps.FontSize[] = [
+ 'body-s',
+ 'body-m',
+ 'heading-xs',
+ 'heading-s',
+ 'heading-m',
+ 'heading-l',
+ 'heading-xl',
+ 'display-l',
+];
+
+export const statusToText: [StatusIndicatorProps.Type, string][] = [
+ ['error', 'Error'],
+ ['warning', 'Warning'],
+ ['success', 'Success'],
+ ['info', 'Info'],
+ ['stopped', 'Stopped'],
+ ['pending', 'Pending'],
+ ['in-progress', 'In progress'],
+ ['loading', 'Loading'],
+];
+
+export function instanceType() {
+ const types = [
+ 't1.micro',
+ 't2.nano',
+ 't2.small',
+ 't2.xlarge',
+ 't2.2xlarge',
+ 'm3.medium',
+ 'm3.large',
+ 'm3.xlarge',
+ 'm3.2xlarge',
+ 'm4.large',
+ 'm4.xlarge',
+ 'm4.2xlarge',
+ 'm4.4xlarge',
+ 'm4.10xlarge',
+ 'm4.16xlarge',
+ 'cr1.8xlarge',
+ 'r5.large',
+ 'r5.xlarge',
+ 'r5.2xlarge',
+ 'r5.metal',
+ 'r5d.xlarge',
+ 'r5d.2xlarge',
+ 'r5d.4xlarge',
+ 'r5d.8xlarge',
+ 'r5d.12xlarge',
+ 'r5d.16xlarge',
+ 'r5d.24xlarge',
+ 'r5d.metal',
+ 'i3.large',
+ 'i3.xlarge',
+ 'i3.2xlarge',
+ 'i3.16xlarge',
+ 'c3.large',
+ 'c3.xlarge',
+ 'c4.2xlarge',
+ 'c5.large',
+ 'c5.4xlarge ',
+ 'g2.2xlarge',
+ 'p2.xlarge',
+ 'm5.large',
+ 'm5.xlarge',
+ 'm5.2xlarge',
+ 'u-6tb1.metal',
+ ];
+ return types[Math.floor(pseudoRandom() * types.length)];
+}
+
+function imageId() {
+ return `ami-${id()}`;
+}
+
+export function generateCollectionItems(count = 5): Instance[] {
+ return range(count).map(() => {
+ const value: Instance = {
+ id: id(),
+ name: `Instance ${id()}`,
+ description: '',
+ state: state(),
+ type: instanceType(),
+ imageId: imageId(),
+ };
+ if (value.state !== 'PENDING') {
+ value.dnsName = dnsName();
+ }
+ return value;
+ });
+}
+
+export const generateDropdownOptions = (count = 25): SelectProps.Options | MultiselectProps.Options => {
+ return [...Array(count).keys()].map(n => {
+ const numberToDisplay = (n + 1).toString();
+ const baseOption = {
+ id: numberToDisplay,
+ value: numberToDisplay,
+ label: `Option ${numberToDisplay}`,
+ };
+ if (n === 0 || n === 24 || n === 49) {
+ return { ...baseOption, disabled: true, disabledReason: 'disabled reason' };
+ }
+ return baseOption;
+ });
+};
diff --git a/pages/demos/pages/components-overview/form-controls.tsx b/pages/demos/pages/components-overview/form-controls.tsx
new file mode 100644
index 0000000000..19d460e1da
--- /dev/null
+++ b/pages/demos/pages/components-overview/form-controls.tsx
@@ -0,0 +1,184 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import Checkbox from '@cloudscape-design/components/checkbox';
+import ColumnLayout from '@cloudscape-design/components/column-layout';
+import Grid from '@cloudscape-design/components/grid';
+import RadioGroup from '@cloudscape-design/components/radio-group';
+import Slider from '@cloudscape-design/components/slider';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import Tiles from '@cloudscape-design/components/tiles';
+import Toggle from '@cloudscape-design/components/toggle';
+
+import { Section } from './utils';
+
+export default function FormControls() {
+ return (
+
+
+
+
+
+ Checked
+
+
+ Unchecked
+
+
+ Disabled
+
+
+ Disabled
+
+
+ Read-only
+
+
+ Read-only
+
+
+ Read-only, disabled
+
+
+ Read-only, disabled
+
+
+
+
+
+
+
+
+
+
+
+
+ Checked
+
+
+ Unchecked
+
+
+ Disabled
+
+
+ Disabled
+
+
+ Read-only
+
+
+ Read-only
+
+
+ Read-only, disabled
+
+
+ Read-only, disabled
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/pages/demos/pages/components-overview/images.tsx b/pages/demos/pages/components-overview/images.tsx
new file mode 100644
index 0000000000..d763b85dff
--- /dev/null
+++ b/pages/demos/pages/components-overview/images.tsx
@@ -0,0 +1,190 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import Box from '@cloudscape-design/components/box';
+import Button from '@cloudscape-design/components/button';
+import ColumnLayout from '@cloudscape-design/components/column-layout';
+import Container from '@cloudscape-design/components/container';
+import Header from '@cloudscape-design/components/header';
+import Link from '@cloudscape-design/components/link';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+
+import imageExampleA from '../../common/image-example-1.png';
+import imageExampleB from '../../common/image-example-2.png';
+import imageExampleC from '../../common/image-example-3.png';
+
+export default function Images() {
+ return (
+
+
+
+ ,
+ position: 'side',
+ width: '40%',
+ }}
+ >
+
+
+
+
+ Product title
+
+
+ Company name
+
+
+ This is a paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut luctus tempor dolor ac
+ accumsan.
+
+
+ $0.1/hour
+
+ Shop now
+
+
+
+ ,
+ position: 'side',
+ width: '40%',
+ }}
+ >
+
+
+
+
+ Product title
+
+
+ Company name
+
+
+ This is a paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut luctus tempor dolor ac
+ accumsan.
+
+
+ $0.1/hour
+
+ Shop now
+
+
+
+ ,
+ position: 'side',
+ width: '40%',
+ }}
+ >
+
+
+
+
+ Product title
+
+
+ Company name
+
+
+ This is a paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut luctus tempor dolor ac
+ accumsan.
+
+
+ $0.1/hour
+
+ Shop now
+
+
+
+
+
+ ),
+ height: 200,
+ }}
+ >
+
+
+ 43 min
+ Video Title
+
+ This is a paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut luctus tempor dolor ac
+ accumsan. This is a paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut luctus tempor
+ dolor ac accumsan.
+
+
+ See video
+
+
+
+
+
+
+ ),
+ height: 200,
+ }}
+ >
+
+
+ 43 min
+ Video Title
+
+ This is a paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut luctus tempor dolor ac
+ accumsan. This is a paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut luctus tempor
+ dolor ac accumsan.
+
+
+ See video
+
+
+
+
+
+
+ ),
+ height: 200,
+ }}
+ >
+
+
+ 43 min
+ Video Title
+
+ This is a paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut luctus tempor dolor ac
+ accumsan. This is a paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut luctus tempor
+ dolor ac accumsan.
+
+
+ See video
+
+
+
+
+
+
+ );
+}
diff --git a/pages/demos/pages/components-overview/kvp-form.tsx b/pages/demos/pages/components-overview/kvp-form.tsx
new file mode 100644
index 0000000000..e182f680c8
--- /dev/null
+++ b/pages/demos/pages/components-overview/kvp-form.tsx
@@ -0,0 +1,146 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import { ColumnLayout } from '@cloudscape-design/components';
+import Box from '@cloudscape-design/components/box';
+import Button from '@cloudscape-design/components/button';
+import CopyToClipboard from '@cloudscape-design/components/copy-to-clipboard';
+import Form from '@cloudscape-design/components/form';
+import FormField from '@cloudscape-design/components/form-field';
+import Grid from '@cloudscape-design/components/grid';
+import Header from '@cloudscape-design/components/header';
+import Icon from '@cloudscape-design/components/icon';
+import Input from '@cloudscape-design/components/input';
+import KeyValuePairs from '@cloudscape-design/components/key-value-pairs';
+import Link from '@cloudscape-design/components/link';
+import Select from '@cloudscape-design/components/select';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import StatusIndicator from '@cloudscape-design/components/status-indicator';
+import Textarea from '@cloudscape-design/components/textarea';
+import Tiles from '@cloudscape-design/components/tiles';
+
+import { Section } from './utils';
+
+export default function KvpForm() {
+ const [value, setValue] = React.useState('standard');
+ return (
+
+
+
+
+
+
+ Info
+
+ ),
+ },
+ {
+ label: 'Status',
+ value: Available ,
+ },
+ { label: 'Price class', value: 'Use only US, Canada, Europe, and Asia' },
+ { label: 'CNAMEs', value: example.com },
+ {
+ label: 'ARN',
+ value: (
+
+ ),
+ },
+ ]}
+ />
+
+
+
+
+
+
+ }
+ header={}
+ >
+
+
+
+
+ setValue(e.detail.value)}
+ columns={4}
+ items={[
+ {
+ value: 'standard',
+ label: 'Standard',
+ description: 'Recommended for most workloads',
+ image: ,
+ },
+ {
+ value: 'optimized',
+ label: 'Optimized',
+ description: 'Best for dynamic content',
+ image: ,
+ },
+ {
+ value: 'custom',
+ label: 'Custom',
+ description: 'Configure your own policy',
+ image: ,
+ },
+ {
+ value: 'disabled',
+ label: 'Disabled',
+ description: 'No caching applied',
+ image: ,
+ },
+ ]}
+ ariaLabel="Cache policy"
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/pages/demos/pages/components-overview/navigation-components.tsx b/pages/demos/pages/components-overview/navigation-components.tsx
new file mode 100644
index 0000000000..9e777f5e5b
--- /dev/null
+++ b/pages/demos/pages/components-overview/navigation-components.tsx
@@ -0,0 +1,114 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useState } from 'react';
+
+import AnchorNavigation from '@cloudscape-design/components/anchor-navigation';
+import BreadcrumbGroup from '@cloudscape-design/components/breadcrumb-group';
+import Grid from '@cloudscape-design/components/grid';
+import Link from '@cloudscape-design/components/link';
+import SideNavigation from '@cloudscape-design/components/side-navigation';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import Tabs from '@cloudscape-design/components/tabs';
+
+import { Section } from './utils';
+
+export default function NavigationComponents() {
+ const [activeTabId, setActiveTabId] = useState('tab1');
+ const [activeHref, setActiveHref] = useState('#/parent-page/child-page1');
+
+ return (
+
+
+
+
+ {
+ if (!event.detail.external) {
+ event.preventDefault();
+ setActiveHref(event.detail.href);
+ }
+ }}
+ items={[
+ { type: 'link', text: 'Page 1', href: '#/page1' },
+ {
+ type: 'expandable-link-group',
+ text: 'Parent page',
+ href: '#/parent-page',
+ items: [
+ {
+ type: 'link',
+ text: 'Child page 1',
+ href: '#/parent-page/child-page1',
+ },
+ {
+ type: 'link',
+ text: 'Child page 2',
+ href: '#/parent-page/child-page2',
+ },
+ ],
+ },
+ { type: 'link', text: 'Page 2', href: '#/page2' },
+ { type: 'link', text: 'Page 3', href: '#/page3' },
+ ]}
+ />
+
+ setActiveTabId(detail.activeTabId)}
+ tabs={[
+ { id: 'tab1', label: 'Tab 1', content: 'Tab 1 Content' },
+ { id: 'tab2', label: 'Tab 2', content: 'Tab 2 Content' },
+ { id: 'tab3', label: 'Tab 3', content: 'Tab 3 Content', disabled: true },
+ ]}
+ />
+
+
+
+
+
+ Primary anchor link
+
+
+ Secondary anchor link
+
+ alert('You clicked the button link!')}>Button link
+
+
+
+ );
+}
diff --git a/pages/demos/pages/components-overview/overlays-and-patterns.tsx b/pages/demos/pages/components-overview/overlays-and-patterns.tsx
new file mode 100644
index 0000000000..01df46e297
--- /dev/null
+++ b/pages/demos/pages/components-overview/overlays-and-patterns.tsx
@@ -0,0 +1,108 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+// code-view replaced with — @cloudscape-design/code-view not available in this environment
+import React, { useState } from 'react';
+
+import Box from '@cloudscape-design/components/box';
+import ColumnLayout from '@cloudscape-design/components/column-layout';
+import TokenGroup from '@cloudscape-design/components/token-group';
+import Wizard from '@cloudscape-design/components/wizard';
+
+import { Section, SubSection } from './utils';
+
+const codeSnippet = `import { applyTheme } from '@cloudscape-design/components/theming';
+
+applyTheme({
+ theme: {
+ tokens: {
+ colorBackgroundLayoutMain: '#f5f5f5',
+ borderRadiusContainer: '8px',
+ },
+ },
+});`;
+
+export default function OverlaysAndPatterns() {
+ const [tokens, setTokens] = useState([
+ { label: 'us-east-1', dismissLabel: 'Remove us-east-1' },
+ { label: 'eu-west-1', dismissLabel: 'Remove eu-west-1' },
+ { label: 'ap-southeast-1', dismissLabel: 'Remove ap-southeast-1' },
+ ]);
+ const [activeWizardStep, setActiveWizardStep] = useState(0);
+
+ return (
+
+ <>
+
+
+ setTokens(tokens.filter((_, i) => i !== detail.itemIndex))}
+ />
+
+
+
+
+
+
+ {codeSnippet}
+
+
+
+
+
+ `Step ${stepNumber}`,
+ collapsedStepsLabel: (stepNumber, stepsCount) => `Step ${stepNumber} of ${stepsCount}`,
+ cancelButton: 'Cancel',
+ previousButton: 'Previous',
+ nextButton: 'Next',
+ submitButton: 'Create distribution',
+ optional: 'optional',
+ }}
+ activeStepIndex={activeWizardStep}
+ onNavigate={({ detail }) => setActiveWizardStep(detail.requestedStepIndex)}
+ steps={[
+ {
+ title: 'Choose origin',
+ description: 'Select the origin for your distribution.',
+ content: (
+
+ Configure the origin settings for your CloudFront distribution.
+
+ ),
+ },
+ {
+ title: 'Configure behaviors',
+ description: 'Set up cache behaviors and path patterns.',
+ content: (
+
+ Define how CloudFront handles requests for different URL paths.
+
+ ),
+ },
+ {
+ title: 'Review and create',
+ description: 'Review your configuration before creating.',
+ content: (
+ Review all settings and confirm the distribution creation.
+ ),
+ },
+ ]}
+ />
+
+
+ >
+
+ );
+}
diff --git a/pages/demos/pages/components-overview/status-components.tsx b/pages/demos/pages/components-overview/status-components.tsx
new file mode 100644
index 0000000000..6ca5b56107
--- /dev/null
+++ b/pages/demos/pages/components-overview/status-components.tsx
@@ -0,0 +1,363 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import Alert from '@cloudscape-design/components/alert';
+import Badge from '@cloudscape-design/components/badge';
+import Button from '@cloudscape-design/components/button';
+import ButtonGroup from '@cloudscape-design/components/button-group';
+import ColumnLayout from '@cloudscape-design/components/column-layout';
+import FileTokenGroup from '@cloudscape-design/components/file-token-group';
+import Flashbar, { FlashbarProps } from '@cloudscape-design/components/flashbar';
+import FormField from '@cloudscape-design/components/form-field';
+import Grid from '@cloudscape-design/components/grid';
+import Input from '@cloudscape-design/components/input';
+import ProgressBar from '@cloudscape-design/components/progress-bar';
+import Select from '@cloudscape-design/components/select';
+import Slider from '@cloudscape-design/components/slider';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import Spinner from '@cloudscape-design/components/spinner';
+import StatusIndicator from '@cloudscape-design/components/status-indicator';
+import Steps from '@cloudscape-design/components/steps';
+
+import { flashbarItems } from './component-data';
+import { Section, SubSection } from './utils';
+
+export default function StatusComponents() {
+ return (
+
+ <>
+
+
+ item.type === 'error') as FlashbarProps.MessageDefinition]} />
+
+ This is an error alert -- check it out!
+
+
+
+
+
+
+
+
+
+
+
+ Error Status
+ Badge
+
+
+
+
+
+
+
+ item.type === 'warning') as FlashbarProps.MessageDefinition]}
+ />
+
+ This is a warning alert -- check it out!
+
+
+
+
+
+
+
+
+
+ Warning Status
+
+
+
+
+
+
+ item.type === 'success') as FlashbarProps.MessageDefinition]}
+ />
+
+ This is a success alert -- check it out!
+
+
+
+
+
+ Success Status
+ Badge
+
+
+
+
+
+
+
+ item.type === 'info') as FlashbarProps.MessageDefinition]} />
+
+ This is an info alert -- check it out!
+
+
+
+
+
+ Info Status
+
+ Badge
+
+
+
+
+
+
+
+
+
+ This is an in-progress flash -- check it out!
+
+ >
+ ),
+ dismissible: true,
+ dismissLabel: 'Dismiss in-progress message',
+ id: 'in-progress',
+ },
+ {
+ header: 'Loading',
+ type: 'in-progress',
+ loading: true,
+ content: 'This is a loading flash -- check it out!',
+ dismissible: true,
+ dismissLabel: 'Dismiss loading message',
+ id: 'loading',
+ },
+ ]}
+ />
+
+ {
+ /* no-op for demo*/
+ }}
+ />
+
+
+
+
+ Loading
+
+ Loading
+
+
+ Loading
+
+
+
+
+
+
+
+
+
+
+
+
+
+ In-progress Status
+ Loading
+ Stopped Status
+ Pending Status
+ Badge
+
+
+
+
+ >
+
+ );
+}
diff --git a/pages/demos/pages/components-overview/table-and-cards.tsx b/pages/demos/pages/components-overview/table-and-cards.tsx
new file mode 100644
index 0000000000..16a79a517f
--- /dev/null
+++ b/pages/demos/pages/components-overview/table-and-cards.tsx
@@ -0,0 +1,189 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useState } from 'react';
+
+import Badge from '@cloudscape-design/components/badge';
+import Cards from '@cloudscape-design/components/cards';
+import CollectionPreferences from '@cloudscape-design/components/collection-preferences';
+import Header from '@cloudscape-design/components/header';
+import Link from '@cloudscape-design/components/link';
+import Pagination from '@cloudscape-design/components/pagination';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import Table from '@cloudscape-design/components/table';
+import TextFilter from '@cloudscape-design/components/text-filter';
+
+import { cardItems, RandomData, tableItems } from './component-data';
+import { Section } from './utils';
+
+export default function TableAndCards() {
+ const [selectedItems, setSelectedItems] = useState({
+ table: [tableItems[2]],
+ cards: [cardItems[1]],
+ });
+ const [filteringText, setFilteringText] = useState('');
+ const [currentPage, setCurrentPage] = useState(1);
+ const [preferences, setPreferences] = useState({
+ pageSize: 5,
+ visibleContent: ['name', 'category', 'description'],
+ });
+
+ return (
+ // Cards already have built-in margin so the space between is smaller than oter sections
+
+
+ (
+
+ {item.name}
+
+ ),
+ sections: [
+ {
+ id: 'description',
+ header: 'Description',
+ content: item => item.description,
+ width: 85,
+ },
+ {
+ id: 'cost',
+ header: 'Cost',
+ content: item => item.amount,
+ width: 15,
+ },
+ ],
+ }}
+ selectionType="multi"
+ selectedItems={selectedItems.cards}
+ onSelectionChange={({ detail }) =>
+ setSelectedItems(prevState => ({ ...prevState, cards: detail.selectedItems }))
+ }
+ ariaLabels={{
+ itemSelectionLabel: (e, item) => `Select ${item.name}`,
+ selectionGroupLabel: 'Item selection',
+ }}
+ />
+
+
+
+ Info
+
+ }
+ >
+ Table with common features
+
+ }
+ filter={
+ setFilteringText(detail.filteringText)}
+ />
+ }
+ pagination={
+ setCurrentPage(detail.currentPageIndex)}
+ ariaLabels={{
+ nextPageLabel: 'Next page',
+ previousPageLabel: 'Previous page',
+ pageLabel: pageNumber => `Page ${pageNumber}`,
+ }}
+ />
+ }
+ preferences={
+
+ setPreferences({
+ pageSize: detail.pageSize ?? 5,
+ visibleContent: (detail.visibleContent as string[]) ?? [],
+ })
+ }
+ pageSizePreference={{
+ title: 'Page size',
+ options: [
+ { value: 5, label: '5 resources' },
+ { value: 10, label: '10 resources' },
+ { value: 20, label: '20 resources' },
+ ],
+ }}
+ visibleContentPreference={{
+ title: 'Select visible content',
+ options: [
+ {
+ label: 'Main properties',
+ options: [
+ { id: 'name', label: 'Name' },
+ { id: 'category', label: 'Category' },
+ { id: 'description', label: 'Description' },
+ ],
+ },
+ ],
+ }}
+ />
+ }
+ resizableColumns={true}
+ columnDefinitions={[
+ {
+ id: 'name',
+ header: 'Name',
+ cell: (item: RandomData) => (
+
+ {item.name}
+
+ ),
+ sortingField: 'name',
+ width: 200,
+ minWidth: 150,
+ },
+ {
+ id: 'category',
+ header: 'Category',
+ cell: (item: RandomData) => {
+ const categories = ['green', 'grey', 'blue'] as const;
+ const labels = ['Serverless', 'Security', 'Agentic AI'];
+ const index = item.name.length % 3;
+ return {labels[index]} ;
+ },
+ sortingField: 'category',
+ width: 150,
+ minWidth: 100,
+ },
+ {
+ id: 'description',
+ header: 'Description',
+ cell: (item: RandomData) => item.description,
+ width: 300,
+ minWidth: 200,
+ },
+ ]}
+ selectionType="single"
+ selectedItems={selectedItems.table}
+ onSelectionChange={({ detail }) =>
+ setSelectedItems(prevState => ({ ...prevState, table: detail.selectedItems }))
+ }
+ ariaLabels={{
+ itemSelectionLabel: (e, item) => `Select ${item.name}`,
+ allItemsSelectionLabel: () => 'Select all items',
+ selectionGroupLabel: 'Item selection',
+ }}
+ />
+
+
+ );
+}
diff --git a/pages/demos/pages/components-overview/typography.tsx b/pages/demos/pages/components-overview/typography.tsx
new file mode 100644
index 0000000000..8791d2b8c4
--- /dev/null
+++ b/pages/demos/pages/components-overview/typography.tsx
@@ -0,0 +1,237 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useState } from 'react';
+
+import {
+ Box,
+ ColumnLayout,
+ Container,
+ Header,
+ Icon,
+ Link,
+ SpaceBetween,
+ TextContent,
+ Toggle,
+} from '@cloudscape-design/components';
+
+export default function Typography() {
+ const [longText, setLongText] = useState(false);
+ return (
+ <>
+
+ setLongText(detail.checked)} checked={longText}>
+ Long text
+
+ }
+ >
+ Typography & Iconography
+
+
+
+
+
+ {longText ? (
+ <>
+
+ Heading 1: The instance type you specify determines the hardware of the host computer used for your
+ instance. Each instance type offers different compute, memory, and storage capabilities.
+
+
+ Heading 2: The instance type you specify determines the hardware of the host computer used for your
+ instance. Each instance type offers different compute, memory, and storage capabilities.
+
+
+ Heading 3: The instance type you specify determines the hardware of the host computer used for your
+ instance. Each instance type offers different compute, memory, and storage capabilities.
+
+
+ Heading 4: The instance type you specify determines the hardware of the host computer used for your
+ instance. Each instance type offers different compute, memory, and storage capabilities.
+
+
+ Heading 5: The instance type you specify determines the hardware of the host computer used for your
+ instance. Each instance type offers different compute, memory, and storage capabilities.
+
+ >
+ ) : (
+ <>
+ Heading 1
+ Heading 2
+ Heading 3
+ Heading 4
+ Heading 5
+ >
+ )}
+
+
+
+ {longText ? (
+ <>
+ Paragraph - The instance type you specify determines the hardware of the host computer used for your
+ instance. Each instance type offers different compute, memory, and storage capabilities, and is grouped in
+ an instance family based on these capabilities. Select an instance type based on the requirements of the
+ application or software that you plan to run on your instance.{' '}
+
+ Amazon EC2
+ {' '}
+ provides each instance with a consistent and predictable amount of CPU capacity, regardless of its{' '}
+
+ underlying hardwares
+
+ . The order of buttons is important when action is required on a data set. It follows the order of major
+ actions that can be performed on items.
+ >
+ ) : (
+ <>
+ Paragraph -{' '}
+
+ Amazon EC2
+ {' '}
+ provides each instance with a consistent and predictable amount of CPU capacity, regardless of its{' '}
+
+ underlying hardwares
+
+ . The order of buttons is important when action is required on a data set. It follows the order of major
+ actions that can be performed on items.
+ >
+ )}
+
+
+ {longText ? (
+ <>
+ Small - Daily instance hours by instance type, aggregated across all regions and availability zones in
+ your account. By default the form field will take up 66% of its container width. Enabling the stretch
+ property will set the width of the form field to 100%. This can be done for fields where a full-width
+ layout is more appropriate, such as when using multi column layout.
+ >
+ ) : (
+ <>Small - Requirements and constraints for the field.>
+ )}
+
+ {/*
+ Icon - Small size
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ */}
+
+ Icon - Normal size
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/*
+ Icon - Medium size
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Icon - Big size
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Icon - Large size
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ */}
+
+ >
+ );
+}
diff --git a/pages/demos/pages/components-overview/utils.tsx b/pages/demos/pages/components-overview/utils.tsx
new file mode 100644
index 0000000000..0cedc2ffc2
--- /dev/null
+++ b/pages/demos/pages/components-overview/utils.tsx
@@ -0,0 +1,35 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+import { ReactElement } from 'react';
+
+import Box from '@cloudscape-design/components/box';
+import Container from '@cloudscape-design/components/container';
+import Header from '@cloudscape-design/components/header';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+
+interface SectionProps {
+ header?: string;
+ level?: 'h2' | 'h3';
+ container?: boolean;
+ children: ReactElement;
+}
+
+export const Section = ({ header, level = 'h2', container = true, children }: SectionProps) => {
+ const content = {children} ;
+ return (
+
+ {header && }
+ {container ? {content} : content}
+
+ );
+};
+
+export const SubSection = ({ header, level = 'h3', children }: SectionProps) => {
+ return (
+
+ {header && {header} }
+ {children}
+
+ );
+};
diff --git a/pages/demos/pages/dashboard/app.tsx b/pages/demos/pages/dashboard/app.tsx
new file mode 100644
index 0000000000..aeb1119bb9
--- /dev/null
+++ b/pages/demos/pages/dashboard/app.tsx
@@ -0,0 +1,69 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useRef, useState } from 'react';
+
+import { AppLayoutProps } from '@cloudscape-design/components/app-layout';
+import Button from '@cloudscape-design/components/button';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import SplitPanel from '@cloudscape-design/components/split-panel';
+
+import { Breadcrumbs, HelpPanelProvider, Notifications } from '../commons';
+import {
+ CustomAppLayout,
+ DemoTopNavigation,
+ GlobalSplitPanelContent,
+ useGlobalSplitPanel,
+} from '../commons/common-components';
+import { Content } from './components/content';
+import { DashboardHeader, DashboardMainInfo } from './components/header';
+import { DashboardSideNavigation } from './components/side-navigation';
+
+import '@cloudscape-design/global-styles/dark-mode-utils.css';
+import '../../styles/top-navigation.scss';
+
+export function App() {
+ const [toolsOpen, setToolsOpen] = useState(false);
+ const [toolsContent, setToolsContent] = useState(() => );
+ const { splitPanelOpen, onSplitPanelToggle, splitPanelSize, onSplitPanelResize, splitPanelPreferences } =
+ useGlobalSplitPanel();
+ const appLayout = useRef(null);
+
+ const handleToolsContentChange = (content: React.ReactNode) => {
+ setToolsOpen(true);
+ setToolsContent(content);
+ appLayout.current?.focusToolsClose();
+ };
+
+ return (
+
+ <>
+
+
+ Launch instance} />
+
+
+ }
+ breadcrumbs={ }
+ navigation={ }
+ tools={toolsContent}
+ toolsOpen={toolsOpen}
+ onToolsChange={({ detail }) => setToolsOpen(detail.open)}
+ splitPanelOpen={splitPanelOpen}
+ onSplitPanelToggle={onSplitPanelToggle}
+ splitPanelSize={splitPanelSize}
+ onSplitPanelResize={onSplitPanelResize}
+ splitPanelPreferences={splitPanelPreferences}
+ splitPanel={
+
+
+
+ }
+ notifications={ }
+ />
+ >
+
+ );
+}
diff --git a/pages/demos/pages/dashboard/assets/bar-chart-dark.svg b/pages/demos/pages/dashboard/assets/bar-chart-dark.svg
new file mode 100644
index 0000000000..a243eb99b1
--- /dev/null
+++ b/pages/demos/pages/dashboard/assets/bar-chart-dark.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/pages/demos/pages/dashboard/assets/bar-chart-light.svg b/pages/demos/pages/dashboard/assets/bar-chart-light.svg
new file mode 100644
index 0000000000..b2c5291edf
--- /dev/null
+++ b/pages/demos/pages/dashboard/assets/bar-chart-light.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/pages/demos/pages/dashboard/assets/line-chart-dark.svg b/pages/demos/pages/dashboard/assets/line-chart-dark.svg
new file mode 100644
index 0000000000..fcd98737ea
--- /dev/null
+++ b/pages/demos/pages/dashboard/assets/line-chart-dark.svg
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/demos/pages/dashboard/assets/line-chart-light.svg b/pages/demos/pages/dashboard/assets/line-chart-light.svg
new file mode 100644
index 0000000000..8d5abe28ea
--- /dev/null
+++ b/pages/demos/pages/dashboard/assets/line-chart-light.svg
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/demos/pages/dashboard/assets/list-dark.svg b/pages/demos/pages/dashboard/assets/list-dark.svg
new file mode 100644
index 0000000000..6c6947ecd9
--- /dev/null
+++ b/pages/demos/pages/dashboard/assets/list-dark.svg
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/pages/demos/pages/dashboard/assets/list-light.svg b/pages/demos/pages/dashboard/assets/list-light.svg
new file mode 100644
index 0000000000..c9519e993f
--- /dev/null
+++ b/pages/demos/pages/dashboard/assets/list-light.svg
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/pages/demos/pages/dashboard/assets/mixed-content-dark.svg b/pages/demos/pages/dashboard/assets/mixed-content-dark.svg
new file mode 100644
index 0000000000..f9d5577582
--- /dev/null
+++ b/pages/demos/pages/dashboard/assets/mixed-content-dark.svg
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/demos/pages/dashboard/assets/mixed-content-light.svg b/pages/demos/pages/dashboard/assets/mixed-content-light.svg
new file mode 100644
index 0000000000..b3e1938f92
--- /dev/null
+++ b/pages/demos/pages/dashboard/assets/mixed-content-light.svg
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/demos/pages/dashboard/assets/pie-chart-dark.svg b/pages/demos/pages/dashboard/assets/pie-chart-dark.svg
new file mode 100644
index 0000000000..888d10747f
--- /dev/null
+++ b/pages/demos/pages/dashboard/assets/pie-chart-dark.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/pages/demos/pages/dashboard/assets/pie-chart-light.svg b/pages/demos/pages/dashboard/assets/pie-chart-light.svg
new file mode 100644
index 0000000000..0ebe473058
--- /dev/null
+++ b/pages/demos/pages/dashboard/assets/pie-chart-light.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/pages/demos/pages/dashboard/assets/table-dark.svg b/pages/demos/pages/dashboard/assets/table-dark.svg
new file mode 100644
index 0000000000..8b6f5b3e97
--- /dev/null
+++ b/pages/demos/pages/dashboard/assets/table-dark.svg
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/demos/pages/dashboard/assets/table-light.svg b/pages/demos/pages/dashboard/assets/table-light.svg
new file mode 100644
index 0000000000..ea7ea3dd39
--- /dev/null
+++ b/pages/demos/pages/dashboard/assets/table-light.svg
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/demos/pages/dashboard/components/content.tsx b/pages/demos/pages/dashboard/components/content.tsx
new file mode 100644
index 0000000000..7cde292054
--- /dev/null
+++ b/pages/demos/pages/dashboard/components/content.tsx
@@ -0,0 +1,51 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import Grid from '@cloudscape-design/components/grid';
+
+import {
+ alarms,
+ BaseStaticWidget,
+ events,
+ featuresSpotlight,
+ instanceHours,
+ instanceLimits,
+ networkTraffic,
+ serviceHealth,
+ serviceOverview,
+ zoneStatus,
+} from '../widgets';
+
+export function Content() {
+ return (
+
+ {[
+ serviceOverview,
+ serviceHealth,
+ instanceHours,
+ networkTraffic,
+ alarms,
+ instanceLimits,
+ events,
+ zoneStatus,
+ featuresSpotlight,
+ ].map((widget, index) => (
+
+ ))}
+
+ );
+}
diff --git a/pages/demos/pages/dashboard/components/density-preferences/images/comfortable-visual-refresh.tsx b/pages/demos/pages/dashboard/components/density-preferences/images/comfortable-visual-refresh.tsx
new file mode 100644
index 0000000000..efa54feaa1
--- /dev/null
+++ b/pages/demos/pages/dashboard/components/density-preferences/images/comfortable-visual-refresh.tsx
@@ -0,0 +1,33 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import { TableRow, TableRows, TopNavigation, WindowPath } from './common';
+
+import * as styles from './styles.module.scss';
+
+const comfortableImage = (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default comfortableImage;
diff --git a/pages/demos/pages/dashboard/components/density-preferences/images/common.tsx b/pages/demos/pages/dashboard/components/density-preferences/images/common.tsx
new file mode 100644
index 0000000000..47cea23cf0
--- /dev/null
+++ b/pages/demos/pages/dashboard/components/density-preferences/images/common.tsx
@@ -0,0 +1,60 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import * as styles from './styles.module.scss';
+
+interface TableRowProps {
+ offset: number;
+ separator?: boolean;
+ compact?: boolean;
+ isHeader?: boolean;
+}
+
+export const TableRow = ({ offset, separator = true, compact = false, isHeader = false }: TableRowProps) => {
+ const offsetTop = 0.4482;
+ const offsetBottom = 3.4482;
+ const separatorDistance = compact ? 7 : 8;
+ return (
+
+
+
+
+
+ {separator && }
+
+ );
+};
+
+interface TableRowsProps {
+ offsetTop: number;
+ rows: number;
+ compact?: boolean;
+}
+
+export const TableRows = ({ offsetTop, rows, compact = false }: TableRowsProps) => {
+ const distance = compact ? 10 : 13;
+ return (
+
+ {[...Array(rows)].map((_, index) => (
+
+ ))}
+
+ );
+};
+
+export const WindowPath = () => (
+
+);
+export const TopNavigation = () => (
+
+
+
+);
diff --git a/pages/demos/pages/dashboard/components/density-preferences/images/compact-visual-refresh.tsx b/pages/demos/pages/dashboard/components/density-preferences/images/compact-visual-refresh.tsx
new file mode 100644
index 0000000000..436c02d14e
--- /dev/null
+++ b/pages/demos/pages/dashboard/components/density-preferences/images/compact-visual-refresh.tsx
@@ -0,0 +1,32 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import { TableRow, TableRows, TopNavigation, WindowPath } from './common';
+
+import * as styles from './styles.module.scss';
+
+const compactImage = (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default compactImage;
diff --git a/pages/demos/pages/dashboard/components/density-preferences/images/index.ts b/pages/demos/pages/dashboard/components/density-preferences/images/index.ts
new file mode 100644
index 0000000000..7e4df5ab0f
--- /dev/null
+++ b/pages/demos/pages/dashboard/components/density-preferences/images/index.ts
@@ -0,0 +1,4 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+export { default as comfortableModeImage } from './comfortable-visual-refresh';
+export { default as compactModeImage } from './compact-visual-refresh';
diff --git a/pages/demos/pages/dashboard/components/density-preferences/images/styles.module.scss b/pages/demos/pages/dashboard/components/density-preferences/images/styles.module.scss
new file mode 100644
index 0000000000..f0436a5af8
--- /dev/null
+++ b/pages/demos/pages/dashboard/components/density-preferences/images/styles.module.scss
@@ -0,0 +1,33 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+
+@use '~@cloudscape-design/design-tokens' as cs;
+
+.window {
+ stroke: cs.$color-background-home-header;
+ fill: cs.$color-background-container-content;
+}
+.top-navigation {
+ fill: cs.$color-background-button-primary-disabled;
+}
+.header {
+ fill: cs.$color-background-layout-main;
+}
+.default {
+ fill: cs.$color-text-body-default;
+}
+.primary {
+ fill: cs.$color-background-button-primary-default;
+}
+.disabled {
+ fill: cs.$color-background-control-disabled;
+}
+.column-header {
+ fill: cs.$color-text-input-disabled;
+}
+.secondary {
+ fill: cs.$color-text-body-secondary;
+}
+.separator {
+ stroke: cs.$color-border-divider-default;
+}
diff --git a/pages/demos/pages/dashboard/components/density-preferences/images/styles.module.scss.d.ts b/pages/demos/pages/dashboard/components/density-preferences/images/styles.module.scss.d.ts
new file mode 100644
index 0000000000..dea6687ed8
--- /dev/null
+++ b/pages/demos/pages/dashboard/components/density-preferences/images/styles.module.scss.d.ts
@@ -0,0 +1,2 @@
+declare const styles: { readonly [key: string]: string };
+export = styles;
diff --git a/pages/demos/pages/dashboard/components/density-preferences/index.tsx b/pages/demos/pages/dashboard/components/density-preferences/index.tsx
new file mode 100644
index 0000000000..a5b3c9579b
--- /dev/null
+++ b/pages/demos/pages/dashboard/components/density-preferences/index.tsx
@@ -0,0 +1,70 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useState } from 'react';
+
+import Box from '@cloudscape-design/components/box';
+import Button from '@cloudscape-design/components/button';
+import FormField from '@cloudscape-design/components/form-field';
+import Modal from '@cloudscape-design/components/modal';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import Tiles from '@cloudscape-design/components/tiles';
+import { Density } from '@cloudscape-design/global-styles';
+
+import { currentDensity, updateDensity } from '../../../../common/apply-mode';
+import { comfortableModeImage, compactModeImage } from './images';
+
+interface DensityPreferencesDialogProps {
+ onDismiss: () => void;
+}
+
+export function DensityPreferencesDialog({ onDismiss }: DensityPreferencesDialogProps) {
+ const [value, setValue] = useState(currentDensity ?? 'comfortable');
+
+ const handleSubmit = () => {
+ updateDensity(value);
+ onDismiss();
+ };
+
+ return (
+ onDismiss()}
+ visible={true}
+ size="medium"
+ closeAriaLabel="Close modal"
+ footer={
+
+
+ onDismiss()}>
+ Cancel
+
+
+ Confirm
+
+
+
+ }
+ header="Density settings"
+ >
+
+ setValue(detail.value as Density)}
+ value={value}
+ items={[
+ {
+ value: 'comfortable',
+ label: 'Comfortable',
+ description: 'Default spacing that optimizes information consumption.',
+ image: comfortableModeImage,
+ },
+ {
+ value: 'compact',
+ label: 'Compact',
+ description: 'Reduced spacing that provides more visibility over content.',
+ image: compactModeImage,
+ },
+ ]}
+ />
+
+
+ );
+}
diff --git a/pages/demos/pages/dashboard/components/empty-state/index.tsx b/pages/demos/pages/dashboard/components/empty-state/index.tsx
new file mode 100644
index 0000000000..66e8c5243b
--- /dev/null
+++ b/pages/demos/pages/dashboard/components/empty-state/index.tsx
@@ -0,0 +1,35 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import Box from '@cloudscape-design/components/box';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+
+import * as styles from './styles.module.scss';
+
+interface EmptyStateProps {
+ icon?: React.ReactNode;
+ title: string;
+ verticalCenter?: boolean;
+ description: string;
+ action?: React.ReactNode;
+}
+
+export const EmptyState = ({ icon, title, description, action, verticalCenter }: EmptyStateProps) => (
+
+
+
+
+ {icon &&
{icon}
}
+
+ {title}
+
+
+ {description}
+
+
+ {action}
+
+
+
+);
diff --git a/pages/demos/pages/dashboard/components/empty-state/styles.module.scss b/pages/demos/pages/dashboard/components/empty-state/styles.module.scss
new file mode 100644
index 0000000000..949ca79d25
--- /dev/null
+++ b/pages/demos/pages/dashboard/components/empty-state/styles.module.scss
@@ -0,0 +1,10 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+@media only screen and (min-width: 960px) {
+ .verticalCenter {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+}
diff --git a/pages/demos/pages/dashboard/components/empty-state/styles.module.scss.d.ts b/pages/demos/pages/dashboard/components/empty-state/styles.module.scss.d.ts
new file mode 100644
index 0000000000..dea6687ed8
--- /dev/null
+++ b/pages/demos/pages/dashboard/components/empty-state/styles.module.scss.d.ts
@@ -0,0 +1,2 @@
+declare const styles: { readonly [key: string]: string };
+export = styles;
diff --git a/pages/demos/pages/dashboard/components/header.tsx b/pages/demos/pages/dashboard/components/header.tsx
new file mode 100644
index 0000000000..1b88222b48
--- /dev/null
+++ b/pages/demos/pages/dashboard/components/header.tsx
@@ -0,0 +1,46 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import Header from '@cloudscape-design/components/header';
+import HelpPanel from '@cloudscape-design/components/help-panel';
+
+import { ExternalLinkGroup, InfoLink, useHelpPanel } from '../../commons';
+
+export function DashboardMainInfo() {
+ return (
+ Service}
+ footer={
+
+ }
+ >
+
+ Amazon Elastic Compute Cloud (Amazon EC2) is a web service that provides resizeable computing
+ capacity—literally, servers in Amazon's data centers—that you use to build and host your software
+ systems.
+
+
+ );
+}
+
+export function DashboardHeader({ actions }: { actions: React.ReactNode }) {
+ const loadHelpPanelContent = useHelpPanel();
+ return (
+ loadHelpPanelContent( )} />}
+ actions={actions}
+ >
+ Service Dashboard
+
+ );
+}
diff --git a/pages/demos/pages/dashboard/components/responsive-layout/index.tsx b/pages/demos/pages/dashboard/components/responsive-layout/index.tsx
new file mode 100644
index 0000000000..c1a3be58f6
--- /dev/null
+++ b/pages/demos/pages/dashboard/components/responsive-layout/index.tsx
@@ -0,0 +1,40 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import { useContainerQuery } from '@cloudscape-design/component-toolkit';
+
+import * as styles from './styles.module.scss';
+
+interface ResponsiveLayoutProps {
+ filters: React.ReactNode;
+ children: React.ReactNode;
+}
+
+export function ResponsiveLayout({ filters, children }: ResponsiveLayoutProps) {
+ const [width, ref] = useContainerQuery(rect => rect.borderBoxWidth);
+ const multiColumn = width && width > 480;
+ return (
+
+
{filters}
+
{children}
+
+ );
+}
+
+interface WidgetLayoutColumnProps {
+ header: React.ReactNode;
+ children: React.ReactNode;
+ footer?: React.ReactNode;
+}
+
+function WidgetLayoutColumn({ header, children }: WidgetLayoutColumnProps) {
+ return (
+
+ {header}
+ {children}
+
+ );
+}
+
+ResponsiveLayout.Column = WidgetLayoutColumn;
diff --git a/pages/demos/pages/dashboard/components/responsive-layout/styles.module.scss b/pages/demos/pages/dashboard/components/responsive-layout/styles.module.scss
new file mode 100644
index 0000000000..a23c9b1563
--- /dev/null
+++ b/pages/demos/pages/dashboard/components/responsive-layout/styles.module.scss
@@ -0,0 +1,56 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+
+@use '~@cloudscape-design/design-tokens' as cs;
+
+.root {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ .filters {
+ padding-bottom: cs.$space-scaled-s;
+ // https://github.com/cloudscape-design/components/blob/7433543fcd2796d6398b67867d25b637c1a2b6de/src/mixed-line-bar-chart/styles.scss#L23
+ max-width: 280px;
+ }
+ .columns {
+ flex: 1;
+ }
+}
+
+.columns {
+ display: flex;
+ // gutter from each side and border width
+ gap: calc((2 * #{cs.$space-scaled-l}) + 1px);
+ &.multi {
+ flex-direction: row;
+ & > * {
+ flex: 1;
+ min-height: 100%;
+ }
+ & > :last-child:not(:first-child)::after {
+ content: ' ';
+ position: absolute;
+ left: calc(-1 * #{cs.$space-scaled-l});
+ height: 100%;
+ border-left: 1px solid cs.$color-border-divider-default;
+ }
+ }
+ &.single {
+ flex-direction: column;
+ & > :nth-child(2) {
+ flex: 1;
+ }
+ }
+}
+
+.column-item {
+ display: grid;
+ grid-template-rows: auto 1fr auto;
+ position: relative;
+ gap: cs.$space-scaled-s;
+ &-footer {
+ padding-top: cs.$space-scaled-s;
+ border-top: 1px solid cs.$color-border-divider-default;
+ text-align: center;
+ }
+}
diff --git a/pages/demos/pages/dashboard/components/responsive-layout/styles.module.scss.d.ts b/pages/demos/pages/dashboard/components/responsive-layout/styles.module.scss.d.ts
new file mode 100644
index 0000000000..dea6687ed8
--- /dev/null
+++ b/pages/demos/pages/dashboard/components/responsive-layout/styles.module.scss.d.ts
@@ -0,0 +1,2 @@
+declare const styles: { readonly [key: string]: string };
+export = styles;
diff --git a/pages/demos/pages/dashboard/components/side-navigation.tsx b/pages/demos/pages/dashboard/components/side-navigation.tsx
new file mode 100644
index 0000000000..479d82459b
--- /dev/null
+++ b/pages/demos/pages/dashboard/components/side-navigation.tsx
@@ -0,0 +1,210 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useState } from 'react';
+
+import Box from '@cloudscape-design/components/box';
+import Link from '@cloudscape-design/components/link';
+import Popover from '@cloudscape-design/components/popover';
+import { SideNavigationProps } from '@cloudscape-design/components/side-navigation';
+
+import { Navigation as CommonNavigation } from '../../commons';
+import { DensityPreferencesDialog } from './density-preferences';
+
+const navItems: SideNavigationProps['items'] = [
+ { type: 'link', text: 'Dashboard', href: '#/' },
+ {
+ type: 'link',
+ text: 'Events',
+ href: '#/events',
+ info: (
+
+
+ AWS can schedule events for your instances, such as reboot, stop/start, or retirement.{' '}
+
+ Learn more
+
+ >
+ }
+ renderWithPortal={true}
+ dismissAriaLabel="Close"
+ >
+
+ New
+
+
+
+ ),
+ },
+ { type: 'link', text: 'Tags', href: '#/tags' },
+ { type: 'link', text: 'Reports', href: '#/reports' },
+ { type: 'link', text: 'Limits', href: '#/limits' },
+ {
+ text: 'Instances',
+ type: 'section',
+ defaultExpanded: true,
+ items: [
+ { type: 'link', text: 'Instances', href: '#/instances' },
+ {
+ type: 'link',
+ text: 'Launch templates',
+ href: '#/launch_templates',
+ info: (
+
+
+ Launch templates is a new capability that enables a new way to templatize your launch requests. Launch
+ templates streamline and simplify the launch process for auto scaling, spot fleet, spot, and on-demand
+ instances.{' '}
+
+ Learn more
+
+ >
+ }
+ renderWithPortal={true}
+ dismissAriaLabel="Close"
+ >
+
+ New
+
+
+
+ ),
+ },
+ { type: 'link', text: 'Spot requests', href: '#/spot_requests' },
+ { type: 'link', text: 'Reserved instances', href: '#/reserved_instances' },
+ { type: 'link', text: 'Dedicated hosts', href: '#/dedicated_hosts' },
+ {
+ type: 'link',
+ text: 'Scheduled instances',
+ href: '#/scheduled_instances',
+ info: (
+
+
+ We are improving the way to create scheduled instances.{' '}
+
+ Learn more
+
+ >
+ }
+ renderWithPortal={true}
+ dismissAriaLabel="Close"
+ >
+
+ Beta
+
+
+
+ ),
+ },
+ { type: 'link', text: 'Capacity reservations', href: '#/capacity_reservations' },
+ ],
+ },
+ {
+ text: 'Images',
+ type: 'section',
+ defaultExpanded: false,
+ items: [
+ { type: 'link', text: 'AMIs', href: '#/amis' },
+ { type: 'link', text: 'Bundle tasks', href: '#/bundle_tasks' },
+ ],
+ },
+ {
+ text: 'Elastic block store',
+ type: 'section',
+ defaultExpanded: false,
+ items: [
+ { type: 'link', text: 'Volumes', href: '#/volumes' },
+ { type: 'link', text: 'Snapshots', href: '#/snapshots' },
+ { type: 'link', text: 'Lifecycle manager', href: '#/lifecycle_manager' },
+ ],
+ },
+ {
+ text: ' Network & security',
+ type: 'section',
+ defaultExpanded: false,
+ items: [
+ { type: 'link', text: 'Security groups', href: '#/security_groups' },
+ { type: 'link', text: 'Elastic IPs', href: '#/elastic_ips' },
+ { type: 'link', text: 'Placement groups', href: '#/placement_groups' },
+ { type: 'link', text: 'Key pairs', href: '#/key_pairs' },
+ { type: 'link', text: 'Network interfaces', href: '#/network_interfaces' },
+ ],
+ },
+ {
+ text: 'Load balancing',
+ type: 'section',
+ defaultExpanded: false,
+ items: [
+ { type: 'link', text: 'Load balancers', href: '#/load_balancers' },
+ { type: 'link', text: 'Target groups', href: '#/target_groups' },
+ ],
+ },
+ {
+ text: 'Auto scaling',
+ type: 'section',
+ defaultExpanded: false,
+ items: [
+ { type: 'link', text: 'Launch configurations', href: '#/launch_configurations' },
+ { type: 'link', text: 'Auto scaling groups', href: '#/auto_scaling_groups' },
+ ],
+ },
+ { type: 'divider' },
+ {
+ type: 'link',
+ href: '#/density_settings',
+ text: 'Density settings',
+ },
+];
+
+export function DashboardSideNavigation() {
+ const [dialogVisible, setDialogVisible] = useState(false);
+ const onFollowHandler: SideNavigationProps['onFollow'] = event => {
+ event.preventDefault();
+ if (event.detail.href === '#/density_settings') {
+ setDialogVisible(true);
+ }
+ };
+
+ return (
+ <>
+
+ {dialogVisible && setDialogVisible(false)} />}
+ >
+ );
+}
diff --git a/pages/demos/pages/dashboard/icons.ts b/pages/demos/pages/dashboard/icons.ts
new file mode 100644
index 0000000000..3293239184
--- /dev/null
+++ b/pages/demos/pages/dashboard/icons.ts
@@ -0,0 +1,32 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+/* eslint-disable @typescript-eslint/no-require-imports */
+export const mixedContent = {
+ light: require('./assets/mixed-content-light.svg').default,
+ dark: require('./assets/mixed-content-dark.svg').default,
+};
+
+export const barChart = {
+ light: require('./assets/bar-chart-light.svg').default,
+ dark: require('./assets/bar-chart-dark.svg').default,
+};
+
+export const lineChart = {
+ light: require('./assets/line-chart-light.svg').default,
+ dark: require('./assets/line-chart-dark.svg').default,
+};
+
+export const list = {
+ light: require('./assets/list-light.svg').default,
+ dark: require('./assets/list-dark.svg').default,
+};
+
+export const pieChart = {
+ light: require('./assets/pie-chart-light.svg').default,
+ dark: require('./assets/pie-chart-dark.svg').default,
+};
+
+export const table = {
+ light: require('./assets/table-light.svg').default,
+ dark: require('./assets/table-dark.svg').default,
+};
diff --git a/pages/demos/pages/dashboard/widgets/account-attributes/index.tsx b/pages/demos/pages/dashboard/widgets/account-attributes/index.tsx
new file mode 100644
index 0000000000..222924a3bd
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/account-attributes/index.tsx
@@ -0,0 +1,47 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import Box from '@cloudscape-design/components/box';
+import Header from '@cloudscape-design/components/header';
+import Link from '@cloudscape-design/components/link';
+
+import { WidgetConfig } from '../interfaces';
+
+function AccountAttributesHeader() {
+ return ;
+}
+
+function AccountAttributesFooter() {
+ return (
+
+
+ Learn more
+
+
+ );
+}
+
+function AccountAttributesContent() {
+ return (
+ <>
+ Supported platforms
+
+ The account supports both the EC2-Classic platform and VPCs in this Region, but the Region does not have a
+ default VPC.
+
+ >
+ );
+}
+
+export const accountAttributes: WidgetConfig = {
+ definition: { defaultRowSpan: 3, defaultColumnSpan: 1 },
+ data: {
+ icon: 'list',
+ title: 'Account attributes',
+ description: 'General info about current account',
+ header: AccountAttributesHeader,
+ content: AccountAttributesContent,
+ footer: AccountAttributesFooter,
+ },
+};
diff --git a/pages/demos/pages/dashboard/widgets/alarms/index.tsx b/pages/demos/pages/dashboard/widgets/alarms/index.tsx
new file mode 100644
index 0000000000..320844c171
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/alarms/index.tsx
@@ -0,0 +1,92 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import Box from '@cloudscape-design/components/box';
+import Button from '@cloudscape-design/components/button';
+import Header from '@cloudscape-design/components/header';
+import Link from '@cloudscape-design/components/link';
+import StatusIndicator, { StatusIndicatorProps } from '@cloudscape-design/components/status-indicator';
+import Table, { TableProps } from '@cloudscape-design/components/table';
+
+import { isVisualRefresh } from '../../../../common/apply-mode';
+import { WidgetConfig } from '../interfaces';
+
+interface Item {
+ name: string;
+ statusText: string;
+ status: StatusIndicatorProps.Type;
+}
+
+function AlarmsHeader() {
+ return (
+
+ View in Cloudwatch
+
+ }
+ >
+ Alarms
+
+ );
+}
+
+function AlarmsFooter() {
+ return (
+
+
+ View all alarms
+
+
+ );
+}
+
+const alarmsDefinition: TableProps- ['columnDefinitions'] = [
+ {
+ id: 'name',
+ header: 'Alarm name',
+ cell: item =>
{item.name},
+ width: 341,
+ isRowHeader: true,
+ },
+ {
+ id: 'status',
+ header: 'Status',
+ cell: ({ statusText, status }) => {statusText} ,
+ width: 164,
+ },
+];
+
+const alarmsItems: TableProps- ['items'] = [
+ { name: 'TargetTracking-table/divstable', statusText: 'In alarm', status: 'warning' },
+ { name: 'TargetTracking-table/divstable', statusText: 'In alarm', status: 'warning' },
+ { name: 'awsroute53-303920aa-0498-4129-a1b7', statusText: 'In alarm', status: 'warning' },
+ { name: 'awsdynamodb-test0mark0test-Consumed-read', statusText: 'Insufficient data', status: 'pending' },
+];
+
+function AlarmsContent() {
+ return (
+
+ );
+}
+
+export const alarms: WidgetConfig = {
+ definition: { defaultRowSpan: 3, defaultColumnSpan: 2 },
+ data: {
+ icon: 'table',
+ title: 'Alarms',
+ description: 'View all your alarms',
+ disableContentPaddings: !isVisualRefresh,
+ header: AlarmsHeader,
+ content: AlarmsContent,
+ footer: AlarmsFooter,
+ },
+};
diff --git a/pages/demos/pages/dashboard/widgets/base-static-widget/index.tsx b/pages/demos/pages/dashboard/widgets/base-static-widget/index.tsx
new file mode 100644
index 0000000000..36a7e795c0
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/base-static-widget/index.tsx
@@ -0,0 +1,27 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import Container from '@cloudscape-design/components/container';
+
+import { WidgetDataType } from '../interfaces';
+
+import * as styles from './styles.module.scss';
+
+export function BaseStaticWidget({ config }: { config: WidgetDataType }) {
+ const Wrapper = config.provider ?? React.Fragment;
+ return (
+
+
+ }
+ fitHeight={true}
+ footer={config.footer && }
+ disableContentPaddings={config.disableContentPaddings}
+ >
+
+
+
+
+ );
+}
diff --git a/pages/demos/pages/dashboard/widgets/base-static-widget/styles.module.scss b/pages/demos/pages/dashboard/widgets/base-static-widget/styles.module.scss
new file mode 100644
index 0000000000..e77b8e1dbb
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/base-static-widget/styles.module.scss
@@ -0,0 +1,5 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+.staticWidget {
+ height: 100%;
+}
diff --git a/pages/demos/pages/dashboard/widgets/base-static-widget/styles.module.scss.d.ts b/pages/demos/pages/dashboard/widgets/base-static-widget/styles.module.scss.d.ts
new file mode 100644
index 0000000000..dea6687ed8
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/base-static-widget/styles.module.scss.d.ts
@@ -0,0 +1,2 @@
+declare const styles: { readonly [key: string]: string };
+export = styles;
diff --git a/pages/demos/pages/dashboard/widgets/chart-commons.tsx b/pages/demos/pages/dashboard/widgets/chart-commons.tsx
new file mode 100644
index 0000000000..6ee143f364
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/chart-commons.tsx
@@ -0,0 +1,49 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+// Rewritten to use @cloudscape-design/components charts instead of @cloudscape-design/chart-components
+import React from 'react';
+
+import Box from '@cloudscape-design/components/box';
+
+export const percentageFormatter = (value: number) => `${(value * 100).toFixed(0)}%`;
+
+export const numberFormatter = (value: number) => {
+ if (Math.abs(value) < 1000) {
+ return value.toString();
+ }
+ return (value / 1000).toFixed(1) + 'k';
+};
+
+export const dateTimeFormatter = (value: Date | number) => {
+ const date = value instanceof Date ? value : new Date(value);
+ return date.toLocaleDateString('en-US', {
+ month: 'short',
+ day: 'numeric',
+ hour: 'numeric',
+ minute: 'numeric',
+ hour12: false,
+ });
+};
+
+export const dateFormatter = (value: Date | number) => {
+ const date = value instanceof Date ? value : new Date(value);
+ return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
+};
+
+export const commonEmptyState = (
+
+ No data available
+
+ There is no data available
+
+
+);
+
+export const commonNoMatchState = (
+
+ No matching data
+
+ There is no matching data to display
+
+
+);
diff --git a/pages/demos/pages/dashboard/widgets/events/data.ts b/pages/demos/pages/dashboard/widgets/events/data.ts
new file mode 100644
index 0000000000..e4e993ab12
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/events/data.ts
@@ -0,0 +1,53 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+export const eventsItems = [
+ {
+ name: 'my-instance-1',
+ id: 'i-b4b5f3b29ac6f0e',
+ type: 'instance-stop',
+ statusText: 'Scheduled',
+ status: 'pending',
+ },
+ {
+ name: 'my-instance-2',
+ id: 'i-f0eb5f329ab4bc6',
+ type: 'instance-stop',
+ statusText: 'Scheduled',
+ status: 'pending',
+ },
+ {
+ name: 'my-instance-3',
+ id: 'i-29ab4bebc6f05f3',
+ type: 'instance-stop',
+ statusText: 'Ongoing',
+ status: 'success',
+ },
+ {
+ name: 'my-instance-4',
+ id: 'i-329ab4bc6f0eb5f',
+ type: 'instance-stop',
+ statusText: 'Ongoing',
+ status: 'success',
+ },
+ {
+ name: 'my-instance-5',
+ id: 'i-b4beb29a5f3c6f0',
+ type: 'instance-stop',
+ statusText: 'Ongoing',
+ status: 'success',
+ },
+ {
+ name: 'my-instance-6',
+ id: 'i-f0eb5f329ab4bc6',
+ type: 'instance-stop',
+ statusText: 'Ongoing',
+ status: 'success',
+ },
+ {
+ name: 'my-instance-7',
+ id: 'i-2029b8187f0e6e',
+ type: 'instance-stop',
+ statusText: 'Ongoing',
+ status: 'success',
+ },
+];
diff --git a/pages/demos/pages/dashboard/widgets/events/index.tsx b/pages/demos/pages/dashboard/widgets/events/index.tsx
new file mode 100644
index 0000000000..e37df1f119
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/events/index.tsx
@@ -0,0 +1,86 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import Box from '@cloudscape-design/components/box';
+import Header from '@cloudscape-design/components/header';
+import Link from '@cloudscape-design/components/link';
+import StatusIndicator, { StatusIndicatorProps } from '@cloudscape-design/components/status-indicator';
+import Table, { TableProps } from '@cloudscape-design/components/table';
+
+import { isVisualRefresh } from '../../../../common/apply-mode';
+import { WidgetConfig } from '../interfaces';
+import { eventsItems } from './data';
+
+function EventsHeader() {
+ return ;
+}
+
+function EventsFooter() {
+ return (
+
+
+ View all events
+
+
+ );
+}
+
+const eventsDefinition: Array> = [
+ {
+ id: 'name',
+ header: 'Event name',
+ cell: item => item.name,
+ minWidth: 135,
+ width: 140,
+ isRowHeader: true,
+ },
+ {
+ id: 'status',
+ header: 'Event status',
+ cell: ({ statusText, status }) => (
+ {statusText}
+ ),
+ minWidth: 120,
+ width: 130,
+ },
+ {
+ id: 'id',
+ header: 'Event ID',
+ cell: item => {item.id},
+ minWidth: 165,
+ width: 170,
+ },
+ {
+ id: 'type',
+ header: 'Event type',
+ cell: item => item.type,
+ minWidth: 130,
+ width: 135,
+ },
+];
+
+export default function EventsContent() {
+ return (
+
+ );
+}
+
+export const events: WidgetConfig = {
+ definition: { defaultRowSpan: 4, defaultColumnSpan: 2 },
+ data: {
+ icon: 'table',
+ title: 'Events',
+ description: 'View your service events',
+ disableContentPaddings: !isVisualRefresh,
+ header: EventsHeader,
+ content: EventsContent,
+ footer: EventsFooter,
+ },
+};
diff --git a/pages/demos/pages/dashboard/widgets/features-spotlight/index.tsx b/pages/demos/pages/dashboard/widgets/features-spotlight/index.tsx
new file mode 100644
index 0000000000..6c04cd1e86
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/features-spotlight/index.tsx
@@ -0,0 +1,80 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import { Alert, SpaceBetween } from '@cloudscape-design/components';
+import Box from '@cloudscape-design/components/box';
+import ColumnLayout from '@cloudscape-design/components/column-layout';
+import Header from '@cloudscape-design/components/header';
+import Link from '@cloudscape-design/components/link';
+
+import { formatReadOnlyRegion } from '../../../../common/aws-region-utils';
+import { WidgetConfig } from '../interfaces';
+
+function FeaturesSpotlightHeader() {
+ return (
+
+ );
+}
+
+function FeaturesSpotlightFooter() {
+ return (
+
+
+ View all posts
+
+
+ );
+}
+
+export function FeaturesSpotlightContent() {
+ return (
+
+
+
+ August 26, 2019
+
+
+ Amazon EC2 Fleet Functionality
+
+
+
+ Amazon EC2 Auto Scaling now lets you provision and automatically scale instances across purchase options,
+ Availability Zones (AZ), and instance families in a single Auto Scaling group (ASG), to optimize scale,
+ performance, and cost.
+
+
+
+ September 9, 2019
+
+
+ Amazon EC2 Hibernation Now Available on Amazon Linux 2
+
+
+
+ Amazon EC2 expands Hibernation support for Amazon Linux 2. You can now hibernate newly launched EC2
+ Instances running Amazon Linux 2, in addition to Amazon Linux and Ubuntu 18.04 LTS OS.
+
+
+
+
+ Provisioning less than 100 GiB of General Purpose (SSD) storage for high throughput workloads could result in
+ higher latencies upon exhaustion of the initial General Purpose (SSD) IO credit balance.
+
+
+ );
+}
+
+export const featuresSpotlight: WidgetConfig = {
+ definition: { defaultRowSpan: 3, defaultColumnSpan: 3 },
+ data: {
+ icon: 'list',
+ title: 'Features spotlight',
+ description: 'Updates on features available in the current region',
+ header: FeaturesSpotlightHeader,
+ content: FeaturesSpotlightContent,
+ footer: FeaturesSpotlightFooter,
+ },
+};
diff --git a/pages/demos/pages/dashboard/widgets/index.ts b/pages/demos/pages/dashboard/widgets/index.ts
new file mode 100644
index 0000000000..3f3a6b6198
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/index.ts
@@ -0,0 +1,13 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+export { BaseStaticWidget } from './base-static-widget';
+export { alarms } from './alarms';
+export { events } from './events';
+export { featuresSpotlight } from './features-spotlight';
+export { instanceLimits } from './instance-limits';
+export { instanceHours } from './instance-hours';
+export { networkTraffic } from './network-traffic';
+export { operationalMetrics } from './operational-metrics';
+export { serviceHealth } from './service-health';
+export { serviceOverview } from './service-overview';
+export { zoneStatus } from './zone-status';
diff --git a/pages/demos/pages/dashboard/widgets/instance-hours/data.ts b/pages/demos/pages/dashboard/widgets/instance-hours/data.ts
new file mode 100644
index 0000000000..eb31d265f8
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/instance-hours/data.ts
@@ -0,0 +1,42 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+export const cpuData = [
+ { date: new Date(2020, 8, 16).getTime(), 'm1.large': 878, 'm1.xlarge': 491, 'm1.medium': 284, 'm1.small': 70 },
+ { date: new Date(2020, 8, 17).getTime(), 'm1.large': 781, 'm1.xlarge': 435, 'm1.medium': 242, 'm1.small': 96 },
+ { date: new Date(2020, 8, 18).getTime(), 'm1.large': 788, 'm1.xlarge': 478, 'm1.medium': 311, 'm1.small': 79 },
+ { date: new Date(2020, 8, 19).getTime(), 'm1.large': 729, 'm1.xlarge': 558, 'm1.medium': 298, 'm1.small': 97 },
+ { date: new Date(2020, 8, 20).getTime(), 'm1.large': 988, 'm1.xlarge': 530, 'm1.medium': 255, 'm1.small': 97 },
+ { date: new Date(2020, 8, 21).getTime(), 'm1.large': 1016, 'm1.xlarge': 445, 'm1.medium': 339, 'm1.small': 70 },
+ { date: new Date(2020, 8, 22).getTime(), 'm1.large': 987, 'm1.xlarge': 549, 'm1.medium': 273, 'm1.small': 62 },
+ { date: new Date(2020, 8, 23).getTime(), 'm1.large': 986, 'm1.xlarge': 518, 'm1.medium': 341, 'm1.small': 67 },
+ { date: new Date(2020, 8, 24).getTime(), 'm1.large': 925, 'm1.xlarge': 454, 'm1.medium': 382, 'm1.small': 68 },
+ { date: new Date(2020, 8, 25).getTime(), 'm1.large': 742, 'm1.xlarge': 538, 'm1.medium': 361, 'm1.small': 70 },
+ { date: new Date(2020, 8, 26).getTime(), 'm1.large': 920, 'm1.xlarge': 486, 'm1.medium': 262, 'm1.small': 91 },
+ { date: new Date(2020, 8, 27).getTime(), 'm1.large': 826, 'm1.xlarge': 457, 'm1.medium': 248, 'm1.small': 76 },
+ { date: new Date(2020, 8, 28).getTime(), 'm1.large': 698, 'm1.xlarge': 534, 'm1.medium': 243, 'm1.small': 66 },
+ { date: new Date(2020, 8, 29).getTime(), 'm1.large': 1003, 'm1.xlarge': 523, 'm1.medium': 393, 'm1.small': 70 },
+ { date: new Date(2020, 8, 30).getTime(), 'm1.large': 811, 'm1.xlarge': 527, 'm1.medium': 353, 'm1.small': 88 },
+];
+
+export const cpuSeries = [
+ {
+ name: 'm1.large',
+ type: 'column',
+ data: cpuData.map(datum => datum['m1.large']),
+ },
+ {
+ name: 'm1.xlarge',
+ type: 'column',
+ data: cpuData.map(datum => datum['m1.xlarge']),
+ },
+ {
+ name: 'm1.medium',
+ type: 'column',
+ data: cpuData.map(datum => datum['m1.medium']),
+ },
+ {
+ name: 'm1.small',
+ type: 'column',
+ data: cpuData.map(datum => datum['m1.small']),
+ },
+] as const;
diff --git a/pages/demos/pages/dashboard/widgets/instance-hours/index.tsx b/pages/demos/pages/dashboard/widgets/instance-hours/index.tsx
new file mode 100644
index 0000000000..7cf58bdec6
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/instance-hours/index.tsx
@@ -0,0 +1,67 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+// Rewritten to use BarChart from @cloudscape-design/components
+import React from 'react';
+
+import BarChart from '@cloudscape-design/components/bar-chart';
+import Header from '@cloudscape-design/components/header';
+
+import { commonEmptyState, commonNoMatchState, dateFormatter, numberFormatter } from '../chart-commons';
+import { WidgetConfig } from '../interfaces';
+import { cpuData } from './data';
+
+function InstanceHoursHeader() {
+ return (
+
+ );
+}
+
+function InstanceHoursContent() {
+ const series = [
+ { title: 'm1.large', type: 'bar' as const, data: cpuData.map(d => ({ x: new Date(d.date), y: d['m1.large'] })) },
+ { title: 'm1.xlarge', type: 'bar' as const, data: cpuData.map(d => ({ x: new Date(d.date), y: d['m1.xlarge'] })) },
+ { title: 'm1.medium', type: 'bar' as const, data: cpuData.map(d => ({ x: new Date(d.date), y: d['m1.medium'] })) },
+ { title: 'm1.small', type: 'bar' as const, data: cpuData.map(d => ({ x: new Date(d.date), y: d['m1.small'] })) },
+ ];
+
+ return (
+ new Date(d.date))}
+ yDomain={[0, 2000]}
+ stackedBars={true}
+ i18nStrings={{
+ xTickFormatter: dateFormatter,
+ yTickFormatter: numberFormatter,
+ filterLabel: 'Filter displayed instance types',
+ filterPlaceholder: 'Filter instance types',
+ filterSelectedAriaLabel: 'selected',
+ legendAriaLabel: 'Legend',
+ chartAriaRoleDescription: 'bar chart',
+ xAxisAriaRoleDescription: 'x axis',
+ yAxisAriaRoleDescription: 'y axis',
+ }}
+ ariaLabel="Instance hours"
+ fitHeight={true}
+ height={300}
+ xTitle="Date"
+ yTitle="Total instance hours"
+ empty={commonEmptyState}
+ noMatch={commonNoMatchState}
+ />
+ );
+}
+
+export const instanceHours: WidgetConfig = {
+ definition: { defaultRowSpan: 4, defaultColumnSpan: 2, minRowSpan: 3 },
+ data: {
+ icon: 'barChart',
+ title: 'Instance hours',
+ description: 'Daily instance hours by instance type',
+ header: InstanceHoursHeader,
+ content: InstanceHoursContent,
+ staticMinHeight: 560,
+ },
+};
diff --git a/pages/demos/pages/dashboard/widgets/instance-limits/index.tsx b/pages/demos/pages/dashboard/widgets/instance-limits/index.tsx
new file mode 100644
index 0000000000..2e20d283e9
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/instance-limits/index.tsx
@@ -0,0 +1,119 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { createContext, Dispatch, SetStateAction, useContext, useState } from 'react';
+
+import Box from '@cloudscape-design/components/box';
+import Button from '@cloudscape-design/components/button';
+import Header from '@cloudscape-design/components/header';
+import Link from '@cloudscape-design/components/link';
+import StatusIndicator, { StatusIndicatorProps } from '@cloudscape-design/components/status-indicator';
+import Table, { TableProps } from '@cloudscape-design/components/table';
+
+import { isVisualRefresh } from '../../../../common/apply-mode';
+import { WidgetConfig } from '../interfaces';
+
+const WidgetContext = createContext<[string | null, Dispatch>]>([
+ null,
+ () => {
+ // do nothing
+ },
+]);
+
+function InstanceLimitsProvider({ children }: { children: React.ReactNode }) {
+ const state = useState(null);
+ return {children} ;
+}
+
+function InstanceLimitsHeader() {
+ const [selectedId] = useContext(WidgetContext);
+ return (
+
+ Request limit increase
+
+ }
+ >
+ On-demand instance limits
+
+ );
+}
+
+function InstanceLimitsFooter() {
+ return (
+
+
+ View all instance limits
+
+
+ );
+}
+
+const instanceLimitsItems = [
+ { name: 'Running on-demand all G instances', statusText: '900 used/920 limit', status: 'warning' },
+ { name: 'Running on-demand all P instances', statusText: '692 used/692 limit', status: 'warning' },
+ { name: 'Running on-demand all Standard instances', statusText: '50 used/10304 limit', status: 'success' },
+ { name: 'Running on-demand all F instances', statusText: '0 used/176 limit', status: 'success' },
+];
+const instanceLimitsDefinition: Array> = [
+ {
+ id: 'name',
+ header: 'Name',
+ cell: item => item.name,
+ width: 320,
+ isRowHeader: true,
+ },
+ {
+ id: 'status',
+ header: 'Status (usage/limit)',
+ cell: ({ statusText, status }) => (
+ {statusText}
+ ),
+ },
+];
+
+export default function InstanceLimitsContent() {
+ const [selectedId, setSelectedId] = useContext(WidgetContext);
+
+ return (
+ setSelectedId(event.detail.selectedItems[0].name)}
+ ariaLabels={{
+ itemSelectionLabel: (data, row) => `select ${row.name}`,
+ allItemsSelectionLabel: () => 'select all',
+ selectionGroupLabel: 'On-demand instance limit selection',
+ }}
+ />
+ );
+}
+
+export const instanceLimits: WidgetConfig = {
+ definition: { defaultRowSpan: 3, defaultColumnSpan: 2 },
+ data: {
+ icon: 'table',
+ title: 'Instance limits',
+ description: 'Current utilization of instance types',
+ disableContentPaddings: !isVisualRefresh,
+ provider: InstanceLimitsProvider,
+ header: InstanceLimitsHeader,
+ content: InstanceLimitsContent,
+ footer: InstanceLimitsFooter,
+ },
+};
diff --git a/pages/demos/pages/dashboard/widgets/interfaces.ts b/pages/demos/pages/dashboard/widgets/interfaces.ts
new file mode 100644
index 0000000000..de2578eec6
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/interfaces.ts
@@ -0,0 +1,26 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+// @cloudscape-design/board-components not available — BoardProps replaced with local type
+import React from 'react';
+
+import * as icons from '../icons';
+
+export interface WidgetDataType {
+ icon: keyof typeof icons;
+ title: string;
+ description: string;
+ disableContentPaddings?: boolean;
+ provider?: React.JSXElementConstructor<{ children: React.ReactElement }>;
+ header: React.JSXElementConstructor>;
+ content: React.JSXElementConstructor>;
+ footer?: React.JSXElementConstructor>;
+ staticMinHeight?: number;
+}
+
+export interface DashboardWidgetItem {
+ id: string;
+ definition?: { defaultRowSpan?: number; defaultColumnSpan?: number; minRowSpan?: number; minColumnSpan?: number };
+ data: WidgetDataType;
+}
+
+export type WidgetConfig = Pick;
diff --git a/pages/demos/pages/dashboard/widgets/network-traffic/data.ts b/pages/demos/pages/dashboard/widgets/network-traffic/data.ts
new file mode 100644
index 0000000000..3c051855f3
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/network-traffic/data.ts
@@ -0,0 +1,132 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+const networkTrafficData = [
+ { date: new Date(1600984800000), 'i-03736447': 68003, 'i-06f70d90': 46560, 'i-02924ba6': 25865, 'i-0e36f15f': 21350 },
+ { date: new Date(1600985700000), 'i-03736447': 67382, 'i-06f70d90': 49666, 'i-02924ba6': 24253, 'i-0e36f15f': 21055 },
+ { date: new Date(1600986600000), 'i-03736447': 74322, 'i-06f70d90': 47996, 'i-02924ba6': 25269, 'i-0e36f15f': 20188 },
+ { date: new Date(1600987500000), 'i-03736447': 72499, 'i-06f70d90': 46020, 'i-02924ba6': 20308, 'i-0e36f15f': 22246 },
+ { date: new Date(1600988400000), 'i-03736447': 69616, 'i-06f70d90': 46568, 'i-02924ba6': 24315, 'i-0e36f15f': 21998 },
+ {
+ date: new Date(1600989300000),
+ 'i-03736447': 70055,
+ 'i-06f70d90': 47205,
+ 'i-02924ba6': 25181,
+ 'i-0e36f15f': 190307,
+ },
+ {
+ date: new Date(1600990200000),
+ 'i-03736447': 74055,
+ 'i-06f70d90': 46329,
+ 'i-02924ba6': 23027,
+ 'i-0e36f15f': 180385,
+ },
+ { date: new Date(1600991100000), 'i-03736447': 73420, 'i-06f70d90': 49614, 'i-02924ba6': 20500, 'i-0e36f15f': 21715 },
+ { date: new Date(1600992000000), 'i-03736447': 65713, 'i-06f70d90': 49792, 'i-02924ba6': 25369, 'i-0e36f15f': 20760 },
+ { date: new Date(1600992900000), 'i-03736447': 68954, 'i-06f70d90': 48284, 'i-02924ba6': 23369, 'i-0e36f15f': 21803 },
+ { date: new Date(1600993800000), 'i-03736447': 74289, 'i-06f70d90': 47697, 'i-02924ba6': 24184, 'i-0e36f15f': 21356 },
+ { date: new Date(1600994700000), 'i-03736447': 76521, 'i-06f70d90': 46463, 'i-02924ba6': 22768, 'i-0e36f15f': 20269 },
+ { date: new Date(1600995600000), 'i-03736447': 78337, 'i-06f70d90': 47384, 'i-02924ba6': 21965, 'i-0e36f15f': 20700 },
+ {
+ date: new Date(1600996500000),
+ 'i-03736447': 105029,
+ 'i-06f70d90': 47986,
+ 'i-02924ba6': 23129,
+ 'i-0e36f15f': 20881,
+ },
+ {
+ date: new Date(1600997400000),
+ 'i-03736447': 104961,
+ 'i-06f70d90': 49529,
+ 'i-02924ba6': 23483,
+ 'i-0e36f15f': 20082,
+ },
+ {
+ date: new Date(1600998300000),
+ 'i-03736447': 102044,
+ 'i-06f70d90': 48146,
+ 'i-02924ba6': 21048,
+ 'i-0e36f15f': 21947,
+ },
+ {
+ date: new Date(1600999200000),
+ 'i-03736447': 120062,
+ 'i-06f70d90': 46001,
+ 'i-02924ba6': 23181,
+ 'i-0e36f15f': 20636,
+ },
+ {
+ date: new Date(1601000100000),
+ 'i-03736447': 140112,
+ 'i-06f70d90': 46649,
+ 'i-02924ba6': 22824,
+ 'i-0e36f15f': 21470,
+ },
+ {
+ date: new Date(1601001000000),
+ 'i-03736447': 138935,
+ 'i-06f70d90': 47895,
+ 'i-02924ba6': 24827,
+ 'i-0e36f15f': 21910,
+ },
+ {
+ date: new Date(1601001900000),
+ 'i-03736447': 139103,
+ 'i-06f70d90': 47977,
+ 'i-02924ba6': 23661,
+ 'i-0e36f15f': 20620,
+ },
+ {
+ date: new Date(1601002800000),
+ 'i-03736447': 132378,
+ 'i-06f70d90': 46908,
+ 'i-02924ba6': 21907,
+ 'i-0e36f15f': 20412,
+ },
+ {
+ date: new Date(1601003700000),
+ 'i-03736447': 112884,
+ 'i-06f70d90': 46496,
+ 'i-02924ba6': 59489,
+ 'i-0e36f15f': 22751,
+ },
+ {
+ date: new Date(1601004600000),
+ 'i-03736447': 74689,
+ 'i-06f70d90': 47991,
+ 'i-02924ba6': 190975,
+ 'i-0e36f15f': 21277,
+ },
+ { date: new Date(1601005500000), 'i-03736447': 68451, 'i-06f70d90': 48881, 'i-02924ba6': 22827, 'i-0e36f15f': 21625 },
+ { date: new Date(1601006400000), 'i-03736447': 66404, 'i-06f70d90': 48833, 'i-02924ba6': 20384, 'i-0e36f15f': 21267 },
+ { date: new Date(1601007300000), 'i-03736447': 67037, 'i-06f70d90': 46665, 'i-02924ba6': 23365, 'i-0e36f15f': 21555 },
+ { date: new Date(1601008200000), 'i-03736447': 70425, 'i-06f70d90': 49552, 'i-02924ba6': 23635, 'i-0e36f15f': 21072 },
+ { date: new Date(1601009100000), 'i-03736447': 65583, 'i-06f70d90': 49013, 'i-02924ba6': 22462, 'i-0e36f15f': 21418 },
+ { date: new Date(1601010000000), 'i-03736447': 67361, 'i-06f70d90': 48834, 'i-02924ba6': 23409, 'i-0e36f15f': 20808 },
+ { date: new Date(1601010900000), 'i-03736447': 66421, 'i-06f70d90': 49644, 'i-02924ba6': 20730, 'i-0e36f15f': 22795 },
+ { date: new Date(1601011800000), 'i-03736447': 69670, 'i-06f70d90': 48032, 'i-02924ba6': 21257, 'i-0e36f15f': 20953 },
+ { date: new Date(1601012700000), 'i-03736447': 68534, 'i-06f70d90': 49544, 'i-02924ba6': 23190, 'i-0e36f15f': 20834 },
+ { date: new Date(1601013600000), 'i-03736447': 71507, 'i-06f70d90': 49043, 'i-02924ba6': 23497, 'i-0e36f15f': 22604 },
+];
+
+export const networkTrafficSeries = [
+ {
+ title: 'i-03736447',
+ type: 'line' as const,
+ data: networkTrafficData.map(d => ({ x: d.date, y: d['i-03736447'] })),
+ },
+ {
+ title: 'i-06f70d90',
+ type: 'line' as const,
+ data: networkTrafficData.map(d => ({ x: d.date, y: d['i-06f70d90'] })),
+ },
+ {
+ title: 'i-02924ba6',
+ type: 'line' as const,
+ data: networkTrafficData.map(d => ({ x: d.date, y: d['i-02924ba6'] })),
+ },
+ {
+ title: 'i-0e36f15f',
+ type: 'line' as const,
+ data: networkTrafficData.map(d => ({ x: d.date, y: d['i-0e36f15f'] })),
+ },
+];
diff --git a/pages/demos/pages/dashboard/widgets/network-traffic/index.tsx b/pages/demos/pages/dashboard/widgets/network-traffic/index.tsx
new file mode 100644
index 0000000000..cdf3b5bc56
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/network-traffic/index.tsx
@@ -0,0 +1,64 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+// Rewritten to use LineChart from @cloudscape-design/components
+import React from 'react';
+
+import Header from '@cloudscape-design/components/header';
+import LineChart from '@cloudscape-design/components/line-chart';
+
+import { commonEmptyState, commonNoMatchState, dateTimeFormatter, numberFormatter } from '../chart-commons';
+import { WidgetConfig } from '../interfaces';
+import { networkTrafficSeries } from './data';
+
+function NetworkTrafficHeader() {
+ return (
+
+ );
+}
+
+function NetworkTrafficContent() {
+ return (
+
+ );
+}
+
+export const networkTraffic: WidgetConfig = {
+ definition: { defaultRowSpan: 4, defaultColumnSpan: 2, minRowSpan: 3 },
+ data: {
+ icon: 'lineChart',
+ title: 'Network traffic',
+ description: 'Incoming and outgoing network traffic',
+ header: NetworkTrafficHeader,
+ content: NetworkTrafficContent,
+ staticMinHeight: 560,
+ },
+};
+
+export default NetworkTrafficContent;
diff --git a/pages/demos/pages/dashboard/widgets/operational-metrics/chart.tsx b/pages/demos/pages/dashboard/widgets/operational-metrics/chart.tsx
new file mode 100644
index 0000000000..f9dcd091f5
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/operational-metrics/chart.tsx
@@ -0,0 +1,35 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import BarChart, { BarChartProps } from '@cloudscape-design/components/bar-chart';
+
+const costs: BarChartProps['series'] = [
+ {
+ type: 'bar',
+ title: 'Value',
+ data: [
+ { x: 'A', y: 170.25 },
+ { x: 'B', y: 116.07 },
+ { x: 'C', y: 54.19 },
+ { x: 'D', y: 15.18 },
+ { x: 'E', y: 15.03 },
+ { x: 'F', y: 49.85 },
+ ],
+ },
+];
+
+export function BreakdownChart() {
+ return (
+
+ );
+}
diff --git a/pages/demos/pages/dashboard/widgets/operational-metrics/index.tsx b/pages/demos/pages/dashboard/widgets/operational-metrics/index.tsx
new file mode 100644
index 0000000000..dbb1fab511
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/operational-metrics/index.tsx
@@ -0,0 +1,159 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { createContext, useContext, useState } from 'react';
+
+import Button from '@cloudscape-design/components/button';
+import FormField from '@cloudscape-design/components/form-field';
+import Header from '@cloudscape-design/components/header';
+import KeyValuePairs from '@cloudscape-design/components/key-value-pairs';
+import Select from '@cloudscape-design/components/select';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import StatusIndicator from '@cloudscape-design/components/status-indicator';
+
+import { EmptyState } from '../../components/empty-state';
+import { ResponsiveLayout } from '../../components/responsive-layout';
+import { WidgetConfig } from '../interfaces';
+import { BreakdownChart } from './chart';
+import { allContent, Content, WidgetPreferences } from './preferences';
+
+interface OperationalWidgetContextType {
+ visibleContent: ReadonlyArray;
+ openPreferences: () => void;
+}
+
+const OperationalWidgetContext = createContext({
+ visibleContent: [],
+ openPreferences: () => {
+ // do nothing
+ },
+});
+
+function OperationalMetricsProvider({ children }: { children: React.ReactElement }) {
+ const [preferencesVisible, setPreferencesVisible] = useState(false);
+ const [visibleContent, setVisibleContent] = useState>(allContent);
+ return (
+ setPreferencesVisible(true) }}>
+ {React.cloneElement(React.Children.only(children), {
+ removeConfirmationText: 'Operational metrics',
+ actions: [{ text: 'Preferences', onClick: () => setPreferencesVisible(true) }],
+ })}
+ {preferencesVisible && (
+ {
+ setVisibleContent(visibleContent);
+ setPreferencesVisible(false);
+ }}
+ onDismiss={() => setPreferencesVisible(false)}
+ />
+ )}
+
+ );
+}
+
+function OperationalMetricsHeader() {
+ return (
+
+ View in Cloudwatch
+
+ }
+ >
+ Operational metrics
+
+ );
+}
+
+function OperationalMetricsContent() {
+ const { visibleContent, openPreferences } = useContext(OperationalWidgetContext);
+ const someCostVisible = (['status', 'running', 'monitoring', 'issues'] as const).some(content =>
+ visibleContent.includes(content)
+ );
+ if (visibleContent.length <= 0) {
+ return (
+ Open preferences}
+ />
+ );
+ }
+ return (
+
+ {
+ /*noop*/
+ }}
+ />
+
+ }
+ >
+ {someCostVisible && (
+ Overview}>
+
+ Running,
+ },
+ ]
+ : []),
+ ...(visibleContent.includes('running')
+ ? [
+ {
+ label: 'Running resources',
+ value: '120',
+ },
+ ]
+ : []),
+ ...(visibleContent.includes('monitoring')
+ ? [
+ {
+ label: 'Monitoring',
+ value: 'Enabled',
+ },
+ ]
+ : []),
+ ...(visibleContent.includes('issues')
+ ? [
+ {
+ label: 'Open issues',
+ value: '0',
+ },
+ ]
+ : []),
+ ]}
+ />
+
+
+ )}
+ {visibleContent.includes('breakdown') && (
+ Breakdown}>
+
+
+ )}
+
+ );
+}
+
+export const operationalMetrics: WidgetConfig = {
+ definition: { defaultRowSpan: 4, defaultColumnSpan: 3 },
+ data: {
+ icon: 'mixedContent',
+ title: 'Operational metrics',
+ description: 'Operational metrics of your service',
+ provider: OperationalMetricsProvider,
+ header: OperationalMetricsHeader,
+ content: OperationalMetricsContent,
+ },
+};
diff --git a/pages/demos/pages/dashboard/widgets/operational-metrics/preferences.module.scss b/pages/demos/pages/dashboard/widgets/operational-metrics/preferences.module.scss
new file mode 100644
index 0000000000..4532380d16
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/operational-metrics/preferences.module.scss
@@ -0,0 +1,18 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+
+@use '~@cloudscape-design/design-tokens' as cs;
+
+.displayPreference {
+ display: flex;
+}
+
+.displayPreferenceLabel {
+ margin-right: auto;
+ margin-left: cs.$space-scaled-xl;
+}
+
+.displayPreferenceGroup {
+ margin-left: 0;
+ color: cs.$color-text-group-label;
+}
diff --git a/pages/demos/pages/dashboard/widgets/operational-metrics/preferences.module.scss.d.ts b/pages/demos/pages/dashboard/widgets/operational-metrics/preferences.module.scss.d.ts
new file mode 100644
index 0000000000..dea6687ed8
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/operational-metrics/preferences.module.scss.d.ts
@@ -0,0 +1,2 @@
+declare const styles: { readonly [key: string]: string };
+export = styles;
diff --git a/pages/demos/pages/dashboard/widgets/operational-metrics/preferences.tsx b/pages/demos/pages/dashboard/widgets/operational-metrics/preferences.tsx
new file mode 100644
index 0000000000..893d960978
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/operational-metrics/preferences.tsx
@@ -0,0 +1,152 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useState } from 'react';
+
+import { useId } from '../../../../use-id-polyfill';
+import Box from '@cloudscape-design/components/box';
+import Button from '@cloudscape-design/components/button';
+import ColumnLayout from '@cloudscape-design/components/column-layout';
+import Modal from '@cloudscape-design/components/modal';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import Toggle from '@cloudscape-design/components/toggle';
+
+import * as styles from './preferences.module.scss';
+
+export const allContent = ['status', 'running', 'monitoring', 'issues', 'breakdown'] as const;
+
+export type Content = (typeof allContent)[number];
+
+interface PreferencesControlProps {
+ label: string;
+ isGroup?: boolean;
+ toggle?: (id: string) => React.ReactNode;
+}
+
+function PreferencesControl({ label, toggle, isGroup }: PreferencesControlProps) {
+ const id = useId();
+ return (
+
+
+ {label}
+
+ {toggle?.(id)}
+
+ );
+}
+
+interface WidgetPreferencesProps {
+ preferences: ReadonlyArray;
+ onDismiss: () => void;
+ onConfirm: (visibleContent: ReadonlyArray) => void;
+}
+
+const metricItems = ['status', 'running', 'monitoring', 'issues'] as const;
+
+export function WidgetPreferences({ onConfirm, onDismiss, preferences }: WidgetPreferencesProps) {
+ const [pendingPreferences, setPendingPreferences] = useState(preferences);
+ function toggle(content: Content, checked: boolean) {
+ setPendingPreferences(pendingPreferences => {
+ const newState = pendingPreferences.slice();
+ if (checked) {
+ newState.push(content);
+ } else {
+ newState.splice(newState.indexOf(content), 1);
+ }
+ return newState;
+ });
+ }
+
+ const visibleMetrics = metricItems.map(content => pendingPreferences.includes(content));
+ const someCostVisible = visibleMetrics.some(visible => visible);
+ const allCostsSame = visibleMetrics.every(visible => visible) || visibleMetrics.every(visible => !visible);
+
+ return (
+
+
+
+ Cancel
+
+ onConfirm(pendingPreferences)}>
+ Confirm
+
+
+
+ }
+ onDismiss={onDismiss}
+ >
+
+ Select visible content
+ (
+ metricItems.forEach(item => toggle(item, event.detail.checked))}
+ />
+ )}
+ />
+ (
+ toggle('status', event.detail.checked)}
+ />
+ )}
+ />
+ (
+ toggle('running', event.detail.checked)}
+ />
+ )}
+ />
+ (
+ toggle('monitoring', event.detail.checked)}
+ />
+ )}
+ />
+ (
+ toggle('issues', event.detail.checked)}
+ />
+ )}
+ />
+ (
+ toggle('breakdown', event.detail.checked)}
+ />
+ )}
+ >
+
+
+ );
+}
diff --git a/pages/demos/pages/dashboard/widgets/service-health/help-content.tsx b/pages/demos/pages/dashboard/widgets/service-health/help-content.tsx
new file mode 100644
index 0000000000..6772229a12
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/service-health/help-content.tsx
@@ -0,0 +1,25 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import HelpPanel from '@cloudscape-design/components/help-panel';
+
+import { ExternalLinkGroup } from '../../../commons';
+
+export function ServiceHealthInfo() {
+ return (
+ Service health}
+ footer={
+
+ }
+ >
+ Amazon Web Services publishes our most up-to-the-minute information on service availability
+
+ );
+}
diff --git a/pages/demos/pages/dashboard/widgets/service-health/index.tsx b/pages/demos/pages/dashboard/widgets/service-health/index.tsx
new file mode 100644
index 0000000000..65089846f5
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/service-health/index.tsx
@@ -0,0 +1,52 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import Box from '@cloudscape-design/components/box';
+import ColumnLayout from '@cloudscape-design/components/column-layout';
+import Header from '@cloudscape-design/components/header';
+import StatusIndicator from '@cloudscape-design/components/status-indicator';
+
+import { formatReadOnlyRegion } from '../../../../common/aws-region-utils';
+import { InfoLink, useHelpPanel } from '../../../commons';
+import { WidgetConfig } from '../interfaces';
+import { ServiceHealthInfo } from './help-content';
+
+function ServiceHealthHeader() {
+ const loadHelpPanelContent = useHelpPanel();
+ return (
+ loadHelpPanelContent( )} />
+ }
+ >
+ Service health
+
+ );
+}
+
+export default function ServiceHealthContent() {
+ return (
+
+
+
Region
+
{formatReadOnlyRegion('us-east-1')}
+
+
+ Status
+ Service is operating normally
+
+
+ );
+}
+export const serviceHealth: WidgetConfig = {
+ definition: { defaultRowSpan: 2, defaultColumnSpan: 1 },
+ data: {
+ icon: 'list',
+ title: 'Service Health',
+ description: 'General information about service health',
+ header: ServiceHealthHeader,
+ content: ServiceHealthContent,
+ },
+};
diff --git a/pages/demos/pages/dashboard/widgets/service-overview/index.tsx b/pages/demos/pages/dashboard/widgets/service-overview/index.tsx
new file mode 100644
index 0000000000..df0f20e2bb
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/service-overview/index.tsx
@@ -0,0 +1,70 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import Header from '@cloudscape-design/components/header';
+import KeyValuePairs from '@cloudscape-design/components/key-value-pairs';
+import Link from '@cloudscape-design/components/link';
+
+import { formatReadOnlyRegion } from '../../../../common/aws-region-utils';
+import { WidgetConfig } from '../interfaces';
+
+function ServiceOverviewHeader() {
+ return (
+
+ Service overview - new
+
+ );
+}
+
+function ServiceOverviewWidget() {
+ return (
+
+ 14
+
+ ),
+ },
+ {
+ label: 'Volumes',
+ value: (
+
+ 126
+
+ ),
+ },
+ {
+ label: 'Security groups',
+ value: (
+
+ 116
+
+ ),
+ },
+ {
+ label: 'Load balancers',
+ value: (
+
+ 28
+
+ ),
+ },
+ ]}
+ />
+ );
+}
+export const serviceOverview: WidgetConfig = {
+ definition: { defaultRowSpan: 2, defaultColumnSpan: 3 },
+ data: {
+ icon: 'list',
+ title: 'Service overview',
+ description: 'Overview of all your resources',
+ header: ServiceOverviewHeader,
+ content: ServiceOverviewWidget,
+ },
+};
diff --git a/pages/demos/pages/dashboard/widgets/zone-status/index.tsx b/pages/demos/pages/dashboard/widgets/zone-status/index.tsx
new file mode 100644
index 0000000000..e01ec91a4d
--- /dev/null
+++ b/pages/demos/pages/dashboard/widgets/zone-status/index.tsx
@@ -0,0 +1,61 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+// Rewritten to use PieChart from @cloudscape-design/components
+import React from 'react';
+
+import Header from '@cloudscape-design/components/header';
+import PieChart from '@cloudscape-design/components/pie-chart';
+
+import { percentageFormatter } from '../chart-commons';
+import { WidgetConfig } from '../interfaces';
+
+function ZoneStatusHeader() {
+ return (
+
+ );
+}
+
+function ZoneStatusContent() {
+ return (
+ `${datum.value} zones, ${percentageFormatter(datum.value / sum)}`}
+ detailPopoverContent={(datum, sum) => [
+ { key: 'Zone count', value: datum.value },
+ { key: 'Percentage', value: percentageFormatter(datum.value / sum) },
+ ]}
+ ariaLabel="Zone status chart"
+ ariaDescription="Pie chart summarizing the status of all zones."
+ fitHeight={true}
+ size="large"
+ i18nStrings={{
+ filterLabel: 'Filter displayed data',
+ filterPlaceholder: 'Filter data',
+ filterSelectedAriaLabel: 'selected',
+ legendAriaLabel: 'Legend',
+ chartAriaRoleDescription: 'pie chart',
+ segmentAriaRoleDescription: 'segment',
+ detailPopoverDismissAriaLabel: 'Dismiss',
+ }}
+ empty={No data available }
+ noMatch={No matching data }
+ />
+ );
+}
+
+export const zoneStatus: WidgetConfig = {
+ definition: { defaultRowSpan: 4, defaultColumnSpan: 2, minRowSpan: 3 },
+ data: {
+ icon: 'pieChart',
+ title: 'Zone status',
+ description: 'Zone status report',
+ header: ZoneStatusHeader,
+ content: ZoneStatusContent,
+ staticMinHeight: 450,
+ },
+};
diff --git a/pages/demos/pages/delete-one-click/app.tsx b/pages/demos/pages/delete-one-click/app.tsx
new file mode 100644
index 0000000000..fe270121d4
--- /dev/null
+++ b/pages/demos/pages/delete-one-click/app.tsx
@@ -0,0 +1,125 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useEffect, useState } from 'react';
+
+import BreadcrumbGroup from '@cloudscape-design/components/breadcrumb-group';
+import Button from '@cloudscape-design/components/button';
+import Container from '@cloudscape-design/components/container';
+import Form from '@cloudscape-design/components/form';
+import Header from '@cloudscape-design/components/header';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import SplitPanel from '@cloudscape-design/components/split-panel';
+import TagEditor, { TagEditorProps } from '@cloudscape-design/components/tag-editor';
+
+import { resourceManageTagsBreadcrumbs } from '../../common/breadcrumbs';
+import { tagEditorI18nStrings } from '../../i18n-strings/tag-editor';
+import {
+ DemoTopNavigation,
+ GlobalSplitPanelContent,
+ Navigation,
+ Notifications,
+ useGlobalSplitPanel,
+} from '../commons/common-components';
+import { CustomAppLayout } from '../commons/common-components';
+import { Info } from './components/info';
+import { loadTagKeys, loadTags, loadTagValues } from './utils';
+
+import '../../styles/top-navigation.scss';
+
+export function App() {
+ const [tags, setTags] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [isValid, setIsValid] = useState(true);
+ const { splitPanelOpen, onSplitPanelToggle, splitPanelSize, onSplitPanelResize, splitPanelPreferences } =
+ useGlobalSplitPanel();
+
+ useEffect(() => {
+ if (loading) {
+ loadTags().then(tags => {
+ setLoading(false);
+ setTags(tags);
+ });
+ }
+ }, [loading]);
+
+ const onChange = ({ tags, valid }: TagEditorProps.ChangeDetail) => {
+ setTags([...tags]);
+ setIsValid(valid);
+ };
+
+ const onSubmit = (event: React.FormEvent) => {
+ if (isValid) {
+ setTags(tags.map(tag => ({ ...tag, existing: true })).filter(tag => !tag.markedForRemoval));
+ }
+ event.preventDefault();
+ };
+
+ const onCancel = () => {
+ setLoading(true);
+ };
+
+ return (
+ <>
+
+
+
+
+ }
+ content={
+
+
+
+
+ }
+ >
+
+ Tags
+
+ }
+ >
+ onChange(e.detail)}
+ keysRequest={loadTagKeys}
+ valuesRequest={loadTagValues}
+ loading={loading}
+ i18nStrings={tagEditorI18nStrings}
+ />
+
+
+
+
+ }
+ breadcrumbs={
+
+ }
+ navigation={ }
+ toolsHide={true}
+ notifications={ }
+ />
+ >
+ );
+}
diff --git a/pages/demos/pages/delete-one-click/components/info.tsx b/pages/demos/pages/delete-one-click/components/info.tsx
new file mode 100644
index 0000000000..0107035466
--- /dev/null
+++ b/pages/demos/pages/delete-one-click/components/info.tsx
@@ -0,0 +1,9 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import Alert from '@cloudscape-design/components/alert';
+
+export const Info = () => (
+ This page illustrates the use of the one-click delete pattern.
+);
diff --git a/pages/demos/pages/delete-one-click/utils.ts b/pages/demos/pages/delete-one-click/utils.ts
new file mode 100644
index 0000000000..ca2ce13612
--- /dev/null
+++ b/pages/demos/pages/delete-one-click/utils.ts
@@ -0,0 +1,31 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import { Tag, TagsResource } from '../../resources/types';
+
+export async function loadTags() {
+ const isUserTag = (tag: Tag) => tag.key.indexOf('aws:') !== 0;
+ const mapExistingTag = (tag: Tag) => ({ ...tag, existing: true });
+
+ const { ResourceTagMappingList } = await window.FakeServer.GetResources();
+ const tags = ResourceTagMappingList.reduce(
+ (allTags: { key: string; value: string }[], resourceTagMapping: { Tags: { key: string; value: string }[] }) => [
+ ...allTags,
+ ...resourceTagMapping.Tags,
+ ],
+ []
+ )
+ .filter(isUserTag)
+ .map(mapExistingTag);
+
+ return tags;
+}
+
+export async function loadTagKeys() {
+ const { TagKeys } = await window.FakeServer.GetTagKeys();
+ return TagKeys;
+}
+
+export async function loadTagValues(key: string) {
+ const { TagValues } = await window.FakeServer.GetTagValues(key as keyof TagsResource['valueMap']);
+ return TagValues;
+}
diff --git a/pages/demos/pages/delete-with-additional-confirmation/app.tsx b/pages/demos/pages/delete-with-additional-confirmation/app.tsx
new file mode 100644
index 0000000000..b901471431
--- /dev/null
+++ b/pages/demos/pages/delete-with-additional-confirmation/app.tsx
@@ -0,0 +1,89 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useEffect, useState } from 'react';
+
+import INSTANCES from '../../resources/ec2-instances';
+import { EC2Instance } from '../../resources/types';
+import fakeDelay from '../commons/fake-delay';
+import useLocationHash from '../delete-with-simple-confirmation/use-location-hash';
+import useNotifications from '../delete-with-simple-confirmation/use-notifications';
+import { DeleteModal } from './components/delete-modal';
+import { InstanceDetailsPage } from './components/instance-details-page';
+import { InstancesPage } from './components/instances-page';
+
+const delay = 3000;
+const failingInstances = [INSTANCES[0]];
+
+export function App() {
+ const [instances, setInstances] = useState(INSTANCES);
+ const [selectedItems, setSelectedItems] = useState([]);
+ const [showDeleteModal, setShowDeleteModal] = useState(true);
+
+ const locationHash = useLocationHash();
+ const locationInstance = instances.find(it => it.id === locationHash);
+ const { clearFailed, notifications, notifyDeleted, notifyFailed, notifyInProgress } = useNotifications({
+ resourceName: 'instance',
+ });
+
+ const deletionWillFail = (instance: EC2Instance) => {
+ return !!failingInstances.find(item => item.id === instance.id);
+ };
+
+ const onDeleteInit = () => setShowDeleteModal(true);
+ const onDeleteDiscard = () => setShowDeleteModal(false);
+ const onDeleteConfirm = async () => {
+ const itemsToDelete = locationInstance ? [locationInstance] : selectedItems;
+ const itemsThatWillSucceed = itemsToDelete.filter(item => !deletionWillFail(item));
+ const itemsThatWillFail = itemsToDelete.filter(deletionWillFail);
+ setSelectedItems([]);
+ setShowDeleteModal(false);
+ notifyInProgress(itemsToDelete);
+ await fakeDelay(delay);
+ const deletedIds = new Set(itemsThatWillSucceed.map(({ id }) => id));
+ setInstances(instances => instances.filter(({ id }) => !deletedIds.has(id)));
+ notifyInProgress(itemsThatWillFail);
+ notifyDeleted(itemsThatWillSucceed);
+ await fakeDelay(delay / 2);
+ notifyFailed(itemsThatWillFail, {
+ retry: async instance => {
+ notifyInProgress([instance]);
+ clearFailed(instance);
+ await fakeDelay(delay);
+ setInstances(instances => instances.filter(({ id }) => id !== instance.id));
+ notifyInProgress([]);
+ notifyDeleted([instance]);
+ },
+ });
+ notifyInProgress([]);
+ };
+
+ useEffect(() => {
+ setSelectedItems([]);
+ }, [locationHash]);
+
+ useEffect(() => {
+ setSelectedItems(INSTANCES.slice(0, 3));
+ }, []);
+
+ return (
+ <>
+ {locationInstance ? (
+
+ ) : (
+
+ )}
+
+ >
+ );
+}
diff --git a/pages/demos/pages/delete-with-additional-confirmation/components/delete-modal.tsx b/pages/demos/pages/delete-with-additional-confirmation/components/delete-modal.tsx
new file mode 100644
index 0000000000..c0e92d75e4
--- /dev/null
+++ b/pages/demos/pages/delete-with-additional-confirmation/components/delete-modal.tsx
@@ -0,0 +1,108 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+import { useEffect, useState } from 'react';
+
+import Alert from '@cloudscape-design/components/alert';
+import Box from '@cloudscape-design/components/box';
+import Button from '@cloudscape-design/components/button';
+import ColumnLayout from '@cloudscape-design/components/column-layout';
+import FormField from '@cloudscape-design/components/form-field';
+import Input from '@cloudscape-design/components/input';
+import Link from '@cloudscape-design/components/link';
+import Modal from '@cloudscape-design/components/modal';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+
+import { EC2Instance } from '../../../resources/types';
+
+interface DeleteModalProps {
+ instances: EC2Instance[];
+ visible: boolean;
+ onDiscard: () => void;
+ onDelete: () => void;
+}
+export function DeleteModal({ instances, visible, onDiscard, onDelete }: DeleteModalProps) {
+ const deleteConsentText = 'confirm';
+
+ const [deleteInputText, setDeleteInputText] = useState('');
+ useEffect(() => {
+ setDeleteInputText('');
+ }, [visible]);
+
+ const inputMatchesConsentText = deleteInputText.toLowerCase() === deleteConsentText;
+
+ const handleDeleteSubmit = (event: React.FormEvent) => {
+ event.preventDefault();
+ if (inputMatchesConsentText) {
+ onDelete();
+ }
+ };
+
+ const isMultiple = instances.length > 1;
+ return (
+
+
+
+ Cancel
+
+
+ Delete
+
+
+
+ }
+ >
+ {instances.length > 0 && (
+
+ {isMultiple ? (
+
+ Permanently delete{' '}
+
+ {instances.length} instances
+
+ ? You can’t undo this action.
+
+ ) : (
+
+ Permanently delete instance{' '}
+
+ {instances[0].id}
+
+ ? You can’t undo this action.
+
+ )}
+
+
+ Proceeding with this action will delete the
+ {isMultiple ? ' instances with all their content ' : ' instance with all its content'} and can affect
+ related resources.{' '}
+
+ Learn more
+
+
+
+ To avoid accidental deletions, we ask you to provide additional written consent.
+
+
+
+ )}
+
+ );
+}
diff --git a/pages/demos/pages/delete-with-additional-confirmation/components/instance-details-page.tsx b/pages/demos/pages/delete-with-additional-confirmation/components/instance-details-page.tsx
new file mode 100644
index 0000000000..2dc06d4e1c
--- /dev/null
+++ b/pages/demos/pages/delete-with-additional-confirmation/components/instance-details-page.tsx
@@ -0,0 +1,85 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import BreadcrumbGroup from '@cloudscape-design/components/breadcrumb-group';
+import Button from '@cloudscape-design/components/button';
+import Container from '@cloudscape-design/components/container';
+import Flashbar, { FlashbarProps } from '@cloudscape-design/components/flashbar';
+import Header from '@cloudscape-design/components/header';
+import KeyValuePairs from '@cloudscape-design/components/key-value-pairs';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+
+import { EC2Instance } from '../../../resources/types';
+import { Navigation } from '../../commons';
+import { CustomAppLayout } from '../../commons/common-components';
+import ItemState from '../../delete-with-simple-confirmation/components/item-state';
+
+interface InstanceDetailsPageProps {
+ instance: EC2Instance;
+ onDeleteInit: () => void;
+ notifications: FlashbarProps.MessageDefinition[];
+}
+export function InstanceDetailsPage({ instance, onDeleteInit, notifications }: InstanceDetailsPageProps) {
+ return (
+
+
+ Edit
+ Delete
+
+ }
+ >
+ {instance.id}
+
+ Instance details}>
+ ,
+ },
+ {
+ label: 'Instance type',
+ value: instance.type,
+ },
+ ]}
+ />
+
+
+ }
+ breadcrumbs={
+
+ }
+ notifications={ }
+ navigation={ }
+ navigationOpen={false}
+ toolsHide={true}
+ />
+ );
+}
diff --git a/pages/demos/pages/delete-with-additional-confirmation/components/instances-page.tsx b/pages/demos/pages/delete-with-additional-confirmation/components/instances-page.tsx
new file mode 100644
index 0000000000..52bb72916b
--- /dev/null
+++ b/pages/demos/pages/delete-with-additional-confirmation/components/instances-page.tsx
@@ -0,0 +1,54 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import BreadcrumbGroup from '@cloudscape-design/components/breadcrumb-group';
+import Flashbar, { FlashbarProps } from '@cloudscape-design/components/flashbar';
+
+import { EC2Instance } from '../../../resources/types';
+import { Navigation } from '../../commons';
+import { CustomAppLayout } from '../../commons/common-components';
+import InstancesTable from './instances-table';
+
+interface InstancesPageProps {
+ instances: EC2Instance[];
+ selectedItems: EC2Instance[];
+ setSelectedItems: (items: EC2Instance[]) => void;
+ onDeleteInit: () => void;
+ notifications: FlashbarProps.MessageDefinition[];
+}
+export function InstancesPage({
+ instances,
+ selectedItems,
+ setSelectedItems,
+ onDeleteInit,
+ notifications,
+}: InstancesPageProps) {
+ return (
+ setSelectedItems(event.detail.selectedItems)}
+ onDelete={onDeleteInit}
+ />
+ }
+ breadcrumbs={
+
+ }
+ notifications={ }
+ navigation={ }
+ navigationOpen={false}
+ toolsHide={true}
+ contentType="table"
+ />
+ );
+}
diff --git a/pages/demos/pages/delete-with-additional-confirmation/components/instances-table.tsx b/pages/demos/pages/delete-with-additional-confirmation/components/instances-table.tsx
new file mode 100644
index 0000000000..d7d0c87e27
--- /dev/null
+++ b/pages/demos/pages/delete-with-additional-confirmation/components/instances-table.tsx
@@ -0,0 +1,116 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import { useCollection } from '@cloudscape-design/collection-hooks';
+import Button from '@cloudscape-design/components/button';
+import Link from '@cloudscape-design/components/link';
+import Pagination from '@cloudscape-design/components/pagination';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import Table, { TableProps } from '@cloudscape-design/components/table';
+import TextFilter from '@cloudscape-design/components/text-filter';
+
+import { getHeaderCounterText, getTextFilterCounterText, renderAriaLive } from '../../../i18n-strings';
+import { EC2Instance } from '../../../resources/types';
+import { FullPageHeader } from '../../commons';
+import { TableEmptyState, TableNoMatchState } from '../../commons/common-components';
+import ItemState from '../../delete-with-simple-confirmation/components/item-state';
+
+const COLUMN_DEFINITIONS: TableProps.ColumnDefinition[] = [
+ {
+ id: 'id',
+ header: 'Instance ID',
+ cell: item => {item.id},
+ isRowHeader: true,
+ },
+ {
+ id: 'state',
+ header: 'Instance state',
+ cell: item => ,
+ },
+ {
+ id: 'type',
+ header: 'Instance type',
+ cell: item => item.type,
+ },
+ {
+ id: 'publicDns',
+ header: 'Public DNS',
+ cell: item => item.publicDns,
+ },
+ {
+ id: 'monitoring',
+ header: 'Monitoring',
+ cell: item => item.monitoring,
+ },
+];
+
+interface InstancesTableProps {
+ instances: EC2Instance[];
+ selectedItems: EC2Instance[];
+ onSelectionChange: (event: {
+ detail: {
+ selectedItems: EC2Instance[];
+ };
+ }) => void;
+ onDelete: () => void;
+}
+export default function InstancesTable({ instances, selectedItems, onSelectionChange, onDelete }: InstancesTableProps) {
+ const { items, actions, filteredItemsCount, collectionProps, filterProps, paginationProps } =
+ useCollection(instances, {
+ filtering: {
+ empty: ,
+ noMatch: actions.setFiltering('')} />,
+ },
+ pagination: { pageSize: 50 },
+ selection: {},
+ });
+
+ const deletingItemsSelected = selectedItems.filter(it => it.state === 'deleting').length > 0;
+
+ return (
+ onSelectionChange(e)}
+ columnDefinitions={COLUMN_DEFINITIONS}
+ items={items}
+ selectionType="multi"
+ ariaLabels={{
+ itemSelectionLabel: (_data, row) => `select ${row.id}`,
+ allItemsSelectionLabel: () => 'select all',
+ selectionGroupLabel: 'Instance selection',
+ }}
+ renderAriaLive={renderAriaLive}
+ variant="full-page"
+ stickyHeader={true}
+ header={
+
+ View details
+ Edit
+
+ Delete
+
+ Create instance
+
+ }
+ />
+ }
+ filter={
+
+ }
+ pagination={ }
+ />
+ );
+}
diff --git a/pages/demos/pages/delete-with-simple-confirmation/components/item-state.tsx b/pages/demos/pages/delete-with-simple-confirmation/components/item-state.tsx
new file mode 100644
index 0000000000..faa5300235
--- /dev/null
+++ b/pages/demos/pages/delete-with-simple-confirmation/components/item-state.tsx
@@ -0,0 +1,12 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import StatusIndicator from '@cloudscape-design/components/status-indicator';
+
+export default function ItemState({ state }: { state: string }) {
+ if (state === 'deleting') {
+ return Deleting... ;
+ }
+ return {state} ;
+}
diff --git a/pages/demos/pages/delete-with-simple-confirmation/use-location-hash.ts b/pages/demos/pages/delete-with-simple-confirmation/use-location-hash.ts
new file mode 100644
index 0000000000..87f972177b
--- /dev/null
+++ b/pages/demos/pages/delete-with-simple-confirmation/use-location-hash.ts
@@ -0,0 +1,19 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import { useEffect, useState } from 'react';
+
+export default function useLocationHash() {
+ const [currentPagePath, setCurrentPage] = useState(window.location.hash.substring(1));
+
+ useEffect(() => {
+ const handler = () => setCurrentPage(window.location.hash.substring(1));
+ window.addEventListener('hashchange', handler);
+ return () => window.removeEventListener('hashchange', handler);
+ }, []);
+
+ useEffect(() => {
+ window.scrollTo({ top: 0 });
+ }, [currentPagePath]);
+
+ return currentPagePath;
+}
diff --git a/pages/demos/pages/delete-with-simple-confirmation/use-notifications.tsx b/pages/demos/pages/delete-with-simple-confirmation/use-notifications.tsx
new file mode 100644
index 0000000000..a43762da95
--- /dev/null
+++ b/pages/demos/pages/delete-with-simple-confirmation/use-notifications.tsx
@@ -0,0 +1,118 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useCallback, useState } from 'react';
+
+import { useId } from '../../use-id-polyfill';
+
+import Button from '@cloudscape-design/components/button';
+import { FlashbarProps } from '@cloudscape-design/components/flashbar';
+
+interface Resource {
+ id: string;
+}
+
+export default function useNotifications({ resourceName }: { resourceName: string }) {
+ const deletingFlashMessageId = useId();
+ const [notifications, setNotifications] = useState([]);
+
+ const dismissNotification = useCallback((id: string) => {
+ setNotifications(notifications => notifications.filter(notification => notification.id !== id));
+ }, []);
+
+ const updateOrAdd = useCallback((notification: FlashbarProps.MessageDefinition) => {
+ setNotifications(notifications => {
+ const existingItemIndex = notifications.findIndex(item => item.id === notification.id);
+ if (existingItemIndex === -1) {
+ // Notification with the same id does not exist, add it
+ return [notification, ...notifications];
+ } else {
+ // Notification with the same id already exists, update it
+ const newArray = [...notifications];
+ newArray.splice(existingItemIndex, 1, notification);
+ return newArray;
+ }
+ });
+ }, []);
+
+ const notifyDeleted = useCallback(
+ (resources: Resource[]) => {
+ if (resources?.length) {
+ const messageId = `delete-notification-${resources[0].id}`;
+ const content =
+ resources.length === 1
+ ? `Successfully deleted ${resourceName} ${resources[0].id}.`
+ : `Successfully deleted ${resources.length} ${resourceName}s.`;
+
+ updateOrAdd({
+ type: 'success',
+ dismissible: true,
+ statusIconAriaLabel: 'success',
+ dismissLabel: 'Dismiss message',
+ content,
+ id: messageId,
+ onDismiss: () => dismissNotification(messageId),
+ });
+ }
+ },
+ [dismissNotification, resourceName, updateOrAdd]
+ );
+
+ const notifyFailed = useCallback(
+ (failedResources: Resource[], options?: { retry?: (resource: Resource) => void }) => {
+ if (failedResources?.length) {
+ setNotifications(notifications => [
+ ...failedResources.map(resource => {
+ const id = `failed-${resource.id}`;
+ const retry = options?.retry;
+ return {
+ type: 'error' as FlashbarProps.Type,
+ dismissible: true,
+ statusIconAriaLabel: 'error',
+ dismissLabel: 'Dismiss message',
+ header: `Failed to delete ${resourceName} ${resource.id}`,
+ content: 'Your request couldn’t be processed because of an issue with the server. Try again later.',
+ action: retry ? retry(resource)}>Retry : undefined,
+ id,
+ onDismiss: () => dismissNotification(id),
+ };
+ }),
+ ...notifications,
+ ]);
+ }
+ },
+ [dismissNotification, resourceName]
+ );
+
+ const clearFailed = (resource: Resource) => {
+ setNotifications(notifications =>
+ notifications.filter(notification => notification.id !== `failed-${resource.id}`)
+ );
+ };
+
+ const notifyInProgress = useCallback(
+ (resources: Resource[]) => {
+ if (resources?.length) {
+ const content =
+ resources.length === 1
+ ? `Deleting ${resourceName} ${resources[0].id}.`
+ : `Deleting ${resources.length} ${resourceName}s.`;
+
+ updateOrAdd({
+ loading: true,
+ type: 'info',
+ statusIconAriaLabel: 'info',
+ dismissible: false,
+ content,
+ id: deletingFlashMessageId,
+ });
+ } else {
+ setNotifications(notifications =>
+ notifications.filter(notification => notification.id !== deletingFlashMessageId)
+ );
+ }
+ },
+ [deletingFlashMessageId, resourceName, updateOrAdd]
+ );
+
+ return { clearFailed, notifications, notifyDeleted, notifyFailed, notifyInProgress };
+}
diff --git a/pages/demos/pages/details-hub/app.tsx b/pages/demos/pages/details-hub/app.tsx
new file mode 100644
index 0000000000..ceb83c7b49
--- /dev/null
+++ b/pages/demos/pages/details-hub/app.tsx
@@ -0,0 +1,66 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import SplitPanel from '@cloudscape-design/components/split-panel';
+
+import {
+ CustomAppLayout,
+ DemoTopNavigation,
+ GlobalSplitPanelContent,
+ Navigation,
+ Notifications,
+ useGlobalSplitPanel,
+} from '../commons/common-components';
+import { Breadcrumbs } from '../details/components/breadcrumbs';
+import { GeneralConfig } from '../details/components/general-config';
+import { OriginsTable } from '../details/components/origins-table';
+import { PageHeader } from '../details/components/page-header';
+import { INSTANCE_DROPDOWN_ITEMS } from '../details/details-config';
+import { LogsTable } from './components/logs-table';
+
+import '../../styles/top-navigation.scss';
+
+export function App() {
+ const { splitPanelOpen, onSplitPanelToggle, splitPanelSize, onSplitPanelResize, splitPanelPreferences } =
+ useGlobalSplitPanel();
+ return (
+ <>
+
+
+
+
+ }
+ content={
+
+
+
+
+
+
+
+
+ }
+ breadcrumbs={ }
+ navigation={ }
+ toolsHide={true}
+ contentType="default"
+ notifications={ }
+ />
+ >
+ );
+}
diff --git a/pages/demos/pages/details-hub/commons.ts b/pages/demos/pages/details-hub/commons.ts
new file mode 100644
index 0000000000..b4a04b9338
--- /dev/null
+++ b/pages/demos/pages/details-hub/commons.ts
@@ -0,0 +1,11 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import { TableProps } from '@cloudscape-design/components/table';
+
+import { baseTableAriaLabels } from '../../i18n-strings';
+
+export const logsTableAriaLabels: TableProps.AriaLabels<{ name: string }> = {
+ ...baseTableAriaLabels,
+ itemSelectionLabel: (data, row) => `select ${row.name}`,
+ selectionGroupLabel: 'Logs selection',
+};
diff --git a/pages/demos/pages/details-hub/components/logs-table.tsx b/pages/demos/pages/details-hub/components/logs-table.tsx
new file mode 100644
index 0000000000..ca86c466b3
--- /dev/null
+++ b/pages/demos/pages/details-hub/components/logs-table.tsx
@@ -0,0 +1,58 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useState } from 'react';
+
+import Box from '@cloudscape-design/components/box';
+import Button from '@cloudscape-design/components/button';
+import Header from '@cloudscape-design/components/header';
+import Link from '@cloudscape-design/components/link';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import Table, { TableProps } from '@cloudscape-design/components/table';
+
+import { getHeaderCounterText } from '../../../i18n-strings';
+import { LogResource } from '../../../resources/types';
+import DataProvider from '../../commons/data-provider';
+import { useAsyncData } from '../../commons/use-async-data';
+import { LOGS_COLUMN_DEFINITIONS } from '../../details/details-config';
+import { logsTableAriaLabels } from '../commons';
+
+export function LogsTable() {
+ const [logs, logsLoading] = useAsyncData(() => new DataProvider().getData('logs'));
+ const [selectedItems, setSelectedItems] = useState>([]);
+ const isOnlyOneSelected = selectedItems.length === 1;
+ const atLeastOneSelected = selectedItems.length > 0;
+
+ return (
+ setSelectedItems(event.detail.selectedItems)}
+ header={
+
+ View
+ Watch
+ Download
+
+ }
+ >
+ Logs
+
+ }
+ footer={
+
+ View all logs
+
+ }
+ />
+ );
+}
diff --git a/pages/demos/pages/details-tabs/app.tsx b/pages/demos/pages/details-tabs/app.tsx
new file mode 100644
index 0000000000..6e942c60bc
--- /dev/null
+++ b/pages/demos/pages/details-tabs/app.tsx
@@ -0,0 +1,130 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useRef, useState } from 'react';
+
+import { AppLayoutProps } from '@cloudscape-design/components/app-layout';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import SplitPanel from '@cloudscape-design/components/split-panel';
+import Tabs from '@cloudscape-design/components/tabs';
+
+import {
+ CustomAppLayout,
+ DemoTopNavigation,
+ GlobalSplitPanelContent,
+ Navigation,
+ Notifications,
+ useGlobalSplitPanel,
+} from '../commons/common-components';
+import { BehaviorsTable } from '../details/components/behaviors-table';
+import { Breadcrumbs } from '../details/components/breadcrumbs';
+import { EmptyTable } from '../details/components/empty-table';
+import { GeneralConfig } from '../details/components/general-config';
+import { OriginsTable } from '../details/components/origins-table';
+import { PageHeader } from '../details/components/page-header';
+import { TagsTable } from '../details/components/tags-table';
+import { INSTANCE_DROPDOWN_ITEMS, INVALIDATIONS_COLUMN_DEFINITIONS } from '../details/details-config';
+import ToolsContent from '../details/tools-content';
+import { Details } from './components/details';
+import { LogsTable } from './components/logs-table';
+
+import '../../styles/top-navigation.scss';
+
+export function App() {
+ const [toolsIndex, setToolsIndex] = useState(0);
+ const [toolsOpen, setToolsOpen] = useState(false);
+ const { splitPanelOpen, onSplitPanelToggle, splitPanelSize, onSplitPanelResize, splitPanelPreferences } =
+ useGlobalSplitPanel();
+ const appLayout = useRef(null);
+
+ const loadHelpPanelContent = (index: number): void => {
+ setToolsIndex(index);
+ setToolsOpen(true);
+ appLayout.current?.focusToolsClose();
+ };
+
+ return (
+ <>
+
+
+
+
+ }
+ content={
+
+
+
+
+ ,
+ },
+ {
+ label: 'Logs',
+ id: 'logs',
+ content: ,
+ },
+ {
+ label: 'Origins',
+ id: 'origins',
+ content: ,
+ },
+ {
+ label: 'Behaviors',
+ id: 'behaviors',
+ content: ,
+ },
+ {
+ label: 'Invalidations',
+ id: 'invalidations',
+ content: ,
+ },
+ {
+ label: 'Tags',
+ id: 'tags',
+ content: ,
+ },
+ ]}
+ ariaLabel="Resource details"
+ />
+
+
+ }
+ breadcrumbs={ }
+ navigation={ }
+ tools={ToolsContent[toolsIndex]}
+ toolsOpen={toolsOpen}
+ onToolsChange={({ detail }: { detail: { open: boolean } }) => setToolsOpen(detail.open)}
+ notifications={ }
+ />
+ >
+ );
+}
diff --git a/pages/demos/pages/details-tabs/components/details.tsx b/pages/demos/pages/details-tabs/components/details.tsx
new file mode 100644
index 0000000000..03a1871588
--- /dev/null
+++ b/pages/demos/pages/details-tabs/components/details.tsx
@@ -0,0 +1,22 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import Button from '@cloudscape-design/components/button';
+import Container from '@cloudscape-design/components/container';
+import Header from '@cloudscape-design/components/header';
+
+import { InfoLink } from '../../commons';
+import { SettingsDetails } from '../../details/components/settings-details';
+
+export const Details = ({ loadHelpPanelContent }: { loadHelpPanelContent: (value: number) => void }) => (
+ loadHelpPanelContent(1)} />} actions={Edit }>
+ Details
+
+ }
+ >
+
+
+);
diff --git a/pages/demos/pages/details-tabs/components/logs-table.tsx b/pages/demos/pages/details-tabs/components/logs-table.tsx
new file mode 100644
index 0000000000..03b43a41a5
--- /dev/null
+++ b/pages/demos/pages/details-tabs/components/logs-table.tsx
@@ -0,0 +1,76 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+import { useState } from 'react';
+
+import { useCollection } from '@cloudscape-design/collection-hooks';
+import Button from '@cloudscape-design/components/button';
+import Header from '@cloudscape-design/components/header';
+import Pagination from '@cloudscape-design/components/pagination';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import Table, { TableProps } from '@cloudscape-design/components/table';
+import TextFilter from '@cloudscape-design/components/text-filter';
+
+import { getHeaderCounterText, getTextFilterCounterText, renderAriaLive } from '../../../i18n-strings';
+import { LogResource } from '../../../resources/types';
+import { TableEmptyState, TableNoMatchState } from '../../commons/common-components';
+import DataProvider from '../../commons/data-provider';
+import { useAsyncData } from '../../commons/use-async-data';
+import { LOGS_COLUMN_DEFINITIONS } from '../../details/details-config';
+import { logsTableAriaLabels } from '../../details-hub/commons';
+
+export function LogsTable() {
+ const [logs, logsLoading] = useAsyncData(() => new DataProvider().getData('logs'));
+ const [selectedItems, setSelectedItems] = useState>([]);
+ const isOnlyOneSelected = selectedItems.length === 1;
+ const atLeastOneSelected = selectedItems.length > 0;
+ const { items, actions, filteredItemsCount, collectionProps, filterProps, paginationProps } =
+ useCollection(logs, {
+ filtering: {
+ empty: ,
+ noMatch: actions.setFiltering('')} />,
+ },
+ pagination: { pageSize: 10 },
+ });
+
+ return (
+ setSelectedItems(evt.detail.selectedItems)}
+ header={
+
+ View
+ Watch
+ Download
+
+ }
+ >
+ Logs
+
+ }
+ filter={
+
+ }
+ pagination={ }
+ />
+ );
+}
diff --git a/pages/demos/pages/details/app.tsx b/pages/demos/pages/details/app.tsx
new file mode 100644
index 0000000000..3368b30a10
--- /dev/null
+++ b/pages/demos/pages/details/app.tsx
@@ -0,0 +1,83 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useRef, useState } from 'react';
+
+import { AppLayoutProps } from '@cloudscape-design/components/app-layout';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import SplitPanel from '@cloudscape-design/components/split-panel';
+
+import {
+ CustomAppLayout,
+ DemoTopNavigation,
+ GlobalSplitPanelContent,
+ Navigation,
+ Notifications,
+ useGlobalSplitPanel,
+} from '../commons/common-components';
+import { BehaviorsTable } from './components/behaviors-table';
+import { Breadcrumbs } from './components/breadcrumbs';
+import { DistSettings } from './components/dist-settings';
+import { DistributionDetails } from './components/distribution-details';
+import { OriginsTable } from './components/origins-table';
+import { PageHeader } from './components/page-header';
+import { TagsTable } from './components/tags-table';
+import ToolsContent from './tools-content';
+
+import '../../styles/top-navigation.scss';
+
+export function App() {
+ const [toolsIndex, setToolsIndex] = useState(0);
+ const [toolsOpen, setToolsOpen] = useState(false);
+ const { splitPanelOpen, onSplitPanelToggle, splitPanelSize, onSplitPanelResize, splitPanelPreferences } =
+ useGlobalSplitPanel();
+ const appLayout = useRef(null);
+
+ const loadHelpPanelContent = (index: number): void => {
+ setToolsIndex(index);
+ setToolsOpen(true);
+ appLayout.current?.focusToolsClose();
+ };
+
+ return (
+ <>
+
+
+
+
+ }
+ content={
+
+
+
+
+
+
+
+
+
+
+ }
+ breadcrumbs={ }
+ navigation={ }
+ tools={ToolsContent[toolsIndex]}
+ toolsOpen={toolsOpen}
+ onToolsChange={({ detail }: { detail: { open: boolean } }) => setToolsOpen(detail.open)}
+ contentType="default"
+ notifications={ }
+ />
+ >
+ );
+}
diff --git a/pages/demos/pages/details/components/behaviors-table.tsx b/pages/demos/pages/details/components/behaviors-table.tsx
new file mode 100644
index 0000000000..1bbb3b6a7c
--- /dev/null
+++ b/pages/demos/pages/details/components/behaviors-table.tsx
@@ -0,0 +1,64 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useState } from 'react';
+
+import Button from '@cloudscape-design/components/button';
+import Header from '@cloudscape-design/components/header';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import Table, { TableProps } from '@cloudscape-design/components/table';
+
+import { baseTableAriaLabels, getHeaderCounterText } from '../../../i18n-strings';
+import { BehaviorResource } from '../../../resources/types';
+import DataProvider from '../../commons/data-provider';
+import { useAsyncData } from '../../commons/use-async-data';
+import { BEHAVIORS_COLUMN_DEFINITIONS } from '../details-config';
+
+const behaviorsSelectionLabels = {
+ ...baseTableAriaLabels,
+ itemSelectionLabel: (
+ _: unknown,
+ row: {
+ pathPattern: string;
+ origin: string;
+ }
+ ) => `select path ${row.pathPattern} from origin ${row.origin}`,
+ selectionGroupLabel: 'Behaviors selection',
+};
+
+export function BehaviorsTable() {
+ const [behaviors, behaviorsLoading] = useAsyncData(() =>
+ new DataProvider().getData('behaviors')
+ );
+ const [selectedItems, setSelectedItems] = useState>([]);
+ const isOnlyOneSelected = selectedItems.length === 1;
+ const atLeastOneSelected = selectedItems.length > 0;
+
+ return (
+ setSelectedItems(event.detail.selectedItems)}
+ header={
+
+ Edit
+ Delete
+ Create behavior
+
+ }
+ >
+ Cache behavior settings
+
+ }
+ />
+ );
+}
diff --git a/pages/demos/pages/details/components/breadcrumbs.tsx b/pages/demos/pages/details/components/breadcrumbs.tsx
new file mode 100644
index 0000000000..9abb323685
--- /dev/null
+++ b/pages/demos/pages/details/components/breadcrumbs.tsx
@@ -0,0 +1,11 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import BreadcrumbGroup from '@cloudscape-design/components/breadcrumb-group';
+
+import { resourceDetailBreadcrumbs } from '../../../common/breadcrumbs';
+
+export const Breadcrumbs = () => (
+
+);
diff --git a/pages/demos/pages/details/components/dist-settings.tsx b/pages/demos/pages/details/components/dist-settings.tsx
new file mode 100644
index 0000000000..9a2903d8d7
--- /dev/null
+++ b/pages/demos/pages/details/components/dist-settings.tsx
@@ -0,0 +1,27 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import Container from '@cloudscape-design/components/container';
+import Header from '@cloudscape-design/components/header';
+
+import { InfoLink } from '../../commons';
+import { SettingsDetails } from './settings-details';
+
+export const DistSettings = ({
+ loadHelpPanelContent,
+ isInProgress,
+}: {
+ isInProgress: boolean;
+ loadHelpPanelContent: (value: number) => void;
+}) => (
+ loadHelpPanelContent(0)} />}>
+ Distribution settings
+
+ }
+ >
+
+
+);
diff --git a/pages/demos/pages/details/components/distribution-details.tsx b/pages/demos/pages/details/components/distribution-details.tsx
new file mode 100644
index 0000000000..48cb4a3815
--- /dev/null
+++ b/pages/demos/pages/details/components/distribution-details.tsx
@@ -0,0 +1,36 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+// @cloudscape-design/code-view not available — replaced with
+import React from 'react';
+
+import Container from '@cloudscape-design/components/container';
+import Header from '@cloudscape-design/components/header';
+import Tabs from '@cloudscape-design/components/tabs';
+
+import { codeSnippets } from '../details-code-snippets';
+
+export const DistributionDetails = () => {
+ return (
+ Distribution configuration details}>
+ {codeSnippets.json} ,
+ },
+ {
+ label: 'YAML',
+ id: 'yaml',
+ content: {codeSnippets.yaml} ,
+ },
+ {
+ label: 'XML',
+ id: 'xml',
+ content: {codeSnippets.xml} ,
+ },
+ ]}
+ />
+
+ );
+};
diff --git a/pages/demos/pages/details/components/empty-table.tsx b/pages/demos/pages/details/components/empty-table.tsx
new file mode 100644
index 0000000000..15639e4b4e
--- /dev/null
+++ b/pages/demos/pages/details/components/empty-table.tsx
@@ -0,0 +1,39 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import Button from '@cloudscape-design/components/button';
+import Header from '@cloudscape-design/components/header';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import Table, { TableProps } from '@cloudscape-design/components/table';
+
+import { TableEmptyState } from '../../commons/common-components';
+
+export function EmptyTable({
+ title,
+ columnDefinitions,
+}: {
+ title: string;
+ columnDefinitions: TableProps.ColumnDefinition[];
+}) {
+ const items: T[] = [];
+ return (
+ }
+ columnDefinitions={columnDefinitions}
+ items={items}
+ header={
+
+ Edit
+ Delete
+ Create {title.toLowerCase()}
+
+ }
+ >{`${title}s`}
+ }
+ />
+ );
+}
diff --git a/pages/demos/pages/details/components/general-config.tsx b/pages/demos/pages/details/components/general-config.tsx
new file mode 100644
index 0000000000..a22e871106
--- /dev/null
+++ b/pages/demos/pages/details/components/general-config.tsx
@@ -0,0 +1,34 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import Container from '@cloudscape-design/components/container';
+import Header from '@cloudscape-design/components/header';
+import KeyValuePairs from '@cloudscape-design/components/key-value-pairs';
+import StatusIndicator from '@cloudscape-design/components/status-indicator';
+
+export const GeneralConfig = () => (
+ General configuration}>
+ Available,
+ },
+ {
+ label: 'Pending maintenance',
+ value: 'None',
+ },
+ ]}
+ />
+
+);
diff --git a/pages/demos/pages/details/components/origins-table.tsx b/pages/demos/pages/details/components/origins-table.tsx
new file mode 100644
index 0000000000..39cc10cb97
--- /dev/null
+++ b/pages/demos/pages/details/components/origins-table.tsx
@@ -0,0 +1,56 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useState } from 'react';
+
+import Button from '@cloudscape-design/components/button';
+import Header from '@cloudscape-design/components/header';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import Table, { TableProps } from '@cloudscape-design/components/table';
+
+import { baseTableAriaLabels, getHeaderCounterText } from '../../../i18n-strings';
+import { OriginResource } from '../../../resources/types';
+import DataProvider from '../../commons/data-provider';
+import { useAsyncData } from '../../commons/use-async-data';
+import { ORIGINS_COLUMN_DEFINITIONS } from '../details-config';
+
+const originsSelectionLabels = {
+ ...baseTableAriaLabels,
+ itemSelectionLabel: (_: unknown, row: { name: string }) => `select ${row.name}`,
+ selectionGroupLabel: 'Origins selection',
+};
+
+export function OriginsTable() {
+ const [origins, originsLoading] = useAsyncData(() => new DataProvider().getData('origins'));
+ const [selectedItems, setSelectedItems] = useState>([]);
+ const isOnlyOneSelected = selectedItems.length === 1;
+ const atLeastOneSelected = selectedItems.length > 0;
+
+ return (
+ setSelectedItems(event.detail.selectedItems)}
+ header={
+
+ Edit
+ Delete
+ Create origin
+
+ }
+ >
+ Origins
+
+ }
+ />
+ );
+}
diff --git a/pages/demos/pages/details/components/page-header.tsx b/pages/demos/pages/details/components/page-header.tsx
new file mode 100644
index 0000000000..29832759b0
--- /dev/null
+++ b/pages/demos/pages/details/components/page-header.tsx
@@ -0,0 +1,35 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import Button from '@cloudscape-design/components/button';
+import ButtonDropdown, { ButtonDropdownProps } from '@cloudscape-design/components/button-dropdown';
+import Header from '@cloudscape-design/components/header';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+
+import { DEMO_DISTRIBUTION } from '../config';
+
+export const PageHeader = ({ buttons }: { buttons: ButtonDropdownProps.ItemOrGroup[] }) => {
+ return (
+
+ {buttons.map((button, key) =>
+ button.itemType === 'action' ? (
+
+ {button.text}
+
+ ) : (
+
+ {button.text}
+
+ )
+ )}
+
+ }
+ >
+ {DEMO_DISTRIBUTION.id}
+
+ );
+};
diff --git a/pages/demos/pages/details/components/settings-details.tsx b/pages/demos/pages/details/components/settings-details.tsx
new file mode 100644
index 0000000000..26f337ba59
--- /dev/null
+++ b/pages/demos/pages/details/components/settings-details.tsx
@@ -0,0 +1,115 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import CopyToClipboard from '@cloudscape-design/components/copy-to-clipboard';
+import KeyValuePairs from '@cloudscape-design/components/key-value-pairs';
+import ProgressBar from '@cloudscape-design/components/progress-bar';
+import StatusIndicator from '@cloudscape-design/components/status-indicator';
+
+import { DistributionResource } from '../../../resources/types';
+import { DEMO_DISTRIBUTION } from '../config';
+
+export const SettingsDetails = ({
+ distribution = DEMO_DISTRIBUTION,
+ isInProgress,
+}: {
+ distribution?: DistributionResource;
+ isInProgress: boolean;
+}) => (
+
+ ),
+ },
+ ],
+ },
+ {
+ type: 'group',
+ items: [
+ {
+ label: 'Status',
+ id: 'status-id',
+ value: distribution.state ? (
+
+ {distribution.state}
+
+ ) : (
+
+ ),
+ },
+ {
+ label: 'Price class',
+ value: distribution.priceClass,
+ },
+ {
+ label: 'CNAMEs',
+ value: '-',
+ },
+ ],
+ },
+ {
+ type: 'group',
+ items: [
+ {
+ label: 'SSL certificate',
+ value: distribution.sslCertificate,
+ },
+ {
+ label: 'Custom SSL client support',
+ value: '-',
+ },
+ {
+ label: 'Logging',
+ value: distribution.logging,
+ },
+ ],
+ },
+ {
+ type: 'group',
+ items: [
+ {
+ label: 'IPv6',
+ value: 'Off',
+ },
+ {
+ label: 'Default root object',
+ value: '-',
+ },
+ {
+ label: 'Comment',
+ value: 'To verify',
+ },
+ ],
+ },
+ ]}
+ />
+);
diff --git a/pages/demos/pages/details/components/tags-table.tsx b/pages/demos/pages/details/components/tags-table.tsx
new file mode 100644
index 0000000000..0459bbd8f4
--- /dev/null
+++ b/pages/demos/pages/details/components/tags-table.tsx
@@ -0,0 +1,83 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import { useCollection } from '@cloudscape-design/collection-hooks';
+import Box from '@cloudscape-design/components/box';
+import Button from '@cloudscape-design/components/button';
+import Header from '@cloudscape-design/components/header';
+import Table from '@cloudscape-design/components/table';
+import TextFilter from '@cloudscape-design/components/text-filter';
+
+import { getTextFilterCounterText } from '../../../i18n-strings';
+import { TagsResource } from '../../../resources/types';
+import { InfoLink } from '../../commons';
+import { useAsyncData } from '../../commons/use-async-data';
+import { TAGS_COLUMN_DEFINITIONS } from '../details-config';
+
+export function TagsTable({ loadHelpPanelContent }: { loadHelpPanelContent: (value: number) => void }) {
+ const [tags, tagsLoading] = useAsyncData(async () => {
+ const { ResourceTagMappingList } = await window.FakeServer.GetResources();
+
+ return ResourceTagMappingList.reduce(
+ (allTags: { key: string; value: string }[], resourceTagMapping: { Tags: { key: string; value: string }[] }) => [
+ ...allTags,
+ ...resourceTagMapping.Tags,
+ ],
+ []
+ );
+ });
+
+ const { items, collectionProps, filteredItemsCount, filterProps, actions } = useCollection(tags, {
+ filtering: {
+ noMatch: (
+
+
+ No matches
+
+
+ No tags matched the search text.
+
+ actions.setFiltering('')}>Clear filter
+
+ ),
+ },
+ sorting: {},
+ });
+
+ return (
+
+ }
+ header={
+ loadHelpPanelContent(2)} />}
+ actions={Manage tags }
+ 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.
+ >
+ }
+ >
+ Tags
+
+ }
+ />
+ );
+}
diff --git a/pages/demos/pages/details/config.ts b/pages/demos/pages/details/config.ts
new file mode 100644
index 0000000000..7e84988383
--- /dev/null
+++ b/pages/demos/pages/details/config.ts
@@ -0,0 +1,15 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import { DistributionResource } from '../../resources/types';
+
+export const DEMO_DISTRIBUTION: DistributionResource = {
+ id: 'SLCCSMWOHOFUY0',
+ domainName: 'abcdef01234567890.cloudfront.net',
+ priceClass: 'Use only US, Canada, Europe, and Asia',
+ sslCertificate: 'Default CloudFront SSL certificate',
+ deliveryMethod: 'Web',
+ origin: 'EXAMPLE-BUCKET-1.s3.amazon',
+ logging: 'Off',
+ tags: { environment: ['development'], department: ['support'] },
+ date: new Date(),
+};
diff --git a/pages/demos/pages/details/details-code-snippets.ts b/pages/demos/pages/details/details-code-snippets.ts
new file mode 100644
index 0000000000..e34f4f929a
--- /dev/null
+++ b/pages/demos/pages/details/details-code-snippets.ts
@@ -0,0 +1,73 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+const xml = `
+
+
+ 1
+
+ example.com
+
+
+
+ true
+ 3600
+
+ true
+
+ 1
+
+
+ S3-aws-phoenix.example.com
+
+
+
+ `;
+
+const json = `{
+ "DistributionConfig": {
+ "Aliases": {
+ "Quantity": 1,
+ "Items": [
+ {
+ "CNAME": "example.com"
+ }
+ ]
+ },
+ "DefaultCacheBehavior": {
+ "Compress": true,
+ "DefaultTTL": 3600
+ },
+ "Enabled": true,
+ "Origins": {
+ "Quantity": 1,
+ "Items": [
+ {
+ "Origin": {
+ "DomainName": "S3-aws-phoenix.example.com"
+ }
+ }
+ ]
+ }
+ }
+}`;
+
+const yaml = `DistributionConfig:
+ Aliases:
+ Quantity: 1
+ Items:
+ - CNAME: example.com
+ DefaultCacheBehavior:
+ Compress: true
+ DefaultTTL: 3600
+ Enabled: true
+ Origins:
+ Quantity: 1
+ Items:
+ - Origin:
+ DomainName: S3-aws-phoenix.example.com`;
+
+export const codeSnippets = {
+ json,
+ yaml,
+ xml,
+};
diff --git a/pages/demos/pages/details/details-config.tsx b/pages/demos/pages/details/details-config.tsx
new file mode 100644
index 0000000000..c53eccda44
--- /dev/null
+++ b/pages/demos/pages/details/details-config.tsx
@@ -0,0 +1,134 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import Link from '@cloudscape-design/components/link';
+import { TableProps } from '@cloudscape-design/components/table';
+
+import { BehaviorResource, InvalidationResource, LogResource, OriginResource } from '../../resources/types';
+
+export const ORIGINS_COLUMN_DEFINITIONS: TableProps.ColumnDefinition[] = [
+ {
+ id: 'name',
+ header: 'Name and path',
+ cell: item => item.name,
+ isRowHeader: true,
+ },
+ {
+ id: 'id',
+ header: 'Origin ID',
+ cell: item => item.id,
+ },
+ {
+ id: 'type',
+ header: 'Origin type',
+ cell: item => item.type,
+ },
+ {
+ id: 'accessIdentity',
+ header: 'Origin access ID',
+ cell: item => item.accessIdentity,
+ },
+];
+
+export const BEHAVIORS_COLUMN_DEFINITIONS: TableProps.ColumnDefinition[] = [
+ {
+ id: 'precedence',
+ header: 'Precedence',
+ cell: item => item.precedence,
+ },
+ {
+ id: 'pathPattern',
+ header: 'Path pattern',
+ cell: item => item.pathPattern,
+ isRowHeader: true,
+ },
+ {
+ id: 'origin',
+ header: 'Origin',
+ cell: item => item.origin,
+ },
+ {
+ id: 'viewerProtocolPolicy',
+ header: 'Viewer protocol policy',
+ cell: item => item.viewerProtocolPolicy,
+ },
+ {
+ id: 'forwardedQueryStrings',
+ header: 'Forwarded query strings',
+ cell: item => item.forwardedQueryStrings,
+ },
+];
+
+export const LOGS_COLUMN_DEFINITIONS: TableProps.ColumnDefinition[] = [
+ {
+ id: 'name',
+ header: 'Name',
+ cell: item => {item.name},
+ isRowHeader: true,
+ },
+ {
+ id: 'lastWritten',
+ header: 'Last written',
+ cell: item => item.lastWritten,
+ },
+ {
+ id: 'size',
+ header: 'Size',
+ cell: item => item.size,
+ },
+];
+
+export const INSTANCE_DROPDOWN_ITEMS = [
+ {
+ text: 'Take snapshot',
+ id: 'snapshot',
+ },
+ {
+ text: 'Reboot',
+ id: 'reboot',
+ },
+ {
+ text: 'Stop',
+ id: 'stop',
+ },
+];
+
+export const INVALIDATIONS_COLUMN_DEFINITIONS: TableProps.ColumnDefinition[] = [
+ {
+ id: 'id',
+ header: 'Invalidation ID',
+ isRowHeader: true,
+ cell: item => item.id,
+ },
+ {
+ id: 'status',
+ header: 'Status',
+ cell: item => item.status,
+ },
+ {
+ id: 'date',
+ header: 'Date',
+ cell: item => item.date,
+ },
+];
+
+export const TAGS_COLUMN_DEFINITIONS: TableProps.ColumnDefinition<{
+ key: string;
+ value: string;
+}>[] = [
+ {
+ id: 'key',
+ header: 'Key',
+ cell: item => item.key,
+ width: 300,
+ isRowHeader: true,
+ sortingField: 'key',
+ },
+ {
+ id: 'value',
+ header: 'Value',
+ cell: item => item.value || '-',
+ sortingField: 'value',
+ },
+];
diff --git a/pages/demos/pages/details/tools-content.tsx b/pages/demos/pages/details/tools-content.tsx
new file mode 100644
index 0000000000..15c9f9d554
--- /dev/null
+++ b/pages/demos/pages/details/tools-content.tsx
@@ -0,0 +1,73 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import HelpPanel from '@cloudscape-design/components/help-panel';
+
+import { ExternalLinkGroup } from '../commons';
+
+function DistributionSettings({ header }: { header: React.ReactNode }) {
+ return (
+ {header}}
+ footer={
+
+ }
+ >
+
+ When you want to use CloudFront to distribute your content, you create a distribution and choose the
+ configuration settings you want. For example:
+
+
+
+ Your content origin—that is, the Amazon S3 bucket, MediaPackage channel, or HTTP server from which CloudFront
+ gets the files to distribute. You can specify any combination of up to 25 Amazon S3 buckets, channels, and/or
+ HTTP servers as your origins.
+
+ Access—whether you want the files to be available to everyone or restrict access to some users.
+ Security—whether you want CloudFront to require users to use HTTPS to access your content.
+
+ Cookie or query-string forwarding—whether you want CloudFront to forward cookies or query strings to your
+ origin.
+
+
+ Geo-restrictions—whether you want CloudFront to prevent users in selected countries from accessing your
+ content.
+
+ Access logs—whether you want CloudFront to create access logs that show viewer activity.
+
+
+ );
+}
+
+/* eslint-disable react/jsx-key */
+export default [
+ ,
+ ,
+ Tags}
+ footer={
+
+ }
+ >
+
+ Tags are words or phrases that you can use to identify and organize your AWS resources. You can add multiple tags
+ to each resource, and each tag includes a key and a value that you define. For example, the key might be "domain"
+ and the value might be "example.com". You can search and filter your resources based on the tags you add.
+
+ ,
+];
diff --git a/pages/demos/pages/edit/app.tsx b/pages/demos/pages/edit/app.tsx
new file mode 100644
index 0000000000..a3dd37508c
--- /dev/null
+++ b/pages/demos/pages/edit/app.tsx
@@ -0,0 +1,96 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useRef } from 'react';
+
+import { AppLayoutProps } from '@cloudscape-design/components/app-layout';
+import Button from '@cloudscape-design/components/button';
+import Form from '@cloudscape-design/components/form';
+import Header from '@cloudscape-design/components/header';
+import Link from '@cloudscape-design/components/link';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import SplitPanel from '@cloudscape-design/components/split-panel';
+
+import {
+ CustomAppLayout,
+ DemoTopNavigation,
+ GlobalSplitPanelContent,
+ Navigation,
+ useGlobalSplitPanel,
+} from '../commons/common-components';
+import { Notifications } from '../commons/common-components';
+import { Breadcrumbs } from './components/breadcrumbs';
+import { Content } from './components/content';
+import { TOOLS_CONTENT } from './edit-config';
+
+import '../../styles/top-navigation.scss';
+
+export const App = () => {
+ const appLayoutRef = useRef(null);
+ const [toolsIndex, setToolsIndex] = React.useState(0);
+ const [toolsOpen, setToolsOpen] = React.useState(false);
+ const { splitPanelOpen, onSplitPanelToggle, splitPanelSize, onSplitPanelResize, splitPanelPreferences } =
+ useGlobalSplitPanel();
+ const loadHelpPanelContent = (toolsIndex: number) => {
+ setToolsIndex(toolsIndex);
+ setToolsOpen(true);
+ appLayoutRef.current?.focusToolsClose();
+ };
+ return (
+ <>
+
+
+
+
+ }
+ content={
+
+
+ }
+ breadcrumbs={ }
+ navigation={ }
+ tools={TOOLS_CONTENT[toolsIndex]}
+ toolsOpen={toolsOpen}
+ onToolsChange={({ detail }) => {
+ setToolsOpen(detail.open);
+ }}
+ notifications={ }
+ />
+ >
+ );
+};
diff --git a/pages/demos/pages/edit/components/breadcrumbs.tsx b/pages/demos/pages/edit/components/breadcrumbs.tsx
new file mode 100644
index 0000000000..17a29105fe
--- /dev/null
+++ b/pages/demos/pages/edit/components/breadcrumbs.tsx
@@ -0,0 +1,11 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import BreadcrumbGroup from '@cloudscape-design/components/breadcrumb-group';
+
+import { resourceEditBreadcrumbs } from '../../../common/breadcrumbs';
+
+export const Breadcrumbs = () => (
+
+);
diff --git a/pages/demos/pages/edit/components/content.tsx b/pages/demos/pages/edit/components/content.tsx
new file mode 100644
index 0000000000..75f7bf280e
--- /dev/null
+++ b/pages/demos/pages/edit/components/content.tsx
@@ -0,0 +1,67 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useState } from 'react';
+
+import Button from '@cloudscape-design/components/button';
+import Container from '@cloudscape-design/components/container';
+import FormField from '@cloudscape-design/components/form-field';
+import Header from '@cloudscape-design/components/header';
+import RadioGroup from '@cloudscape-design/components/radio-group';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import Textarea from '@cloudscape-design/components/textarea';
+
+import { InfoLink } from '../../commons';
+import { PRICE_CLASS_OPTIONS, SSL_CERTIFICATE_OPTIONS } from '../edit-config';
+import { DistributionsFooter } from './distribution-footer';
+
+export const Content = ({ loadHelpPanelContent }: { loadHelpPanelContent: (value: number) => void }) => {
+ const [cNames, setCnames] = useState('');
+ const [priceClass, setPriceClass] = useState(PRICE_CLASS_OPTIONS[0].value);
+ const [tlsCertificate, setTlsCertificate] = useState(SSL_CERTIFICATE_OPTIONS[0].value);
+ return (
+ Distribution settings}
+ footer={ loadHelpPanelContent(index)} />}
+ >
+
+
+ setPriceClass(event.detail.value)}
+ />
+
+
+ Alternative domain names (CNAMEs) - optional
+ >
+ }
+ info={ loadHelpPanelContent(1)} />}
+ description="You must list any custom domain names that you use in addition to the CloudFront domain name for the URLs for your files."
+ constraintText="Specify up to 100 CNAMEs separated with commas or put each on a new line."
+ stretch={true}
+ >
+
+ loadHelpPanelContent(2)} />}
+ stretch={true}
+ >
+ setTlsCertificate(event.detail.value)}
+ ariaRequired={true}
+ />
+
+ Request or import a certificate with AWS Certificate Manager (ACM)
+
+
+ );
+};
diff --git a/pages/demos/pages/edit/components/distribution-footer.tsx b/pages/demos/pages/edit/components/distribution-footer.tsx
new file mode 100644
index 0000000000..a71c5082e9
--- /dev/null
+++ b/pages/demos/pages/edit/components/distribution-footer.tsx
@@ -0,0 +1,65 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useState } from 'react';
+
+import Checkbox from '@cloudscape-design/components/checkbox';
+import ExpandableSection from '@cloudscape-design/components/expandable-section';
+import FormField from '@cloudscape-design/components/form-field';
+import Input from '@cloudscape-design/components/input';
+import RadioGroup from '@cloudscape-design/components/radio-group';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import Textarea from '@cloudscape-design/components/textarea';
+
+import { InfoLink } from '../../commons';
+import { SUPPORTED_HTTP_VERSIONS_OPTIONS } from '../edit-config';
+
+export const DistributionsFooter = ({ loadHelpPanelContent }: { loadHelpPanelContent: (index: number) => void }) => {
+ const [comment, setComment] = useState('');
+ const [rootObject, setRootObject] = useState('');
+ const [supportedHttpVersions, setSupportedHttpVersions] = useState(SUPPORTED_HTTP_VERSIONS_OPTIONS[0].value);
+ const [loggingEnabled, setLoggingEnabled] = useState(false);
+ const [ipv6Enabled, setIpv6Enabled] = useState(false);
+ return (
+
+
+
+ setSupportedHttpVersions(event.detail.value)}
+ ariaRequired={true}
+ />
+
+ loadHelpPanelContent(3)} />}
+ description="Type the name of the object that you want CloudFront to return when a viewer request points to your root URL."
+ >
+ setRootObject(event.detail.value)}
+ ariaRequired={true}
+ />
+
+
+ setLoggingEnabled(event.detail.checked)}>
+ Turn on logging
+
+
+
+ setIpv6Enabled(event.detail.checked)}>
+ Enabled
+
+
+
+
+
+
+ );
+};
diff --git a/pages/demos/pages/edit/edit-config.tsx b/pages/demos/pages/edit/edit-config.tsx
new file mode 100644
index 0000000000..81608fc517
--- /dev/null
+++ b/pages/demos/pages/edit/edit-config.tsx
@@ -0,0 +1,137 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import HelpPanel from '@cloudscape-design/components/help-panel';
+
+import { ExternalLinkGroup } from '../commons';
+
+export const PRICE_CLASS_OPTIONS = [
+ { label: 'Use all edge locations (best performance)', value: '0' },
+ { label: 'Use only US, Canada, and Europe', value: '1' },
+ { label: 'Use only US, Canada, Europe, and Asia', value: '2' },
+];
+
+export const SSL_CERTIFICATE_OPTIONS = [
+ {
+ label: 'Default CloudFront SSL/TLS certificate',
+ value: 'default',
+ description: 'Provides HTTPS or HTTP access to your content using a CloudFront domain name.',
+ },
+ {
+ label: 'Custom SSL/TLS certificate (example.com)',
+ value: 'custom',
+ description: 'Grants access by using an alternate domain name, such as https://www.example.com/.',
+ },
+];
+
+export const SUPPORTED_HTTP_VERSIONS_OPTIONS = [
+ { label: 'HTTP 2', value: 'http2' },
+ { label: 'HTTP 1', value: 'http1' },
+];
+
+/*eslint-disable react/jsx-key*/
+export const TOOLS_CONTENT = [
+ Edit distribution}
+ footer={
+
+ }
+ >
+ You can update your CloudFront distribution by editing its settings and saving your changes.
+ ,
+ Alternate domain names (CNAMEs)}
+ footer={
+
+ }
+ >
+
+ If you want to use your own domain names in the URLs for your files instead of the CloudFront domain name, enter
+ the domain names in the box. These custom domain names are known as alternative domain names (CNAMEs). Both
+ web and RTMP distributions support CNAMEs.
+
+
+ For example, if you add www.example.com as your domain name, you would use the following URL to view{' '}
+ /images/image.jpg:
+
+ https://www.example.com/images/image.jpg
+ Before you begin
+ Before you add a CNAME, make sure that you do the following:
+
+ Register the domain name with Amazon Route 53 or another domain provider.
+
+ Add a certificate from an authorized certificate authority (CA) to CloudFront that covers the domain name that
+ you plan to use with the distribution, to validate that you are authorized to use the domain.
+
+
+ ,
+ SSL/TLS certificate}
+ footer={
+
+ }
+ >
+
+ When CloudFront receives a request for content, it finds the domain name in the request header and responds to the
+ request with the applicable SSL/TLS certificate. CloudFront and the viewer perform an SSL/TLS negotiation, and if
+ the negotiation is successful CloudFront returns the requested content to the viewer.
+
+
+ You can use the default CloudFront SSL/TLS certificate or a custom SSL/TLS certificate. The default certificate
+ requires that you use the CloudFront domain name for your distribution in the URLs for your files, for example,{' '}
+ https://1234567890abcdef0.cloudfront.net/logo.png. If you use your own domain name, such as{' '}
+ example.com, you must choose one of these options:
+
+
+ Use an SSL/TLS certificate provided by AWS Certificate Manager (ACM)
+ Import a certificate from a third-party certificate authority into ACM or the IAM certificate store
+ Create and import a self-signed certificate
+
+ ,
+ Root object}
+ footer={
+
+ }
+ >
+
+ You can configure CloudFront to return a specific object (the default root object) when a user requests the root
+ URL for your web distribution instead of requesting an object in your distribution. Specifying a default root
+ object lets you avoid exposing the contents of your distribution or returning an error.
+
+ ,
+];
+/*eslint-enable react/jsx-key*/
diff --git a/pages/demos/pages/form/app.tsx b/pages/demos/pages/form/app.tsx
new file mode 100644
index 0000000000..c31001af92
--- /dev/null
+++ b/pages/demos/pages/form/app.tsx
@@ -0,0 +1,84 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useRef, useState } from 'react';
+
+import { AppLayoutProps } from '@cloudscape-design/components/app-layout';
+import BreadcrumbGroup from '@cloudscape-design/components/breadcrumb-group';
+import { SelectProps } from '@cloudscape-design/components/select';
+import SplitPanel from '@cloudscape-design/components/split-panel';
+
+import { resourceCreateBreadcrumbs } from '../../common/breadcrumbs';
+import {
+ CustomAppLayout,
+ DemoTopNavigation,
+ GlobalSplitPanelContent,
+ Navigation,
+ Notifications,
+ useGlobalSplitPanel,
+} from '../commons/common-components';
+import { FormFull, FormHeader } from './components/form';
+import ToolsContent from './components/tools-content';
+
+import '../../styles/top-navigation.scss';
+
+const Breadcrumbs = () => (
+
+);
+
+export function App() {
+ const [toolsIndex, setToolsIndex] = useState(0);
+ const [toolsOpen, setToolsOpen] = useState(false);
+ const { splitPanelOpen, onSplitPanelToggle, splitPanelSize, onSplitPanelResize, splitPanelPreferences } =
+ useGlobalSplitPanel();
+ const appLayout = useRef(null);
+
+ // Minimal cache policy props for form compatibility
+ const [selectedCachePolicy, setSelectedCachePolicy] = useState(null);
+ const cachePolicyButtonRef = useRef(null);
+ const cachePolicyProps = {
+ buttonRef: cachePolicyButtonRef,
+ policies: [],
+ selectedPolicy: selectedCachePolicy,
+ setSelectedPolicy: (option: SelectProps.Option) => setSelectedCachePolicy(option),
+ toggleSplitPanel: () => undefined,
+ };
+
+ const loadHelpPanelContent = (index: number) => {
+ setToolsIndex(index);
+ setToolsOpen(true);
+ appLayout.current?.focusToolsClose();
+ };
+
+ return (
+ <>
+
+ }
+ cachePolicyProps={cachePolicyProps}
+ />
+ }
+ breadcrumbs={ }
+ navigation={ }
+ tools={ToolsContent[toolsIndex]}
+ toolsOpen={toolsOpen}
+ onToolsChange={({ detail }) => setToolsOpen(detail.open)}
+ notifications={ }
+ splitPanelOpen={splitPanelOpen}
+ onSplitPanelToggle={onSplitPanelToggle}
+ splitPanelSize={splitPanelSize}
+ onSplitPanelResize={onSplitPanelResize}
+ splitPanelPreferences={splitPanelPreferences}
+ splitPanel={
+
+
+
+ }
+ />
+ >
+ );
+}
diff --git a/pages/demos/pages/form/components/api-defaults-inputs.tsx b/pages/demos/pages/form/components/api-defaults-inputs.tsx
new file mode 100644
index 0000000000..bda7bd71c4
--- /dev/null
+++ b/pages/demos/pages/form/components/api-defaults-inputs.tsx
@@ -0,0 +1,46 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useState } from 'react';
+
+import FormField from '@cloudscape-design/components/form-field';
+import Select, { SelectProps } from '@cloudscape-design/components/select';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+
+import { InfoLink } from '../../commons/common-components';
+import { AVAILABILITY_ZONE_OPTIONS, ENCRYPTION_KEY_OPTIONS } from '../form-config';
+
+interface APIDefaultsInputsProps {
+ loadHelpPanelContent: (value: number) => void;
+}
+
+export default function APIDefaultsInputs({ loadHelpPanelContent }: APIDefaultsInputsProps) {
+ const [encryptionKey, setEncryptionKey] = useState(ENCRYPTION_KEY_OPTIONS[0]);
+ const [availabilityZone, setAvailabilityZone] = useState(AVAILABILITY_ZONE_OPTIONS[0]);
+
+ return (
+
+
+ setEncryptionKey(selectedOption)}
+ triggerVariant="option"
+ data-testid="encryption-key-select"
+ />
+
+
+ loadHelpPanelContent(12)} />}>
+ setAvailabilityZone(selectedOption)}
+ triggerVariant="option"
+ data-testid="availability-zone-select"
+ />
+
+
+ );
+}
diff --git a/pages/demos/pages/form/components/cache-behavior-footer.tsx b/pages/demos/pages/form/components/cache-behavior-footer.tsx
new file mode 100644
index 0000000000..eda9b85e75
--- /dev/null
+++ b/pages/demos/pages/form/components/cache-behavior-footer.tsx
@@ -0,0 +1,113 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useState } from 'react';
+
+import Box from '@cloudscape-design/components/box';
+import Button from '@cloudscape-design/components/button';
+import Checkbox from '@cloudscape-design/components/checkbox';
+import ColumnLayout from '@cloudscape-design/components/column-layout';
+import ExpandableSection from '@cloudscape-design/components/expandable-section';
+import FormField from '@cloudscape-design/components/form-field';
+import Input from '@cloudscape-design/components/input';
+import RadioGroup from '@cloudscape-design/components/radio-group';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+
+import { COOKIE_OPTIONS, CURRENT_COMPRESSION_OPTIONS, QUERY_STRING_OPTIONS } from '../form-config';
+
+export default function CacheBehaviorFooter() {
+ const [lambdaType, setLambdaType] = useState('');
+ const [lambdaArn, setLambdaArn] = useState('');
+ const [cookies, setCookies] = useState(COOKIE_OPTIONS[0].value);
+ const [queryStringSettings, setQueryStringSettings] = useState(QUERY_STRING_OPTIONS[0].value);
+ const [smoothStreaming, setSmoothStreaming] = useState(false);
+ const [requireSignature, setRequireSignature] = useState(false);
+ const [compressionMode, setCompressionMode] = useState(CURRENT_COMPRESSION_OPTIONS[0].value);
+
+ function changeHandler(callBackFn: React.Dispatch>, value: T) {
+ callBackFn(value);
+ }
+
+ return (
+
+
+
+
Path pattern
+
Default (*)
+
+
+ changeHandler(setCookies, event.detail.value)}
+ ariaRequired={true}
+ />
+
+
+ changeHandler(setQueryStringSettings, event.detail.value)}
+ ariaRequired={true}
+ />
+
+
+ changeHandler(setSmoothStreaming, event.detail.checked)}
+ >
+ Turn on Microsoft Smooth Streaming
+
+
+
+ changeHandler(setRequireSignature, event.detail.checked)}
+ >
+ Require signed URL or signed cookie
+
+
+
+ changeHandler(setCompressionMode, event.detail.value)}
+ ariaRequired={true}
+ />
+
+ Lambda functions}
+ description="These functions run in response to CloudFront events."
+ stretch={true}
+ >
+
+
+ changeHandler(setLambdaType, event.detail.value)}
+ />
+
+
+ changeHandler(setLambdaArn, event.detail.value)}
+ />
+
+
+ Add lambda function
+
+
+
+
+
+ );
+}
diff --git a/pages/demos/pages/form/components/cache-behavior-panel.tsx b/pages/demos/pages/form/components/cache-behavior-panel.tsx
new file mode 100644
index 0000000000..3a48db69af
--- /dev/null
+++ b/pages/demos/pages/form/components/cache-behavior-panel.tsx
@@ -0,0 +1,177 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useEffect, useState } from 'react';
+
+import Button from '@cloudscape-design/components/button';
+import CodeEditor, { CodeEditorProps } from '@cloudscape-design/components/code-editor';
+import ColumnLayout from '@cloudscape-design/components/column-layout';
+import Container from '@cloudscape-design/components/container';
+import FormField from '@cloudscape-design/components/form-field';
+import Header from '@cloudscape-design/components/header';
+import Input from '@cloudscape-design/components/input';
+import RadioGroup from '@cloudscape-design/components/radio-group';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+
+import { InfoLink } from '../../commons/common-components';
+import {
+ ALLOWED_HTTP_METHOD_OPTIONS,
+ CODE_EDITOR_THEMES,
+ FORWARD_HEADER_OPTIONS,
+ VIEWER_PROTOCOL_POLICY_OPTIONS,
+} from '../form-config';
+import { FormDataAttributesErrors, FormDataAttributesValues, FormRefs } from '../types';
+import CacheBehaviorFooter from './cache-behavior-footer';
+
+type AceBuildsModule = typeof import('ace-builds');
+
+const defaultState = { minimumTtl: 0, maximumTtl: 31536000, defaultTtl: 86400 };
+
+interface CacheBehaviorPanelProps {
+ loadHelpPanelContent: (value: number) => void;
+ validation?: boolean;
+ errors?: Partial;
+ setErrors?: (error: Partial) => void;
+ setData?: (data: Partial) => void;
+ refs?: FormRefs;
+}
+
+export default function CacheBehaviorPanel({
+ loadHelpPanelContent,
+ validation = false,
+ errors = {},
+ setErrors,
+ setData,
+ refs,
+}: CacheBehaviorPanelProps) {
+ const [minimumTtl, setMinimumTtl] = useState(defaultState.minimumTtl);
+ const [maximumTtl, setMaximumTtl] = useState(defaultState.maximumTtl);
+ const [defaultTtl, setDefaultTtl] = useState(defaultState.defaultTtl);
+
+ const [viewerProtocolPolicy, setViewerProtocolPolicy] = useState(VIEWER_PROTOCOL_POLICY_OPTIONS[0].value);
+ const [allowedHttpMethods, setAllowedHttpMethods] = useState(ALLOWED_HTTP_METHOD_OPTIONS[0].value);
+ const [forwardHeaders, setForwardHeaders] = useState(FORWARD_HEADER_OPTIONS[0].value);
+ const [ace, setAce] = useState();
+ const [codeEditorLoading, setCodeEditorLoading] = useState(true);
+ const [codeEditorValue, setCodeEditorValue] = useState('');
+ const [codeEditorPreferences, setCodeEditorPreferences] = useState(null);
+
+ useEffect(() => {
+ setCodeEditorLoading(true);
+ import('ace-builds').then(ace => {
+ ace.config.set('basePath', './libs/ace/');
+ setAce(ace);
+ setCodeEditorLoading(false);
+ });
+ }, []);
+
+ const onCodeEditorChange: CodeEditorProps['onChange'] = event => {
+ const { value } = event.detail;
+ setCodeEditorValue(value);
+
+ if (validation) {
+ setData?.({ codeEditor: value });
+ setErrors?.({ codeEditor: '' });
+ }
+ };
+
+ const onCodeEditorPreferencesChange: CodeEditorProps['onPreferencesChange'] = event => {
+ setCodeEditorPreferences(event.detail);
+ };
+
+ const onSetToDefault = () => {
+ setMinimumTtl(defaultState.minimumTtl);
+ setMaximumTtl(defaultState.maximumTtl);
+ setDefaultTtl(defaultState.defaultTtl);
+ };
+
+ return (
+ loadHelpPanelContent(9)} />}>
+ Cache behavior settings
+
+ }
+ footer={ }
+ >
+
+
+ setViewerProtocolPolicy(event.detail.value)}
+ ariaRequired={true}
+ />
+
+
+ setAllowedHttpMethods(event.detail.value)}
+ ariaRequired={true}
+ />
+
+
+ setForwardHeaders(event.detail.value)}
+ ariaRequired={true}
+ />
+
+
+
+
+ setMinimumTtl(Number(event.detail.value))}
+ ariaRequired={true}
+ />
+
+
+ setMaximumTtl(Number(event.detail.value))}
+ ariaRequired={true}
+ />
+
+
+ setDefaultTtl(Number(event.detail.value))}
+ ariaRequired={true}
+ />
+
+
+
+ Set to default
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/pages/demos/pages/form/components/cache-behavior-panel/cache-behavior-footer.tsx b/pages/demos/pages/form/components/cache-behavior-panel/cache-behavior-footer.tsx
new file mode 100644
index 0000000000..3c2f647ed3
--- /dev/null
+++ b/pages/demos/pages/form/components/cache-behavior-panel/cache-behavior-footer.tsx
@@ -0,0 +1,113 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useState } from 'react';
+
+import Box from '@cloudscape-design/components/box';
+import Button from '@cloudscape-design/components/button';
+import Checkbox from '@cloudscape-design/components/checkbox';
+import ColumnLayout from '@cloudscape-design/components/column-layout';
+import ExpandableSection from '@cloudscape-design/components/expandable-section';
+import FormField from '@cloudscape-design/components/form-field';
+import Input from '@cloudscape-design/components/input';
+import RadioGroup from '@cloudscape-design/components/radio-group';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+
+import { COOKIE_OPTIONS, CURRENT_COMPRESSION_OPTIONS, QUERY_STRING_OPTIONS } from '../../form-config';
+
+export default function CacheBehaviorFooter() {
+ const [lambdaType, setLambdaType] = useState('');
+ const [lambdaArn, setLambdaArn] = useState('');
+ const [cookies, setCookies] = useState(COOKIE_OPTIONS[0].value);
+ const [queryStringSettings, setQueryStringSettings] = useState(QUERY_STRING_OPTIONS[0].value);
+ const [smoothStreaming, setSmoothStreaming] = useState(false);
+ const [requireSignature, setRequireSignature] = useState(false);
+ const [compressionMode, setCompressionMode] = useState(CURRENT_COMPRESSION_OPTIONS[0].value);
+
+ function changeHandler(callBackFn: React.Dispatch>, value: T) {
+ callBackFn(value);
+ }
+
+ return (
+
+
+
+
Path pattern
+
Default (*)
+
+
+ changeHandler(setCookies, event.detail.value)}
+ ariaRequired={true}
+ />
+
+
+ changeHandler(setQueryStringSettings, event.detail.value)}
+ ariaRequired={true}
+ />
+
+
+ changeHandler(setSmoothStreaming, event.detail.checked)}
+ >
+ Turn on Microsoft Smooth Streaming
+
+
+
+ changeHandler(setRequireSignature, event.detail.checked)}
+ >
+ Require signed URL or signed cookie
+
+
+
+ changeHandler(setCompressionMode, event.detail.value)}
+ ariaRequired={true}
+ />
+
+ Lambda functions}
+ description="These functions run in response to CloudFront events."
+ stretch={true}
+ >
+
+
+ changeHandler(setLambdaType, event.detail.value)}
+ />
+
+
+ changeHandler(setLambdaArn, event.detail.value)}
+ />
+
+
+ Add lambda function
+
+
+
+
+
+ );
+}
diff --git a/pages/demos/pages/form/components/cache-behavior-panel/cache-behavior-panel.tsx b/pages/demos/pages/form/components/cache-behavior-panel/cache-behavior-panel.tsx
new file mode 100644
index 0000000000..4bda941d05
--- /dev/null
+++ b/pages/demos/pages/form/components/cache-behavior-panel/cache-behavior-panel.tsx
@@ -0,0 +1,219 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useEffect, useState } from 'react';
+
+import Button from '@cloudscape-design/components/button';
+import CodeEditor, { CodeEditorProps } from '@cloudscape-design/components/code-editor';
+import ColumnLayout from '@cloudscape-design/components/column-layout';
+import Container from '@cloudscape-design/components/container';
+import FormField from '@cloudscape-design/components/form-field';
+import Header from '@cloudscape-design/components/header';
+import Input from '@cloudscape-design/components/input';
+import RadioGroup from '@cloudscape-design/components/radio-group';
+import Select from '@cloudscape-design/components/select';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+
+import { InfoLink } from '../../../commons/common-components';
+import {
+ ALLOWED_HTTP_METHOD_OPTIONS,
+ CODE_EDITOR_THEMES,
+ FORWARD_HEADER_OPTIONS,
+ VIEWER_PROTOCOL_POLICY_OPTIONS,
+} from '../../form-config';
+import { CachePolicyProps, FormPanelProps } from '../../types';
+import CacheBehaviorFooter from './cache-behavior-footer';
+import OriginRequestPolicy from './origin-request-policy';
+
+type AceBuildsModule = typeof import('ace-builds');
+
+interface CacheBehaviorPanelProps extends FormPanelProps {
+ cachePolicyProps: CachePolicyProps;
+}
+
+const defaultState = { minimumTtl: 0, maximumTtl: 31536000, defaultTtl: 86400 };
+
+export default function CacheBehaviorPanel({
+ data,
+ loadHelpPanelContent,
+ validation = false,
+ errors,
+ setErrors,
+ setData,
+ refs,
+ cachePolicyProps,
+}: CacheBehaviorPanelProps) {
+ const [minimumTtl, setMinimumTtl] = useState(defaultState.minimumTtl);
+ const [maximumTtl, setMaximumTtl] = useState(defaultState.maximumTtl);
+ const [defaultTtl, setDefaultTtl] = useState(defaultState.defaultTtl);
+
+ const [viewerProtocolPolicy, setViewerProtocolPolicy] = useState(VIEWER_PROTOCOL_POLICY_OPTIONS[0].value);
+ const [allowedHttpMethods, setAllowedHttpMethods] = useState(ALLOWED_HTTP_METHOD_OPTIONS[0].value);
+ const [forwardHeaders, setForwardHeaders] = useState(FORWARD_HEADER_OPTIONS[0].value);
+ const [ace, setAce] = useState();
+ const [codeEditorLoading, setCodeEditorLoading] = useState(true);
+ const [codeEditorValue, setCodeEditorValue] = useState('');
+ const [codeEditorPreferences, setCodeEditorPreferences] = useState(null);
+
+ useEffect(() => {
+ setCodeEditorLoading(true);
+ import('ace-builds').then(ace => {
+ ace.config.set('basePath', './libs/ace/');
+ setAce(ace);
+ setCodeEditorLoading(false);
+ });
+ }, []);
+
+ const onCodeEditorChange: CodeEditorProps['onChange'] = event => {
+ const { value } = event.detail;
+ setCodeEditorValue(value);
+
+ if (validation) {
+ setData?.({ codeEditor: value });
+ setErrors?.({ codeEditor: '' });
+ }
+ };
+
+ const onCodeEditorPreferencesChange: CodeEditorProps['onPreferencesChange'] = event => {
+ setCodeEditorPreferences(event.detail);
+ };
+
+ const onSetToDefault = () => {
+ setMinimumTtl(defaultState.minimumTtl);
+ setMaximumTtl(defaultState.maximumTtl);
+ setDefaultTtl(defaultState.defaultTtl);
+ };
+
+ return (
+ loadHelpPanelContent(8)} />}>
+ Cache behavior settings
+
+ }
+ footer={ }
+ >
+
+
+
+
+
+
+ cachePolicyProps.toggleSplitPanel(true)}
+ >
+ Create cache policy
+
+
+ }
+ >
+ {
+ cachePolicyProps.setSelectedPolicy(selectedOption);
+ setErrors?.({ cachePolicy: '' });
+ }}
+ data-testid="cache-policy-select"
+ />
+
+
+
+ setViewerProtocolPolicy(event.detail.value)}
+ ariaRequired={true}
+ />
+
+
+ setAllowedHttpMethods(event.detail.value)}
+ ariaRequired={true}
+ />
+
+
+ setForwardHeaders(event.detail.value)}
+ ariaRequired={true}
+ />
+
+
+
+
+ setMinimumTtl(Number(event.detail.value))}
+ ariaRequired={true}
+ />
+
+
+ setMaximumTtl(Number(event.detail.value))}
+ ariaRequired={true}
+ />
+
+
+ setDefaultTtl(Number(event.detail.value))}
+ ariaRequired={true}
+ />
+
+
+
+ Set to default
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/pages/demos/pages/form/components/cache-behavior-panel/cache-key-origin-settings.tsx b/pages/demos/pages/form/components/cache-behavior-panel/cache-key-origin-settings.tsx
new file mode 100644
index 0000000000..196d5b698c
--- /dev/null
+++ b/pages/demos/pages/form/components/cache-behavior-panel/cache-key-origin-settings.tsx
@@ -0,0 +1,69 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import FormField from '@cloudscape-design/components/form-field';
+import Select, { SelectProps } from '@cloudscape-design/components/select';
+import SpaceBetween, { SpaceBetweenProps } from '@cloudscape-design/components/space-between';
+
+import {
+ ORIGIN_REQUEST_COOKIE_OPTIONS,
+ ORIGIN_REQUEST_HEADER_OPTIONS,
+ ORIGIN_REQUEST_QUERY_STRING_OPTIONS,
+} from '../../form-config';
+
+interface CacheKeyOriginSettingsProps {
+ header: SelectProps.Option;
+ queryStrings: SelectProps.Option;
+ cookies: SelectProps.Option;
+ setHeader: (selectedOption: SelectProps.Option) => void;
+ setQueryStrings: (selectedOption: SelectProps.Option) => void;
+ setCookies: (selectedOption: SelectProps.Option) => void;
+ spacing: SpaceBetweenProps.Size;
+}
+
+export default function CacheKeyOriginSettings({
+ header,
+ queryStrings,
+ cookies,
+ setHeader,
+ setQueryStrings,
+ setCookies,
+ spacing,
+}: CacheKeyOriginSettingsProps) {
+ return (
+
+
+ setHeader(selectedOption)}
+ ariaRequired={true}
+ />
+
+
+
+ setQueryStrings(selectedOption)}
+ ariaRequired={true}
+ />
+
+
+
+ setCookies(selectedOption)}
+ ariaRequired={true}
+ />
+
+
+ );
+}
diff --git a/pages/demos/pages/form/components/cache-behavior-panel/create-cache-policy.tsx b/pages/demos/pages/form/components/cache-behavior-panel/create-cache-policy.tsx
new file mode 100644
index 0000000000..88362197ca
--- /dev/null
+++ b/pages/demos/pages/form/components/cache-behavior-panel/create-cache-policy.tsx
@@ -0,0 +1,176 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useRef } from 'react';
+
+import Box from '@cloudscape-design/components/box';
+import Button from '@cloudscape-design/components/button';
+import FormField from '@cloudscape-design/components/form-field';
+import Header from '@cloudscape-design/components/header';
+import Input from '@cloudscape-design/components/input';
+import Link from '@cloudscape-design/components/link';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+
+import validateField from '../../form-validation-config';
+import { CreateCachePolicyAttributesErrors, CreateCachePolicyAttributesValues } from '../../types';
+import CacheKeyOriginSettings from './cache-key-origin-settings';
+
+interface CreateCachePolicyProps {
+ validation?: boolean;
+ data: CreateCachePolicyAttributesValues;
+ setData: (updateObj: Partial) => void;
+ errors: CreateCachePolicyAttributesErrors;
+ setErrors: (updateObj: CreateCachePolicyAttributesErrors) => void;
+ onSubmit: () => void;
+ onCancel: () => void;
+}
+
+function Section({ title, children }: { title: string; children: React.ReactNode }) {
+ return (
+
+
+
+ {children}
+
+ );
+}
+
+export default function CreateCachePolicy({
+ data,
+ setData,
+ errors,
+ setErrors,
+ onSubmit,
+ onCancel,
+ validation,
+}: CreateCachePolicyProps) {
+ const nameRef = useRef(null);
+
+ const onCreate = () => {
+ if (!validation) {
+ return;
+ }
+
+ const { errorText } = validateField('cachePolicyName', data.name);
+ if (errorText && errorText.length > 0) {
+ setErrors({ nameError: errorText });
+ nameRef.current?.focus();
+ return;
+ }
+
+ setData({ isSubmitting: true });
+
+ // Emulate create request
+ setTimeout(() => {
+ setData({ isSubmitting: false });
+ onSubmit();
+ }, 2000);
+ };
+
+ return (
+
+ );
+}
diff --git a/pages/demos/pages/form/components/cache-behavior-panel/origin-request-policy.tsx b/pages/demos/pages/form/components/cache-behavior-panel/origin-request-policy.tsx
new file mode 100644
index 0000000000..f5e386b25e
--- /dev/null
+++ b/pages/demos/pages/form/components/cache-behavior-panel/origin-request-policy.tsx
@@ -0,0 +1,132 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useState } from 'react';
+
+import FormField from '@cloudscape-design/components/form-field';
+import Input, { InputProps } from '@cloudscape-design/components/input';
+import Link from '@cloudscape-design/components/link';
+import RadioGroup from '@cloudscape-design/components/radio-group';
+import Select, { SelectProps } from '@cloudscape-design/components/select';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+
+import {
+ EXISTING_ORIGIN_REQUEST_POLICIES,
+ ORIGIN_REQUEST_COOKIE_OPTIONS,
+ ORIGIN_REQUEST_HEADER_OPTIONS,
+ ORIGIN_REQUEST_QUERY_STRING_OPTIONS,
+} from '../../form-config';
+import validateField from '../../form-validation-config';
+import { FormPanelProps } from '../../types';
+import CacheKeyOriginSettings from './cache-key-origin-settings';
+
+export default function OriginRequestPolicy({
+ data,
+ validation = false,
+ errors,
+ setErrors,
+ setData,
+ refs,
+}: Omit) {
+ const [existingOrNewPolicy, setExistingOrNewPolicy] = useState('existing');
+ const [selectedExistingOriginRequestPolicy, setSelectedExistingOriginRequestPolicy] =
+ useState(null);
+ const [header, setHeader] = useState(ORIGIN_REQUEST_HEADER_OPTIONS[0]);
+ const [queryStrings, setQueryStrings] = useState(ORIGIN_REQUEST_QUERY_STRING_OPTIONS[0]);
+ const [cookies, setCookies] = useState(ORIGIN_REQUEST_COOKIE_OPTIONS[0]);
+
+ const onNameChange: InputProps['onChange'] = event => {
+ const { value } = event.detail;
+ setData({ originRequestPolicyName: value });
+
+ if (validation) {
+ setErrors?.({ originRequestPolicyName: '' });
+ }
+ };
+
+ const onNameBlur = () => {
+ if (!validation || !setErrors || existingOrNewPolicy !== 'new') {
+ return;
+ }
+
+ const value = data.originRequestPolicyName;
+ const { errorText } = validateField('originRequestPolicyName', value);
+
+ setErrors({ originRequestPolicyName: errorText });
+ };
+
+ return (
+
+
+ Choose an existing origin request policy or create a new one. To configure advanced settings{' '}
+
+ create origin request policy
+
+ .
+ >
+ }
+ stretch={true}
+ >
+ {
+ setExistingOrNewPolicy(event.detail.value);
+ setData({ isOriginRequestPolicyNew: event.detail.value === 'new' });
+ }}
+ ariaRequired={true}
+ />
+
+
+ {existingOrNewPolicy === 'existing' ? (
+
+ Choose an existing origin policy - optional
+ >
+ }
+ >
+ setSelectedExistingOriginRequestPolicy(selectedOption)}
+ />
+
+ ) : (
+ <>
+
+
+
+
+
+ >
+ )}
+
+ );
+}
diff --git a/pages/demos/pages/form/components/cache-behavior-panel/use-create-cache-policy.ts b/pages/demos/pages/form/components/cache-behavior-panel/use-create-cache-policy.ts
new file mode 100644
index 0000000000..658fe7a531
--- /dev/null
+++ b/pages/demos/pages/form/components/cache-behavior-panel/use-create-cache-policy.ts
@@ -0,0 +1,53 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import { useState } from 'react';
+
+import {
+ ORIGIN_REQUEST_COOKIE_OPTIONS,
+ ORIGIN_REQUEST_HEADER_OPTIONS,
+ ORIGIN_REQUEST_QUERY_STRING_OPTIONS,
+} from '../../form-config';
+import { CreateCachePolicyAttributesErrors, CreateCachePolicyAttributesValues } from '../../types';
+
+const defaultData = {
+ name: '',
+ description: '',
+ minimumTtl: 1,
+ maximumTtl: 31536000,
+ defaultTtl: 86400,
+ header: ORIGIN_REQUEST_HEADER_OPTIONS[0],
+ queryStrings: ORIGIN_REQUEST_QUERY_STRING_OPTIONS[0],
+ cookies: ORIGIN_REQUEST_COOKIE_OPTIONS[0],
+ isSubmitting: false,
+};
+
+const defaultErrors = {
+ nameError: '',
+};
+
+export default function useCreateCachePolicy(): [
+ CreateCachePolicyAttributesValues,
+ (updateObj: Partial) => void,
+ CreateCachePolicyAttributesErrors,
+ (updateObj: CreateCachePolicyAttributesErrors) => void,
+ boolean,
+ () => void,
+] {
+ const [data, _setData] = useState(defaultData);
+ const [errors, _setErrors] = useState(defaultErrors);
+ const setData = (updateObj: Partial = {}) => {
+ _setData(prevData => ({ ...prevData, ...updateObj }));
+ };
+ const setErrors = (updateObj: CreateCachePolicyAttributesErrors) => {
+ _setErrors(prevErrors => ({ ...prevErrors, ...updateObj }));
+ };
+
+ const resetData = () => {
+ setData(defaultData);
+ setErrors(defaultErrors);
+ };
+
+ const isDataChanged = JSON.stringify(defaultData) !== JSON.stringify(data);
+
+ return [data, setData, errors, setErrors, isDataChanged, resetData];
+}
diff --git a/pages/demos/pages/form/components/content-delivery-panel.tsx b/pages/demos/pages/form/components/content-delivery-panel.tsx
new file mode 100644
index 0000000000..a4e2406d77
--- /dev/null
+++ b/pages/demos/pages/form/components/content-delivery-panel.tsx
@@ -0,0 +1,46 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useState } from 'react';
+
+import Container from '@cloudscape-design/components/container';
+import FormField from '@cloudscape-design/components/form-field';
+import Header from '@cloudscape-design/components/header';
+import Tiles from '@cloudscape-design/components/tiles';
+
+import { InfoLink } from '../../commons/common-components';
+
+interface ContentDeliveryPanelProps {
+ loadHelpPanelContent: (value: number) => void;
+}
+
+export default function ContentDeliveryPanel({ loadHelpPanelContent }: ContentDeliveryPanelProps) {
+ const [deliveryMethod, setDeliveryMethod] = useState('web');
+
+ return (
+ Distribution content delivery}>
+ loadHelpPanelContent(1)} />}
+ stretch={true}
+ >
+ setDeliveryMethod(e.detail.value)}
+ />
+
+
+ );
+}
diff --git a/pages/demos/pages/form/components/distribution-panel.tsx b/pages/demos/pages/form/components/distribution-panel.tsx
new file mode 100644
index 0000000000..08b3e6beba
--- /dev/null
+++ b/pages/demos/pages/form/components/distribution-panel.tsx
@@ -0,0 +1,315 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useState } from 'react';
+
+import Button from '@cloudscape-design/components/button';
+import Checkbox from '@cloudscape-design/components/checkbox';
+import Container from '@cloudscape-design/components/container';
+import DatePicker from '@cloudscape-design/components/date-picker';
+import ExpandableSection from '@cloudscape-design/components/expandable-section';
+import FileUpload, { FileUploadProps } from '@cloudscape-design/components/file-upload';
+import FormField from '@cloudscape-design/components/form-field';
+import Header from '@cloudscape-design/components/header';
+import Input from '@cloudscape-design/components/input';
+import RadioGroup from '@cloudscape-design/components/radio-group';
+import Select, { SelectProps } from '@cloudscape-design/components/select';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import Textarea from '@cloudscape-design/components/textarea';
+import TimeInput from '@cloudscape-design/components/time-input';
+
+import { InfoLink } from '../../commons/common-components';
+import useContentOrigins from '../../commons/use-content-origins';
+import { CUSTOM_SSL_CERTIFICATES, SUPPORTED_HTTP_VERSIONS_OPTIONS } from '../form-config';
+import validateField from '../form-validation-config';
+import { FormDataAttributesKeys, FormDataAttributesValues, FormPanelProps } from '../types';
+import APIDefaultsInputs from './api-defaults-inputs';
+
+interface DistributionsFooterProps {
+ state: FormDataAttributesValues;
+ onChange: (attribute: T, value: FormDataAttributesValues[T]) => void;
+}
+
+function DistributionsFooter({ state, onChange }: DistributionsFooterProps) {
+ return (
+
+
+
+ onChange('httpVersion', value)}
+ />
+
+
+ onChange('ipv6isOn', checked)}>
+ Turn on
+
+
+
+
+ );
+}
+
+const isS3PermissionError = (attribute: FormDataAttributesKeys, errorText: string) =>
+ attribute === 's3BucketSelectedOption' &&
+ errorText ===
+ "CloudFront isn't allowed to write logs to this bucket. You must enable access control lists (ACL) for the bucket.";
+
+export default function DistributionPanel({
+ loadHelpPanelContent,
+ validation = false,
+ data,
+ errors,
+ setData,
+ setErrors,
+ refs,
+ showAPIDefaultInputs,
+}: FormPanelProps) {
+ const [contentOriginsState, contentOriginsHandlers] = useContentOrigins();
+ const [customSSLCertificate, setCustomSSLCertificate] = useState(null);
+
+ const onChange: DistributionsFooterProps['onChange'] = (attribute, value) => {
+ setData({ [attribute]: value });
+
+ if (!validation || !errors || !setErrors) {
+ return;
+ }
+
+ // Validates when there is an error message in the field
+ if (errors[attribute]?.length > 0) {
+ const { errorText } = validateField(attribute, value);
+
+ // S3 bucket selection acts as server side validation
+ // so the error message is set only upon form submission and
+ // error message is reset when a bucket is selected
+ if (isS3PermissionError(attribute, errorText!)) {
+ setErrors({ [attribute]: '' });
+ } else {
+ setErrors({ [attribute]: errorText });
+ }
+ }
+ };
+
+ const onFunctionsChange: FileUploadProps['onChange'] = ({ detail }) => {
+ const functions = detail.value;
+ setData({ functions });
+
+ if (validation) {
+ const { errorText: functionsError } = validateField('functions', functions);
+ const functionsFileErrors = functions.map(file => validateField('functionFile', file).errorText);
+ // Setting to empty array so that on submit, valid files are not focused
+ const areErrorsEmpty = functionsFileErrors.every(fileError => !fileError || fileError.length === 0);
+
+ setErrors?.({
+ functions: functionsError,
+ functionFiles: areErrorsEmpty ? [] : (functionsFileErrors as string[]),
+ });
+ }
+ };
+
+ const onBlur = (attribute: FormDataAttributesKeys) => {
+ if (!validation || !setErrors) {
+ return;
+ }
+
+ const value = data[attribute];
+ const { errorText } = validateField(attribute, value);
+
+ if (isS3PermissionError(attribute, errorText!)) {
+ return;
+ }
+
+ setErrors({ [attribute]: errorText });
+ };
+
+ return (
+ Distribution settings}
+ footer={ }
+ >
+
+ loadHelpPanelContent(2)} />}
+ description="Enter the URL of the object that you want CloudFront to return when a viewer request points to your root URL."
+ constraintText="Enter a valid root object. Example: https://example.com"
+ errorText={errors?.cloudFrontRootObject}
+ i18nStrings={{ errorIconAriaLabel: 'Error' }}
+ >
+ onChange('cloudFrontRootObject', value)}
+ onBlur={() => onBlur('cloudFrontRootObject')}
+ ref={refs?.cloudFrontRootObject}
+ data-testid="root-input"
+ />
+
+
+ {showAPIDefaultInputs && }
+
+
+ Custom SSL certificate - optional
+ >
+ }
+ description="Choose a certificate from AWS Certificate Manager"
+ secondaryControl={
+
+
+
+
+ Create SSL certificate
+
+
+ }
+ >
+ setCustomSSLCertificate(selectedOption)}
+ />
+
+
+ Alternative domain names (CNAMEs) - optional
+ >
+ }
+ info={ loadHelpPanelContent(3)} />}
+ description="List any custom domain names that you use in addition to the CloudFront domain name for the URLs for your files."
+ constraintText="Specify up to 3 CNAMEs separated with commas."
+ stretch={true}
+ errorText={errors?.alternativeDomainNames}
+ i18nStrings={{ errorIconAriaLabel: 'Error' }}
+ >
+
+
+ onChange('s3BucketSelectedOption', selectedOption)}
+ onBlur={() => onBlur('s3BucketSelectedOption')}
+ ref={refs?.s3BucketSelectedOption}
+ />
+
+
+ Certificate expiry}>
+
+
+ onChange('certificateExpiryDate', value)}
+ openCalendarAriaLabel={selectedDate =>
+ 'Choose certificate expiry date' + (selectedDate ? `, selected date is ${selectedDate}` : '')
+ }
+ onBlur={() => onBlur('certificateExpiryDate')}
+ ref={refs?.certificateExpiryDate}
+ />
+
+
+ onChange('certificateExpiryTime', value)}
+ onBlur={() => onBlur('certificateExpiryTime')}
+ ref={refs?.certificateExpiryTime}
+ />
+
+
+
+
+ loadHelpPanelContent(11)} />}
+ >
+ (multiple ? 'Choose files' : 'Choose file'),
+ dropzoneText: multiple => (multiple ? 'Drop files to upload' : 'Drop file to upload'),
+ removeFileAriaLabel: fileIndex => `Remove file ${fileIndex + 1}`,
+ limitShowFewer: 'Show fewer files',
+ limitShowMore: 'Show more files',
+ errorIconAriaLabel: 'Error',
+ }}
+ ref={refs?.functions}
+ />
+
+
+
+ );
+}
diff --git a/pages/demos/pages/form/components/form.tsx b/pages/demos/pages/form/components/form.tsx
new file mode 100644
index 0000000000..f2f50c5b9e
--- /dev/null
+++ b/pages/demos/pages/form/components/form.tsx
@@ -0,0 +1,381 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useEffect, useRef, useState } from 'react';
+
+import { AttributeEditorProps } from '@cloudscape-design/components/attribute-editor';
+import Button from '@cloudscape-design/components/button';
+import { CodeEditorProps } from '@cloudscape-design/components/code-editor';
+import { DatePickerProps } from '@cloudscape-design/components/date-picker';
+import { FileUploadProps } from '@cloudscape-design/components/file-upload';
+import Form from '@cloudscape-design/components/form';
+import Header from '@cloudscape-design/components/header';
+import { InputProps } from '@cloudscape-design/components/input';
+import Link from '@cloudscape-design/components/link';
+import { SelectProps } from '@cloudscape-design/components/select';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+import { TagEditorProps } from '@cloudscape-design/components/tag-editor';
+import { TextareaProps } from '@cloudscape-design/components/textarea';
+
+import { InfoLink } from '../../commons/common-components';
+import validateField from '../form-validation-config';
+import {
+ CachePolicyProps,
+ FormDataAttributesErrors,
+ FormDataAttributesKeys,
+ FormDataAttributesValues,
+ FormRefs,
+} from '../types';
+import CacheBehaviorPanel from './cache-behavior-panel/cache-behavior-panel';
+import ContentDeliveryPanel from './content-delivery-panel';
+import DistributionsPanel from './distribution-panel';
+import OriginPanel from './origin-panel';
+import TagsPanel from './tags-panel';
+
+export function FormHeader({ loadHelpPanelContent }: { loadHelpPanelContent: (value: number) => void }) {
+ return (
+ loadHelpPanelContent(0)} />}
+ description="When you create an Amazon CloudFront distribution, you tell CloudFront where to find your content by specifying your origin servers."
+ >
+ Create distribution
+
+ );
+}
+
+function FormActions({ onCancelClick }: { onCancelClick: (event: CustomEvent) => void }) {
+ return (
+
+
+ Cancel
+
+
+ Create distribution
+
+
+ );
+}
+
+function BaseForm({
+ content,
+ onCancelClick,
+ errorText = null,
+ onSubmitClick,
+ header,
+}: {
+ content?: React.ReactNode;
+ onCancelClick: (event: CustomEvent) => void;
+ errorText?: React.ReactNode;
+ onSubmitClick?: () => void;
+ header?: React.ReactNode;
+}) {
+ return (
+ }
+ errorText={errorText}
+ errorIconAriaLabel="Error"
+ >
+ {content}
+
+
+ );
+}
+
+const defaultErrors: FormDataAttributesErrors = {
+ cloudFrontRootObject: '',
+ alternativeDomainNames: '',
+ s3BucketSelectedOption: '',
+ certificateExpiryDate: '',
+ certificateExpiryTime: '',
+ functions: '',
+ customHeaders: '',
+ functionFiles: [],
+ tags: '',
+ originId: '',
+ codeEditor: '',
+ httpVersion: '',
+ ipv6isOn: '',
+ functionFile: '',
+ originRequestPolicyName: '',
+ isOriginRequestPolicyNew: '',
+ cachePolicy: '',
+};
+
+const defaultData: FormDataAttributesValues = {
+ cloudFrontRootObject: '',
+ alternativeDomainNames: '',
+ s3BucketSelectedOption: null,
+ certificateExpiryDate: '',
+ certificateExpiryTime: '',
+ httpVersion: 'http2',
+ ipv6isOn: false,
+ functions: [],
+ originId: '',
+ customHeaders: [
+ {
+ key: '',
+ value: '',
+ },
+ ],
+ codeEditor: '',
+ tags: '',
+ functionFile: undefined,
+ functionFiles: [],
+ originRequestPolicyName: '',
+ isOriginRequestPolicyNew: false,
+ cachePolicy: null,
+};
+
+const fieldsToValidate = [
+ 'cloudFrontRootObject',
+ 's3BucketSelectedOption',
+ 'alternativeDomainNames',
+ 'certificateExpiryDate',
+ 'certificateExpiryTime',
+ 'functions',
+ 'originId',
+ 'codeEditor',
+] as const;
+
+export function FormFull({
+ loadHelpPanelContent,
+ header,
+ cachePolicyProps,
+}: {
+ loadHelpPanelContent: (value: number) => void;
+ header: React.ReactNode;
+ cachePolicyProps: CachePolicyProps;
+}) {
+ const [data, _setData] = useState(defaultData);
+ const setData = (updateObj = {}) => _setData(prevData => ({ ...prevData, ...updateObj }));
+
+ const handleCancelClick = () => {
+ // do nothing
+ };
+
+ return (
+
+
+
+
+
+
+
+ }
+ />
+ );
+}
+
+export const LimitedForm = ({
+ loadHelpPanelContent,
+ updateDirty,
+ onCancelClick,
+ header,
+}: {
+ loadHelpPanelContent: (value: number) => void;
+ updateDirty: (value: boolean) => void;
+ onCancelClick: (event: CustomEvent) => void;
+ header: React.ReactNode;
+}) => {
+ const [data, _setData] = useState(defaultData);
+ const setData = (updateObj = {}) => _setData(prevData => ({ ...prevData, ...updateObj }));
+
+ useEffect(() => {
+ const isDirty = JSON.stringify(data) !== JSON.stringify(defaultData);
+ updateDirty(isDirty);
+ }, [data, updateDirty]);
+
+ return (
+ }
+ />
+ );
+};
+
+export const FormWithValidation = ({
+ loadHelpPanelContent,
+ header,
+ cachePolicyProps,
+}: {
+ loadHelpPanelContent: (value: number) => void;
+ header: React.ReactNode;
+ cachePolicyProps: CachePolicyProps;
+}) => {
+ const [formErrorText, setFormErrorText] = useState(null);
+ const [data, _setData] = useState(defaultData);
+ const [errors, _setErrors] = useState(defaultErrors);
+
+ const setErrors = (updateObj: Partial = {}) =>
+ _setErrors(prevErrors => ({ ...prevErrors, ...updateObj }));
+ const setData = (updateObj: Partial = {}) =>
+ _setData(prevData => ({ ...prevData, ...updateObj }));
+
+ const refs: FormRefs = {
+ cloudFrontRootObject: useRef(null),
+ alternativeDomainNames: useRef(null),
+ s3BucketSelectedOption: useRef(null),
+ certificateExpiryDate: useRef(null),
+ certificateExpiryTime: useRef(null),
+ functions: useRef(null),
+ originId: useRef(null),
+ customHeaders: useRef(null),
+ codeEditor: useRef(null),
+ tags: useRef(null),
+ originRequestPolicyName: useRef(null),
+ cachePolicy: useRef(null),
+ };
+
+ const shouldFocus = (errorsState: FormDataAttributesErrors, attribute: FormDataAttributesKeys) => {
+ let shouldFocus = errorsState[attribute] && errorsState[attribute].length > 0;
+
+ if (attribute === 'functions' && !shouldFocus) {
+ shouldFocus = errorsState.functionFiles?.length !== undefined && errorsState.functionFiles.length > 0;
+ }
+
+ return shouldFocus;
+ };
+
+ const focusTopMostError = (errorsState: FormDataAttributesErrors) => {
+ for (const [attribute, ref] of Object.entries(refs)) {
+ if (shouldFocus(errorsState, attribute as FormDataAttributesKeys)) {
+ if (ref.current?.focus) {
+ return ref.current.focus();
+ }
+
+ if (ref.current?.focusAddButton) {
+ return ref.current.focusAddButton();
+ }
+ }
+ }
+ };
+
+ const validateCustomHeaders = () => {
+ // Custom header errors are embedded in individual header items
+ // customHeadersError here sets errors.customHeaders so that when there are
+ // errors on submission, customHeaders is focused
+ let customHeadersError = '';
+
+ const validatedItems = data.customHeaders.map(item => {
+ const { errorText: keyError } = validateField('customHeaders', item.key, 'name');
+ const { errorText: valueError } = validateField('customHeaders', item.value, 'value');
+
+ customHeadersError = keyError! || valueError!;
+
+ return { ...item, keyError, valueError };
+ });
+
+ setData({ customHeaders: validatedItems });
+ return customHeadersError;
+ };
+
+ const validateOriginRequestPolicyName = () => {
+ if (!data.isOriginRequestPolicyNew) {
+ return '';
+ }
+
+ const { errorText } = validateField('originRequestPolicyName', data.originRequestPolicyName);
+ return errorText || '';
+ };
+
+ const validateCachePolicy = () => {
+ const { errorText } = validateField('cachePolicy', cachePolicyProps.selectedPolicy);
+ return errorText || '';
+ };
+
+ const handleCancelClick = () => {
+ // do nothing
+ };
+
+ const onSubmit = () => {
+ setFormErrorText(
+ <>
+ You have reached the maximum amount of distributions you can create.{' '}
+
+ Learn more about distribution limits
+
+ >
+ );
+
+ const newErrors = { ...errors };
+
+ fieldsToValidate.forEach(attribute => {
+ const { errorText } = validateField(attribute, data[attribute], data[attribute]);
+ if (errorText) {
+ newErrors[attribute] = errorText;
+ }
+ });
+ newErrors.customHeaders = validateCustomHeaders();
+ newErrors.originRequestPolicyName = validateOriginRequestPolicyName();
+ newErrors.cachePolicy = validateCachePolicy();
+
+ setErrors(newErrors);
+ focusTopMostError(newErrors);
+ };
+
+ return (
+
+
+
+
+
+
+ }
+ onSubmitClick={onSubmit}
+ errorText={formErrorText}
+ />
+ );
+};
diff --git a/pages/demos/pages/form/components/headers-editor.tsx b/pages/demos/pages/form/components/headers-editor.tsx
new file mode 100644
index 0000000000..2df0f86261
--- /dev/null
+++ b/pages/demos/pages/form/components/headers-editor.tsx
@@ -0,0 +1,127 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import AttributeEditor, { AttributeEditorProps } from '@cloudscape-design/components/attribute-editor';
+import Autosuggest, { AutosuggestProps } from '@cloudscape-design/components/autosuggest';
+
+import validateField from '../form-validation-config';
+import { CustomHeader, FormDataAttributesValues, FormRefs } from '../types';
+
+const validateHeader = (key: T, value: CustomHeader[T], messageValue: string) => {
+ const { warningText, errorText } = validateField('customHeaders', value, messageValue);
+
+ const errorAttribute = key + 'Error';
+ const warningAttribute = key + 'Warning';
+ return { [errorAttribute]: errorText, [warningAttribute]: warningText };
+};
+
+interface HeaderEditorProps {
+ data: FormDataAttributesValues;
+ setData: (data: Partial) => void;
+ validation?: boolean;
+ refs?: FormRefs;
+}
+
+export default function HeadersEditor({ validation = false, refs, data, setData }: HeaderEditorProps) {
+ const { customHeaders: items } = data;
+
+ const onAddHeader: AttributeEditorProps['onAddButtonClick'] = () =>
+ setData({ customHeaders: [...items, {} as CustomHeader] });
+
+ const onRemoveHeader: AttributeEditorProps['onRemoveButtonClick'] = ({ detail: { itemIndex } }) => {
+ const itemsCopy = items.slice();
+ itemsCopy.splice(itemIndex, 1);
+ setData({ customHeaders: itemsCopy });
+ };
+
+ const updateItem = (update: Partial, item: CustomHeader, index: number) => {
+ const itemsCopy = items.slice();
+ const updatedItem = { ...item, ...update };
+ itemsCopy.splice(index, 1, updatedItem);
+ setData({ customHeaders: itemsCopy });
+ };
+
+ const onChange = (key: keyof CustomHeader, item: CustomHeader, index: number): AutosuggestProps['onChange'] => {
+ return ({ detail }) => {
+ let updateObj: Partial = { [key]: detail.value };
+
+ if (validation) {
+ const keyError = `${key}Error` as keyof CustomHeader;
+ const keyWarning = `${key}Warning` as keyof CustomHeader;
+ if (keyError || keyWarning) {
+ const validationTexts = validateHeader(key, detail.value, key === 'key' ? 'name' : key);
+ updateObj = { ...updateObj, ...validationTexts };
+ }
+ }
+
+ updateItem(updateObj, item, index);
+ };
+ };
+
+ const onBlur = (key: keyof CustomHeader, item: CustomHeader, index: number) => {
+ if (!validation) {
+ return;
+ }
+
+ const value = item[key];
+ const validationTexts = validateHeader(key, value, key === 'key' ? 'name' : key);
+ updateItem(validationTexts, item, index);
+ };
+
+ const definitions: AttributeEditorProps.FieldDefinition[] = [
+ {
+ label: 'Custom header name',
+ control: (item, index) => {
+ return (
+ onBlur('key', item, index)}
+ value={item.key || ''}
+ options={[{ value: 'Header-Name-1' }, { value: 'Header-Name-2' }, { value: 'Header-Name-3' }]}
+ enteredTextLabel={value => `Use: "${value}"`}
+ ariaRequired={true}
+ />
+ );
+ },
+ errorText: ({ keyError }) => keyError,
+ warningText: ({ keyWarning }) => keyWarning,
+ },
+ {
+ label: 'Custom header value',
+ control: (item, index) => {
+ return (
+ onBlur('value', item, index)}
+ options={[{ value: 'Value-1' }, { value: 'Value-2' }, { value: 'Value-3' }]}
+ enteredTextLabel={value => `Use: "${value}"`}
+ ariaRequired={true}
+ />
+ );
+ },
+ errorText: ({ valueError }) => valueError,
+ warningText: ({ valueWarning }) => valueWarning,
+ },
+ ];
+
+ return (
+
+ );
+}
diff --git a/pages/demos/pages/form/components/origin-panel.tsx b/pages/demos/pages/form/components/origin-panel.tsx
new file mode 100644
index 0000000000..c8511522c2
--- /dev/null
+++ b/pages/demos/pages/form/components/origin-panel.tsx
@@ -0,0 +1,148 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useState } from 'react';
+
+import Alert from '@cloudscape-design/components/alert';
+import Container from '@cloudscape-design/components/container';
+import FormField from '@cloudscape-design/components/form-field';
+import Header from '@cloudscape-design/components/header';
+import Input from '@cloudscape-design/components/input';
+import Multiselect, { MultiselectProps } from '@cloudscape-design/components/multiselect';
+import SpaceBetween from '@cloudscape-design/components/space-between';
+
+import { InfoLink } from '../../commons/common-components';
+import useContentOrigins from '../../commons/use-content-origins';
+import validateField from '../form-validation-config';
+import { FormPanelProps } from '../types';
+import HeadersEditor from './headers-editor';
+
+export default function OriginPanel({
+ loadHelpPanelContent,
+ validation = false,
+ data,
+ errors,
+ setData,
+ setErrors,
+ refs,
+}: FormPanelProps) {
+ const [contentOriginsState, contentOriginsHandlers] = useContentOrigins();
+ const [selectedOptions, setSelectedOptions] = useState([]);
+ const [contentPath, setContentPath] = useState('');
+
+ const validateOriginId = (value = data.originId) => {
+ const { errorText } = validateField('originId', value, value);
+
+ setErrors?.({ originId: errorText });
+ };
+
+ const onChangeOriginId = (value: string) => {
+ setData({ originId: value });
+
+ if (!validation || !errors) {
+ return;
+ }
+
+ if (errors.originId?.length > 0) {
+ validateOriginId(value);
+ }
+ };
+
+ const onBlurOriginId = () => {
+ if (!validation) {
+ return;
+ }
+
+ validateOriginId(data.originId);
+ };
+
+ return (
+ Origin settings}
+ >
+
+
+ Content origin - optional
+ >
+ }
+ info={ loadHelpPanelContent(4)} />}
+ description="The Amazon S3 bucket or web server that you want CloudFront to get your web content from."
+ i18nStrings={{ errorIconAriaLabel: 'Error' }}
+ >
+ !option.label.includes('NO-ACCESS'))}
+ selectedOptions={selectedOptions}
+ selectedAriaLabel="Selected"
+ onChange={event => setSelectedOptions(event.detail.selectedOptions)}
+ statusType={contentOriginsState.status}
+ deselectAriaLabel={option => `Remove option ${option.label} from selection`}
+ placeholder="Choose an S3 bucket or web server"
+ loadingText="Loading origins"
+ errorText="Error fetching origins."
+ recoveryText="Retry"
+ finishedText={
+ contentOriginsState.filteringText
+ ? `End of "${contentOriginsState.filteringText}" results`
+ : 'End of all results'
+ }
+ empty={contentOriginsState.filteringText ? "We can't find a match" : 'No origins'}
+ filteringType="manual"
+ filteringAriaLabel="Filter origins"
+ filteringClearAriaLabel="Clear"
+ />
+
+
+ Path to content - optional
+ >
+ }
+ info={ loadHelpPanelContent(5)} />}
+ description="The directory in your Amazon S3 bucket or your custom origin."
+ i18nStrings={{ errorIconAriaLabel: 'Error' }}
+ >
+ setContentPath(event.detail.value)} />
+
+ loadHelpPanelContent(6)} />}
+ description="This value lets you distinguish multiple origins in the same distribution from one another."
+ constraintText="Valid characters are a-z, A-Z, 0-9, hypens (-), and periods (.)."
+ errorText={errors?.originId}
+ i18nStrings={{ errorIconAriaLabel: 'Error' }}
+ >
+ onChangeOriginId(e.detail.value)}
+ onBlur={onBlurOriginId}
+ ref={refs?.originId}
+ />
+
+
+
+
+ {validation && (
+
+ To see the warning text, add empty character (space) into the "Custom header name" field.
+
+ )}
+
+
+
+ Provisioning less than 100 GiB of General Purpose (SSD) storage for high throughput workloads could result in
+ higher latencies upon exhaustion of the initial General Purpose (SSD) IO credit balance.
+
+
+
+ );
+}
diff --git a/pages/demos/pages/form/components/tags-panel.tsx b/pages/demos/pages/form/components/tags-panel.tsx
new file mode 100644
index 0000000000..7f34ed9700
--- /dev/null
+++ b/pages/demos/pages/form/components/tags-panel.tsx
@@ -0,0 +1,54 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React, { useState } from 'react';
+
+import Container from '@cloudscape-design/components/container';
+import Header from '@cloudscape-design/components/header';
+import TagEditor, { TagEditorProps } from '@cloudscape-design/components/tag-editor';
+
+import { tagEditorI18nStrings } from '../../../i18n-strings/tag-editor';
+import { InfoLink } from '../../commons/common-components';
+import { FormDataAttributesErrors, FormRefs } from '../types';
+
+interface TagsPanelProps {
+ loadHelpPanelContent: (value: number) => void;
+ refs?: FormRefs;
+ setErrors?: (errors: Partial) => void;
+}
+
+export default function TagsPanel({ loadHelpPanelContent, refs, setErrors }: TagsPanelProps) {
+ const [tags, setTags] = useState([]);
+
+ const onChange = ({ detail }: { detail: TagEditorProps.ChangeDetail }) => {
+ const { tags } = detail;
+ setTags(tags);
+
+ if (setErrors) {
+ setErrors({ tags: !detail.valid ? 'invalid' : '' });
+ }
+ };
+
+ return (
+ loadHelpPanelContent(9)} />}
+ 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."
+ >
+ Tags
+
+ }
+ >
+ window.FakeServer.GetTagKeys().then(({ TagKeys }) => TagKeys)}
+ valuesRequest={key => window.FakeServer.GetTagValues(key).then(({ TagValues }) => TagValues)}
+ i18nStrings={tagEditorI18nStrings}
+ ref={refs?.tags}
+ />
+
+ );
+}
diff --git a/pages/demos/pages/form/components/tools-content.tsx b/pages/demos/pages/form/components/tools-content.tsx
new file mode 100644
index 0000000000..fdc425ce52
--- /dev/null
+++ b/pages/demos/pages/form/components/tools-content.tsx
@@ -0,0 +1,427 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: MIT-0
+import React from 'react';
+
+import HelpPanel from '@cloudscape-design/components/help-panel';
+
+import { ExternalLink, ExternalLinkGroup } from '../../commons';
+
+/* eslint-disable react/jsx-key */
+export default [
+ Create distribution}
+ footer={
+
+ }
+ >
+
+ When you create an Amazon CloudFront distribution, you tell CloudFront where to find your content by specifying
+ your origin servers . An origin stores the original version of your objects (your files). For example, you
+ can specify an Amazon S3 bucket, an AWS Elemental MediaStore container, or an AWS Elemental MediaPackage channel.
+ You can also specify a custom origin , such as an Amazon EC2 instance or your own HTTP web server.
+
+
+ When CloudFront receives requests for your content, it gets the content from the origin and distributes it through
+ a worldwide network of data centers called edge locations . CloudFront uses the edge locations that are
+ closest to your viewers, so that your content is delivered with the lowest latency and best possible performance.
+
+ After you create the distribution, you can add more origins to it.
+ ,
+ Delivery method}
+ footer={
+
+ }
+ >
+
+ To create a distribution, you start by choosing the delivery method. Unless you use Adobe Media Server with RTMP,
+ your choice is always Web .{' '}
+
+
+ Note
+
+ You can't change the delivery method for a distribution after you create it.{' '}
+
+ Web
+ Create a web distribution if you want to do the following:
+
+ Speed up distribution of static and dynamic content, for example, .html, .css, .php, and graphics files.
+ Distribute media files using HTTP or HTTPS.
+ Add, update, or delete objects, and submit data from web forms.
+ Use live streaming to stream an event in real time.
+
+ RTMP
+
+ Create an RTMP distribution to speed up distribution of your streaming media files using the RTMP protocol of
+ Adobe Media Server. An RTMP distribution allows an end user to begin playing a media file before the file has
+ finished downloading from a CloudFront edge location.
+
+
+ To create an RTMP distribution, you must store the media files in an Amazon S3 bucket. To use CloudFront to serve
+ both a media player and the media files, you need two types of distributions: a web distribution for the media
+ player, and an RTMP distribution for the media files.
+
+ ,
+ Root object}
+ footer={
+
+ }
+ >
+
+ You can configure CloudFront to return a specific object (the default root object) when a user requests the root
+ URL for your web distribution instead of requesting an object in your distribution. Specifying a default root
+ object lets you avoid exposing the contents of your distribution or returning an error.{' '}
+
+ ,
+ Alternative domain names (CNAMEs)}
+ footer={
+
+ }
+ >
+
+ If you want to use your own domain names in the URLs for your files instead of the CloudFront domain name, enter
+ the domain names in the box. These custom domain names are known as alternative domain names (CNAMEs) . Both
+ web and RTMP distributions support CNAMEs.
+
+
+ For example, if you add www.example.com as your domain name, you would use the following URL to view{' '}
+ /images/image.jpg:
+
+ https://www.example.com/images/image.jpg
+ Before you begin
+ Before you add a CNAME, make sure that you do the following:
+
+ Register the domain name with Amazon Route 53 or another domain provider.
+
+ Add a certificate from an authorized certificate authority (CA) to CloudFront that covers the domain name that
+ you plan to use with the distribution, to validate that you are authorized to use the domain.
+
+
+ ,
+ Content origin}
+ footer={
+
+ }
+ >
+
+ CloudFront gets your objects (your files) from an origin that you specify, such as an S3 bucket or a web server.
+ The files must be publicly readable.
+
+
+ The Content origin dropdown list shows the AWS resources associated with the current AWS account. You can
+ choose from this list, or you can enter the domain name of a different origin. For example, you can specify the
+ following content origins:
+
+
+
+ Amazon S3 bucket – myawsbucket.s3.amazonaws.com
+
+
+ Amazon S3 bucket configured as a website –{' '}
+ https://bucket-name.s3-website-us-west-2.amazonaws.com
+
+
+ MediaStore container – mymediastore.data.mediastore.us-west-1.amazonaws.com
+
+
+ MediaPackage endpoint – mymediapackage.mediapackage.us-west-1.amazon.com
+
+
+ Amazon EC2 instance – ec2-203-0-113-25.compute-1.amazonaws.com
+
+
+ Elastic Load Balancing load balancer –{' '}
+ my-load-balancer-1234567890.us-west-2.elb.amazonaws.com
+
+
+ Your own web server – https://example.com
+
+
+ ,
+ Path to content}
+ footer={
+
+ }
+ >
+
+ If you want CloudFront to request your content from a directory in your origin, enter the directory path,
+ beginning with a slash (/). CloudFront appends the directory path to the origin, for example,{' '}
+ cf-origin.example.com/production/images. Don't add a slash (/) at the end of the path.
+
+ For example, suppose you've specified the following values for your distribution:
+
+
+ Origin Domain Name – An S3 bucket named myawsbucket
+
+
+ Origin Path – /production
+
+
+ Alternate Domain Names (CNAMEs) – example.com
+
+
+
+ When an end user enters example.com/index.html in a browser, CloudFront sends a request to Amazon S3
+ for myawsbucket/production/index.html.
+
+ ,
+ Origin ID}
+ footer={
+
+ }
+ >
+
+ The origin ID is a string that uniquely distinguishes the origin or origin group in this distribution. If
+ you create cache behaviors in addition to the default cache behavior, you use the ID that you specify here to
+ identify the origin or origin group that you want CloudFront to route a request to when the request matches the
+ path pattern for that cache behavior.
+
+ ,
+ Custom headers}
+ footer={
+
+ }
+ >
+
+ If you want CloudFront to include a custom header whenever it forwards a request to your origin, specify a header
+ name and value. All custom header names and values that you specify will be included in every request to this
+ origin. If a header was already supplied in the client request, it is overridden.{' '}
+
+ Custom headers have a variety of uses, including the following:
+
+
+ You can identify the requests that are forwarded to your custom origin by CloudFront. This is useful if you want
+ to know whether users are bypassing CloudFront or if you're using more than one CDN and you want information
+ about which requests are coming from each CDN. (If you're using an Amazon S3 origin and you turn on{' '}
+
+ Amazon S3 server access logging
+
+ , the logs don't include header information.)
+
+
+ If you've configured more than one CloudFront distribution to use the same origin, you can specify different
+ custom headers for the origins in each distribution and use the logs for your web server to distinguish between
+ the requests that CloudFront forwards for each distribution.
+
+
+ If some of your users use viewers that don't support cross-origin resource sharing (CORS), you can configure
+ CloudFront to forward the Origin header to your origin. That will cause your origin to return the{' '}
+ Access-Control-Allow-Origin header for every request.
+
+
+ You can use custom headers together and, optionally, signed URLs or signed cookies, to control access to content
+ on a custom origin. If you configure your custom origin to respond to requests only if they include a custom
+ header, you can prevent users from bypassing CloudFront and submitting requests directly to your origin.
+
+
+ ,
+ Cache behavior settings}
+ footer={
+
+ }
+ >
+
+ A cache behavior lets you configure a variety of CloudFront functionality for a given URL path pattern for
+ files on your website. For example, one cache behavior might apply to all .jpg files in the images{' '}
+ directory on a web server that you're using as an origin server for CloudFront. The functionality that you can
+ configure for each cache behavior includes the following:
+
+
+
+ The protocol policy that you want viewers to use to access your content in CloudFront edge locations:
+