diff --git a/examples-list.json b/examples-list.json index ba3e9f1e..9bdf0bce 100644 --- a/examples-list.json +++ b/examples-list.json @@ -1,104 +1,24 @@ [ { - "path": "cards", - "title": "Card view" + "path": "bingo-home", + "title": "Bingo - Home" }, { - "path": "chat", - "title": "Chat" - }, - { - "path": "delete-one-click", - "title": "One-click delete" - }, - { - "path": "delete-with-simple-confirmation", - "title": "Delete with simple confirmation" + "path": "bingo-ideas", + "title": "Bingo - Share an idea" }, { "path": "delete-with-additional-confirmation", "title": "Delete with additional confirmation" }, - { - "path": "details", - "title": "Details page" - }, { "path": "details-hub", "title": "Details page as hub" }, - { - "path": "details-tabs", - "title": "Details page with tabs" - }, - { - "path": "edit", - "title": "Edit resource" - }, { "path": "form", "title": "Single page create" }, - { - "path": "form-unsaved-changes", - "title": "Communicate unsaved changes" - }, - { - "path": "form-validation", - "title": "Form validation" - }, - { - "path": "product-detail-page", - "title": "Product detail page" - }, - { - "path": "server-side-table", - "title": "Table view (server-side)" - }, - { - "path": "server-side-table-property-filter", - "title": "Table property filter (server-side)" - }, - { - "path": "table-property-filter", - "title": "Table property filter" - }, - { - "path": "table-select-filter", - "title": "Table with select filter" - }, - { - "path": "table-date-filter", - "title": "Table view with date range picker filter" - }, - { - "path": "table-saved-filters", - "title": "Table view with saved filter sets" - }, - { - "path": "table", - "title": "Table view" - }, - { - "path": "table-editable", - "title": "Table view with inline editing" - }, - { - "path": "table-expandable", - "title": "Table view with expandable rows" - }, - { - "path": "manage-tags", - "title": "Manage tags" - }, - { - "path": "read-from-s3", - "title": "Read from Amazon S3" - }, - { - "path": "write-to-s3", - "title": "Write to Amazon S3" - }, { "path": "wizard", "title": "Multipage create" @@ -106,25 +26,5 @@ { "path": "dashboard", "title": "Service dashboard" - }, - { - "path": "configurable-dashboard", - "title": "Configurable dashboard" - }, - { - "path": "onboarding", - "title": "Hands-on tutorials" - }, - { - "path": "split-panel-multiple", - "title": "Split view" - }, - { - "path": "split-panel-comparison", - "title": "Split view with details comparison" - }, - { - "path": "non-console", - "title": "Top navigation" } ] diff --git a/scripts/generate-html-files.js b/scripts/generate-html-files.js index f84f7d3e..d048379e 100644 --- a/scripts/generate-html-files.js +++ b/scripts/generate-html-files.js @@ -15,6 +15,17 @@ function getPageContent(pageName, { title }) { +
+ + + +
`, }); @@ -41,12 +52,65 @@ function createHtml({ title, headImports, bodyImports, bodyContent }) { + ${headImports} ${bodyContent} + ${bodyImports} `; diff --git a/src/common/theme-definition.tsx b/src/common/theme-definition.tsx new file mode 100644 index 00000000..61423859 --- /dev/null +++ b/src/common/theme-definition.tsx @@ -0,0 +1,76 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 +import { Theme } from '@cloudscape-design/components/theming'; + +const customTheme: Theme = { + tokens: { + colorBackgroundButtonPrimaryDefault: { light: '{colorGrey800}', dark: '{colorGrey200}' }, + colorBackgroundButtonPrimaryHover: { light: '{colorGrey650}', dark: '{colorGrey300}' }, + colorBackgroundButtonPrimaryActive: { light: '{colorGrey800}', dark: '{colorGrey200}' }, + + colorBorderButtonPrimaryDefault: { light: '{colorGrey800}', dark: '{colorGrey200}' }, + colorBorderButtonPrimaryHover: { light: '{colorGrey650}', dark: '{colorGrey300}' }, + colorBorderButtonPrimaryActive: { light: '{colorGrey800}', dark: '{colorGrey200}' }, + + colorTextButtonPrimaryActive: { light: '{colorWhite}', dark: '{colorGrey900}' }, + colorTextButtonPrimaryHover: { light: '{colorGrey100}', dark: '{colorGrey900}' }, + colorTextButtonPrimaryDefault: { light: '{colorWhite}', dark: '{colorGrey900}' }, + + colorBackgroundButtonNormalDefault: { light: '{colorWhite}', dark: '{colorGrey800}' }, + colorBackgroundButtonNormalActive: { light: '{colorWhite}', dark: '{colorGrey800}' }, + colorBackgroundButtonNormalHover: { light: '{colorGrey100}', dark: '{colorGrey650}' }, + + colorBorderButtonNormalDefault: { light: '{colorGrey900}', dark: '{colorGrey200}' }, + colorBorderButtonNormalHover: { light: '{colorGrey900}', dark: '{colorGrey100}' }, + colorBorderButtonNormalActive: { light: '{colorGrey900}', dark: '{colorGrey200}' }, + + colorTextButtonNormalActive: { light: '{colorGrey800}', dark: '{colorWhite}' }, + colorTextButtonNormalHover: { light: '{colorGrey800}', dark: '{colorWhite}' }, + colorTextButtonNormalDefault: { light: '{colorGrey800}', dark: '{colorWhite}' }, + + colorTextLinkDefault: { light: '{colorGrey900}', dark: '{colorGrey100}' }, + colorTextLinkHover: { light: '{colorGrey700}', dark: '{colorWhite}' }, + colorTextAccent: { light: '{colorBlue600}', dark: '{colorBlue500}' }, + + colorBackgroundHomeHeader: { light: '{colorAwsSquidInk}', dark: '{colorBlack}' }, + fontFamilyBase: "'Amazon Ember Display', Amazon Ember, Arial, sans-serif", + fontFamilyMonospace: "'Amazon Ember Mono', 'Monaco', 'Courier New', monospace", + + fontSizeBodyM: '16px', + fontSizeBodyS: '14px', + lineHeightBodyM: '24px', + + fontSizeHeadingXl: '36px', + fontSizeHeadingL: '28px', + fontSizeHeadingM: '24px', + fontSizeHeadingS: '20px', + fontSizeHeadingXs: '18px', + fontSizeDisplayL: '48px', + + lineHeightHeadingXl: '44px', + lineHeightHeadingL: '36px', + lineHeightHeadingM: '32px', + lineHeightHeadingS: '28px', + lineHeightHeadingXs: '24px', + lineHeightDisplayL: '56px', + + fontWeightButton: '700', + fontWeightHeadingXl: '500', + fontWeightHeadingL: '500', + fontWeightHeadingM: '500', + fontWeightHeadingS: '500', + fontWeightHeadingXs: '500', + + borderRadiusButton: '24px', + + colorBackgroundNotificationSeverityCritical: { light: '#FFC2C2' }, + colorBackgroundNotificationSeverityHigh: { light: '#FFC0AD' }, + colorBackgroundNotificationSeverityMedium: { light: '#FFFBBD' }, + colorBackgroundNotificationSeverityLow: { light: '#D9FFD6' }, + colorBackgroundNotificationSeverityNeutral: { light: '{colorGrey200}' }, + + colorTextNotificationSeverityNeutral: { light: '{colorGrey700}' }, + }, +} as any; + +export default customTheme; diff --git a/src/pages/bingo-home/app.tsx b/src/pages/bingo-home/app.tsx new file mode 100644 index 00000000..d6de78fb --- /dev/null +++ b/src/pages/bingo-home/app.tsx @@ -0,0 +1,105 @@ +// 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 Tabs from '@cloudscape-design/components/tabs'; + +import { CustomAppLayout, Navigation, Notifications } 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'; + +export function App() { + const [toolsIndex, setToolsIndex] = useState(0); + const [toolsOpen, setToolsOpen] = useState(false); + const appLayout = useRef(null); + + const loadHelpPanelContent = (index: number): void => { + setToolsIndex(index); + setToolsOpen(true); + appLayout.current?.focusToolsClose(); + }; + + return ( + + + + + , + }, + { + 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/src/pages/bingo-home/components/details.tsx b/src/pages/bingo-home/components/details.tsx new file mode 100644 index 00000000..03a18715 --- /dev/null +++ b/src/pages/bingo-home/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={}> + Details + + } + > + + +); diff --git a/src/pages/bingo-home/components/logs-table.tsx b/src/pages/bingo-home/components/logs-table.tsx new file mode 100644 index 00000000..03b43a41 --- /dev/null +++ b/src/pages/bingo-home/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={ +
+ + + + + } + > + Logs +
+ } + filter={ + + } + pagination={} + /> + ); +} diff --git a/src/pages/bingo-home/index.jsx b/src/pages/bingo-home/index.jsx new file mode 100644 index 00000000..00401556 --- /dev/null +++ b/src/pages/bingo-home/index.jsx @@ -0,0 +1,199 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 +import React, { useRef, useState } from 'react'; +import { createRoot } from 'react-dom/client'; + +import { applyTheme } from '@cloudscape-design/components/theming'; + +import customTheme from '../../common/theme-definition'; +applyTheme({ theme: customTheme }); + +import { useCollection } from '@cloudscape-design/collection-hooks'; +import { + AppLayout, + Box, + Button, + Container, + ContentLayout, + Header, + Link, + Pagination, + SpaceBetween, + Table, + Tabs, + TextFilter, +} from '@cloudscape-design/components'; + +import { getHeaderCounterText, getTextFilterCounterText, renderAriaLive } from '../../i18n-strings'; +import { CustomNavBar } from '../bingo-ideas/index'; +import { SideContentHome, TableEmptyState, TableNoMatchState } from '../commons/common-components'; +import DataProvider from '../commons/data-provider'; +import thumbnail1Image from '../commons/thumbnail-1.png'; +import thumbnail1Image2 from '../commons/thumbnail-2.png'; +import thumbnail1Image3 from '../commons/thumbnail-3.png'; +import { useAsyncData } from '../commons/use-async-data'; +import { DashboardSideNavigation } from '../dashboard/components/side-navigation'; +import { BehaviorsTable, EmptyTable, ForYou, OriginsTable, TagsTable } from '../details/common-components'; +import { INVALIDATIONS_COLUMN_DEFINITIONS, LOGS_COLUMN_DEFINITIONS } from '../details/details-config'; +import { logsTableAriaLabels } from '../details-hub/commons'; + +import '../../styles/base.scss'; +import '../../styles/product-page.scss'; + +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={ +
+ + + + + } + > + Logs +
+ } + filter={ + + } + pagination={} + /> + + ); +} + +function CarouselCard({ imageSrc }) { + return ( + + ), + height: 120, + position: 'top', + }} + > + + Podcast by Eli Volluri + + See details + + + + ); +} + +function Carousel() { + return ( + + + + + + ); +} + +const App = () => { + const appLayout = useRef(null); + + const loadHelpPanelContent = () => { + appLayout.current?.focusToolsClose(); + }; + + const tabs = [ + { label: 'For you', id: 'details', content: }, + { label: 'Following', id: 'logs', content: }, + { label: 'Popular', id: 'origins', content: }, + { label: 'Watch', id: 'behaviors', content: }, + { + label: 'Listen', + id: 'invalidations', + content: , + }, + { label: 'Custom feed', id: 'tags', content: }, + ]; + + return ( + } + toolsHide + maxContentWidth={9999999999999} + breadcrumbs={} + content={ + +
+ + +
+ + + +
Spotlight
+
+ + + +
+ +
Trending
+ +
+
+
+
+
+ } + /> + ); +}; + +createRoot(document.getElementById('app')).render(); diff --git a/src/pages/bingo-ideas/hero-header.tsx b/src/pages/bingo-ideas/hero-header.tsx new file mode 100644 index 00000000..2ed513ec --- /dev/null +++ b/src/pages/bingo-ideas/hero-header.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 Box from '@cloudscape-design/components/box'; +import Button from '@cloudscape-design/components/button'; +import Grid from '@cloudscape-design/components/grid'; +import SpaceBetween from '@cloudscape-design/components/space-between'; + +function HeroHeader() { + return ( + + + +
+ Share an idea + + Your ideas help us know where we could make an impact for our customers. Have an idea on mind? We would + like to hear it! + +
+ + + + + + +
+
+
+ ); +} + +export { HeroHeader }; diff --git a/src/pages/bingo-ideas/images/aws-partner-badge.png b/src/pages/bingo-ideas/images/aws-partner-badge.png new file mode 100644 index 00000000..d2cdb862 Binary files /dev/null and b/src/pages/bingo-ideas/images/aws-partner-badge.png differ diff --git a/src/pages/bingo-ideas/images/video-thumbnail.jpg b/src/pages/bingo-ideas/images/video-thumbnail.jpg new file mode 100644 index 00000000..f0d252db Binary files /dev/null and b/src/pages/bingo-ideas/images/video-thumbnail.jpg differ diff --git a/src/pages/bingo-ideas/index.tsx b/src/pages/bingo-ideas/index.tsx new file mode 100644 index 00000000..213ceead --- /dev/null +++ b/src/pages/bingo-ideas/index.tsx @@ -0,0 +1,574 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 +import React, { useState } from 'react'; +import { createRoot } from 'react-dom/client'; + +import { applyTheme } from '@cloudscape-design/components/theming'; + +import customTheme from '../../common/theme-definition'; +applyTheme({ theme: customTheme }); +import { + AppLayout, + AttributeEditor, + Badge, + Box, + ContentLayout, + DatePicker, + FormField, + Header, + Icon, + Input, + Link, + Pagination, + Select, + SpaceBetween, + Table, + Tabs, +} from '@cloudscape-design/components'; +import Button from '@cloudscape-design/components/button'; + +import anonymousAvatar from '../commons/avatar-a.png'; +import headerGradientImageDark from '../commons/bg-gradient-purple-dark.png'; +import headerGradientImageLight from '../commons/bg-gradient-purple-light.png'; +import { SideContent } from '../commons/common-components'; +import { DashboardSideNavigation } from '../dashboard/components/side-navigation'; +import { HeroHeader } from './hero-header'; + +import '../../styles/product-page.scss'; + +interface Item { + name: JSX.Element | string; + alt: JSX.Element | string; + description: string; + type: string; + size?: string; + comments?: string; + ideator?: JSX.Element | string; + date?: string; +} + +interface ColumnDefinition { + id: string; + header: string; + cell: (item: Item) => JSX.Element | string; + sortingField?: string; + isRowHeader?: boolean; +} + +interface ColumnDisplay { + id: string; + visible: boolean; +} + +function Ideator() { + return ( +
+ + + johndoe + +
+ ); +} + +const CustomTable: React.FC = () => { + const [sortingColumn, setSortingColumn] = useState<{ sortingField?: string }>({ + sortingField: 'name', + }); + const [sortingDescending, setSortingDescending] = useState(false); + + const [selectedOption] = React.useState(null); + + return ( +
item.name, + sortingField: 'name', + isRowHeader: true, + }, + { + id: 'ideator', + header: 'Ideator', + cell: (item: Item) => item.ideator, + sortingField: 'ideator', + isRowHeader: true, + }, + { + id: 'date', + header: 'Published', + cell: (item: Item) => item.date, + sortingField: 'date', + isRowHeader: true, + }, + { + id: 'value', + header: 'Status', + cell: (item: Item) => item.alt, + sortingField: 'alt', + }, + { + id: 'type', + header: ( + + <>Liked + + + ), + cell: (item: Item) => item.type, + sortingField: 'type', + }, + { + id: 'description', + header: ( + + <>Views + + + ), + cell: (item: Item) => item.description, + sortingField: 'description', + }, + { + id: 'comments', + header: ( + + <>Comments + + + ), + cell: (item: Item) => item.comments, + sortingField: 'comments', + }, + ] as ColumnDefinition[] + } + columnDisplay={ + [ + { id: 'variable', visible: true }, + { id: 'ideator', visible: true }, + { id: 'date', visible: true }, + { id: 'value', visible: true }, + { id: 'type', visible: true }, + { id: 'description', visible: true }, + { id: 'comments', visible: true }, + ] as ColumnDisplay[] + } + enableKeyboardNavigation + items={ + [ + { + name: ( + + Serverless: Alarms + + ), + ideator: , + date: '5 hours ago', + alt: Neutral, + description: '27', + type: '125', + comments: '36', + }, + { + name: ( + + Model compatibility + + ), + ideator: , + date: '5 hours ago', + alt: Delivered, + description: '27', + type: '115', + comments: '42', + }, + { + name: ( + + Serverless: Alarms + + ), + ideator: , + date: '7 hours ago', + alt: Neutral, + description: '36', + type: '134', + comments: '21', + }, + { + name: ( + + AWSome Quiz + + ), + ideator: , + date: '10 hours ago', + alt: Investigating, + description: '34', + type: '125', + comments: '12', + }, + { + name: ( + + Serverless: Container + + ), + ideator: , + date: '12 hours ago', + alt: Delivered, + description: '56', + type: '132', + comments: '21', + }, + { + name: ( + + Effective deployment + + ), + ideator: , + date: 'May 12, 2025', + alt: Neutral, + description: '32', + type: '124', + comments: '15', + }, + { + name: ( + + Serverless: Alarms + + ), + ideator: , + date: 'May 13, 2025', + alt: Neutral, + description: '14', + type: '125', + comments: '6', + }, + { + name: ( + + Serverless: Alarms + + ), + ideator: , + date: 'May 13, 2025', + alt: Delivered, + description: '51', + type: '115', + comments: '4', + }, + { + name: ( + + Serverless: Alarms + + ), + ideator: , + date: 'May 15, 2025', + alt: Neutral, + description: '2', + type: '134', + comments: '26', + }, + { + name: ( + + Serverless: Alarms + + ), + ideator: , + date: 'May 20, 2025', + alt: Investigating, + description: '21', + type: '125', + comments: '42', + }, + { + name: ( + + Serverless: Alarms + + ), + ideator: , + date: 'May 21, 2025', + alt: Delivered, + description: '31', + type: '132', + comments: '31', + }, + ] as Item[] + } + sortingColumn={sortingColumn} + sortingDescending={sortingDescending} + onSortingChange={event => { + setSortingColumn(event.detail.sortingColumn); + setSortingDescending(event.detail.isDescending ?? false); + }} + trackBy="name" + variant="embedded" + filter={ +
+
+ +
+
+ +
+
+ } + header={
Product ideas
} + pagination={} + /> + ); +}; + +function CustomAttributeEditor() { + const [items, setItems] = React.useState([ + { key: 'some-key-1', value: 'some-value-1' }, + { key: 'some-key-2', value: 'some-value-2' }, + ]); + const [value, setValue] = React.useState(''); + return ( + + setItems([...items, { key: '', value: '' }])} + onRemoveButtonClick={({ detail: { itemIndex } }) => { + const tmpItems = [...items]; + tmpItems.splice(itemIndex, 1); + setItems(tmpItems); + }} + items={items} + addButtonText="Add new item" + definition={[ + { + label: 'Key', + control: item => , + }, + { + label: 'Value', + control: item => , + }, + ]} + empty="No items associated with the resource." + /> + + setValue(detail.value)} + value={value} + openCalendarAriaLabel={selectedDate => + 'Choose certificate expiry date' + (selectedDate ? `, selected date is ${selectedDate}` : '') + } + placeholder="YYYY/MM/DD" + /> + + + ); +} + +function ShareAnIdea() { + return ( + + + + ), + }, + { + label: 'My ideas', + id: 'second', + content: ( + + + + ), + }, + ]} + /> + ); +} + +export function CustomNavBar() { + const [value, setValue] = React.useState(''); + return ( + +
+ +
+ setValue(detail.value)} + placeholder="What are you looking for?" + type="search" + /> +
+ +
+ +
+
+
+ ); +} + +function App() { + return ( +
+ {/* */} + } + disableContentPaddings + breadcrumbs={} + content={ + hello
} + header={} + defaultPadding={true} + maxContentWidth={1440} + disableOverlap={true} + headerBackgroundStyle={mode => { + if (mode === 'light') { + return `url(${headerGradientImageLight}) center center/100% 100%`; + } else { + return `url(${headerGradientImageDark}) center center/100% 100%`; + } + }} + > +
+ + +
+ +
+
+ + } + /> + + ); +} + +createRoot(document.getElementById('app')!).render(); diff --git a/src/pages/bingo-ideas/on-this-page.tsx b/src/pages/bingo-ideas/on-this-page.tsx new file mode 100644 index 00000000..8aabcce0 --- /dev/null +++ b/src/pages/bingo-ideas/on-this-page.tsx @@ -0,0 +1,57 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 +import React from 'react'; + +import AnchorNavigation from '@cloudscape-design/components/anchor-navigation'; +import Box from '@cloudscape-design/components/box'; +import ExpandableSection from '@cloudscape-design/components/expandable-section'; + +function OnThisPageNavigation({ variant }: { variant: 'mobile' | 'side' }) { + const anchorNavigation = ( + + ); + + return variant === 'side' ? ( +
+ + On this page + + {anchorNavigation} +
+ ) : ( + + {anchorNavigation} + + ); +} + +export { OnThisPageNavigation }; diff --git a/src/pages/bingo-ideas/root.tsx b/src/pages/bingo-ideas/root.tsx new file mode 100644 index 00000000..3858e218 --- /dev/null +++ b/src/pages/bingo-ideas/root.tsx @@ -0,0 +1,430 @@ +// 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 Box from '@cloudscape-design/components/box'; +import BreadcrumbGroup from '@cloudscape-design/components/breadcrumb-group'; +import Button from '@cloudscape-design/components/button'; +import Container from '@cloudscape-design/components/container'; +import ContentLayout from '@cloudscape-design/components/content-layout'; +import Flashbar from '@cloudscape-design/components/flashbar'; +import Header from '@cloudscape-design/components/header'; +import { I18nProvider } from '@cloudscape-design/components/i18n'; +import enMessages from '@cloudscape-design/components/i18n/messages/all.en.json'; +import Link from '@cloudscape-design/components/link'; +import SpaceBetween from '@cloudscape-design/components/space-between'; +import Table from '@cloudscape-design/components/table'; +import TextContent from '@cloudscape-design/components/text-content'; +import { applyTheme } from '@cloudscape-design/components/theming'; + +import { isVisualRefresh } from '../../common/apply-mode'; +import { useDisclaimerFlashbarItem } from '../commons/disclaimer-flashbar-item'; +import { HeroHeader } from './hero-header'; +import badgePartnerAdvanced from './images/aws-partner-badge.png'; +import videoThumbnail from './images/video-thumbnail.jpg'; +import { OnThisPageNavigation } from './on-this-page'; +import { UserFeedback } from './user-feedback'; + +import '../../styles/product-page.scss'; + +// Apply runtime theming for a more suitable page background color +if (!isVisualRefresh) { + applyTheme({ + theme: { + tokens: { + colorBackgroundLayoutMain: { + light: 'white', + dark: '{colorGrey900}', + }, + }, + }, + }); +} + +function ProductOverview() { + return ( +
+
+ Product overview +
+ +
+ + Receive real-time data insights to build process improvements, track key performance indicators, and predict + future business outcomes. Create a new Cloud Data Solution account to receive a 30 day free trial of all + Cloud Data Solution services. + + + Gather actionable analytics at scale to improve customer experiences and application development. Plus, + leverage large data sets to drive business decisions. + +
+ +
+ + Product details + + +
+
Sold by
+
+ + Cloud Data + +
+ +
Product category
+
Software as a Service
+ +
Delivery method
+
+ QuickLaunch +
+ CloudFormation Template +
+
+
+
+ + + + Video thumbnail + + ), + }} + > + + 6 min + + + Video Title + + + + + + +
+
Highlights
+ +
    +
  • Real-time analytic alerts to detect anomalies across your products and services.
  • +
  • + Prepare data sets to increase visibility into areas of your organization to make business decisions. +
  • +
  • Build and manage large data sets to gain deeper insights and track trends.
  • +
  • Begin a 30 day free trial to get actionable insights today.
  • +
+
+
+ +
+
Vendor insights
+ + + The current version of this product contains a security profile, and has acquired the certifications + below. +
+ + View all profiles for this product + +
+ AWS Partner Advanced badge +
+
+
+
+ ); +} + +function Pricing() { + return ( +
+
+ Pricing +
+ +
+ + Use this tool to estimate the software and infrastructure costs based your configuration choices. Your usage + and costs might be different from this estimate. The costs will be reflected on your monthly billing + reports.{' '} + + Contact us + {' '} + to request contract pricing for this product. + +
+ +
Cloud Data Solution} + columnDefinitions={[ + { header: 'Units', cell: item => item.units }, + { header: 'Description', cell: item => item.description }, + { header: '12 months', cell: item => item['12months'] }, + { header: '24 months', cell: item => item['24months'] }, + { header: '36 months', cell: item => item['36months'] }, + ]} + items={[ + { + units: 'Elite package', + description: '50 users, each user can backup up to 20 devices', + '12months': '$1,200', + '24months': '$2,400', + '36months': '$3,600', + }, + { + units: 'Premium package', + description: '30 users, each user can backup up to 10 devices', + '12months': '$840', + '24months': '$1,680', + '36months': '$2,520', + }, + { + units: 'Basic package', + description: '10 users, each user can backup up to 2 devices', + '12months': '$840', + '24months': '$1,680', + '36months': '$2,520', + }, + ]} + /> + + + ); +} + +function Details() { + return ( +
+ + Details + + +
+
Delivery method
+ + Software as a Service (SaaS) is a delivery model for software applications whereby the + vendor hosts and operates the application over the Internet. Customers pay for using the software without + owning the underlying infrastructure. With SaaS Contracts, customers will pay for usage through their bill.{' '} + + Learn more + + +
+ +
+
Terms and conditions
+ + By subscribing to this product you agree to terms and conditions outlined in the product{' '} + + End User License Agreement (EULA) + + . + +
+
+
+ ); +} + +function ProductCard({ + title, + vendor, + logo, + category, + description, + isNew = false, +}: { + title: string; + vendor: string; + logo: string; + category: string; + description: string; + isNew?: boolean; +}) { + return ( +
  • + + {`${title} + + + + {title} + + By {vendor} + {category} + {isNew && New} + + {description} + + + +
  • + ); +} + +function MoreFromVendor() { + return ( +
    + + More from this vendor + +
      + + +
    +
    + ); +} + +function Support() { + return ( +
    + + Support + + + +
    +
    Cloud Data Solution
    + + Your purchase also includes 24x7 support from Cloud Data. You can log a support ticket for any issues + directly from your Cloud One console. If you experience any issues or have questions, please contact our + Cloud Security experts by email at{' '} + + mock@example.com + + . + +
    +
    +
    Infrastructure
    + + Cloud Support is a one-on-one, fast-response support channel that is staffed 24x7x365 with experienced and + technical support engineers. The service helps customers of all sizes and technical abilities to + successfully utilize the products and features provided by Cloud Data. + + + Learn more + +
    +
    +
    Refund policy
    + The service does not currently support refunds, but you can cancel at any time. +
    +
    +
    + ); +} + +function RelatedProducts() { + return ( +
    + + Related products and services + +
      + + +
    +
    + ); +} + +export function App() { + const [disclaimerDismissed, dismissDisclaimer] = useState(false); + const disclaimerItem = useDisclaimerFlashbarItem(() => dismissDisclaimer(true)); + const showFlashbar = !disclaimerDismissed && disclaimerItem; + const headerVariant = isVisualRefresh ? 'high-contrast' : 'divider'; + + return ( + + } + breadcrumbs={ + + } + headerVariant={headerVariant} + header={} + defaultPadding={true} + maxContentWidth={1040} + disableOverlap={true} + > +
    +
    + +
    + + + +
    + + +
    + + + +
    + + +
    +
    +
    + ); +} diff --git a/src/pages/bingo-ideas/user-feedback.tsx b/src/pages/bingo-ideas/user-feedback.tsx new file mode 100644 index 00000000..bb713387 --- /dev/null +++ b/src/pages/bingo-ideas/user-feedback.tsx @@ -0,0 +1,79 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 +import React, { useRef, useState } from 'react'; +import { flushSync } from 'react-dom'; + +import Box from '@cloudscape-design/components/box'; +import Button from '@cloudscape-design/components/button'; +import Icon from '@cloudscape-design/components/icon'; +import SpaceBetween from '@cloudscape-design/components/space-between'; + +type Sentiment = 'yes' | 'no'; + +function UserFeedback() { + const [loadingSentiment, setLoadingSentiment] = useState(null); + const [successfulSentiment, setSuccessfulSentiment] = useState(null); + const successRef = useRef(null); + + const submitSentiment = (sentiment: Sentiment) => { + setLoadingSentiment(sentiment); + setSuccessfulSentiment(null); + + setTimeout(() => { + flushSync(() => { + setSuccessfulSentiment(sentiment); + setLoadingSentiment(null); + }); + successRef.current?.focus(); + }, 500); + }; + + return ( + + Was this page helpful? + {!successfulSentiment && ( + + + + + )} + + {successfulSentiment && ( +
    + + {successfulSentiment === 'yes' ? ( + + Helpful.{' '} + + ) : ( + + Not helpful.{' '} + + )} + Thanks, your feedback has been recorded. + +
    + )} +
    + ); +} + +export { UserFeedback }; diff --git a/src/pages/commons/article-image.png b/src/pages/commons/article-image.png new file mode 100644 index 00000000..1162fb91 Binary files /dev/null and b/src/pages/commons/article-image.png differ diff --git a/src/pages/commons/avatar-a.png b/src/pages/commons/avatar-a.png new file mode 100644 index 00000000..704a5854 Binary files /dev/null and b/src/pages/commons/avatar-a.png differ diff --git a/src/pages/commons/avatar-b.png b/src/pages/commons/avatar-b.png new file mode 100644 index 00000000..22f0339d Binary files /dev/null and b/src/pages/commons/avatar-b.png differ diff --git a/src/pages/commons/aws-logo.svg b/src/pages/commons/aws-logo.svg new file mode 100644 index 00000000..323c203e --- /dev/null +++ b/src/pages/commons/aws-logo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/pages/commons/bg-gradient-green-light.png b/src/pages/commons/bg-gradient-green-light.png new file mode 100644 index 00000000..e5d8ba91 Binary files /dev/null and b/src/pages/commons/bg-gradient-green-light.png differ diff --git a/src/pages/commons/bg-gradient-purple-dark.png b/src/pages/commons/bg-gradient-purple-dark.png new file mode 100644 index 00000000..a742ba1f Binary files /dev/null and b/src/pages/commons/bg-gradient-purple-dark.png differ diff --git a/src/pages/commons/bg-gradient-purple-light.png b/src/pages/commons/bg-gradient-purple-light.png new file mode 100644 index 00000000..4075343a Binary files /dev/null and b/src/pages/commons/bg-gradient-purple-light.png differ diff --git a/src/pages/commons/common-components.tsx b/src/pages/commons/common-components.tsx index b5c6e671..b0441520 100644 --- a/src/pages/commons/common-components.tsx +++ b/src/pages/commons/common-components.tsx @@ -2,13 +2,22 @@ // SPDX-License-Identifier: MIT-0 import React, { forwardRef } from 'react'; -import AppLayout, { AppLayoutProps } from '@cloudscape-design/components/app-layout'; -import Badge from '@cloudscape-design/components/badge'; -import Box from '@cloudscape-design/components/box'; -import Button from '@cloudscape-design/components/button'; +import { + AppLayout, + AppLayoutProps, + Badge, + Box, + Button, + ColumnLayout, + SpaceBetween, + TopNavigation, +} from '@cloudscape-design/components'; import { I18nProvider } from '@cloudscape-design/components/i18n'; import enMessages from '@cloudscape-design/components/i18n/messages/all.en.json'; -import SpaceBetween from '@cloudscape-design/components/space-between'; + +import awsLogo from '../commons/aws-logo.svg'; + +import '../../styles/base.scss'; // backward compatibility export * from './index'; @@ -63,10 +72,194 @@ export const TableEmptyState = ({ resourceName }: { resourceName: string }) => ( ); -export const CustomAppLayout = forwardRef(function CustomAppLayout(props, ref) { +export const CustomAppLayout = forwardRef((props, ref) => { return ( ); }); + +CustomAppLayout.displayName = 'CustomAppLayout'; + +export function TopNav() { + return ( + + ); +} + +export function SideContent() { + return ( + + + Recents + + + + 6h ago + + AWS Secrets manager announces open source release of secrets manager agent + + + + 6h ago + + AWS Secrets manager announces open source release of secrets manager agent + + + + 6h ago + + AWS Secrets manager announces open source release of secrets manager agent + + + + + ); +} + +export function SideContentHome() { + return ( + + + + Recents + + + + 6h ago + + AWS Secrets manager announces open source release of secrets manager agent + + + + 6h ago + + AWS Secrets manager announces open source release of secrets manager agent + + + + 6h ago + + AWS Secrets manager announces open source release of secrets manager agent + + + + + + + + + + Announcements + + + + 6h ago + + AWS Secrets manager announces open source release of secrets manager agent + + + + 6h ago + + AWS Secrets manager announces open source release of secrets manager agent + + + + 6h ago + + AWS Secrets manager announces open source release of secrets manager agent + + + + + + + + + Help others + + + + 6h ago + + AWS Secrets manager announces open source release of secrets manager agent + + + + 6h ago + + AWS Secrets manager announces open source release of secrets manager agent + + + + 6h ago + + AWS Secrets manager announces open source release of secrets manager agent + + + + + ); +} diff --git a/src/pages/commons/data-provider.jsx b/src/pages/commons/data-provider.jsx new file mode 100644 index 00000000..a8cb5316 --- /dev/null +++ b/src/pages/commons/data-provider.jsx @@ -0,0 +1,14 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 +export default class DataProvider { + getData(name) { + return fetch(`./resources/${name}.json`) + .then(response => { + if (!response.ok) { + throw new Error(`Response error: ${response.status}`); + } + return response.json(); + }) + .then(data => data.map(it => ({ ...it, date: new Date(it.date) }))); + } +} diff --git a/src/pages/commons/data-provider.ts b/src/pages/commons/data-provider.ts deleted file mode 100644 index 784341dd..00000000 --- a/src/pages/commons/data-provider.ts +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: MIT-0 -export default class DataProvider { - async getData(name: string) { - const response = await fetch(`./resources/${name}.json`); - if (!response.ok) { - throw new Error(`Response error: ${response.status}`); - } - return (await response.json()) as T[]; - } - async getDataWithDates(name: string) { - const data = await this.getData(name); - return data.map((it: T) => ({ ...it, date: new Date(it.date) })); - } -} diff --git a/src/pages/commons/full-page-header.tsx b/src/pages/commons/full-page-header.tsx index 2341f30a..62f06900 100644 --- a/src/pages/commons/full-page-header.tsx +++ b/src/pages/commons/full-page-header.tsx @@ -2,9 +2,7 @@ // SPDX-License-Identifier: MIT-0 import React from 'react'; -import Button from '@cloudscape-design/components/button'; -import Header, { HeaderProps } from '@cloudscape-design/components/header'; -import SpaceBetween from '@cloudscape-design/components/space-between'; +import { Button, Header, HeaderProps, SpaceBetween } from '@cloudscape-design/components'; import { InfoLink } from './info-link'; @@ -12,7 +10,7 @@ interface FullPageHeaderProps extends HeaderProps { title?: string; createButtonText?: string; extraActions?: React.ReactNode; - selectedItemsCount?: number; + selectedItemsCount: number; onInfoLinkClick?: () => void; } diff --git a/src/pages/commons/help-panel.ts b/src/pages/commons/help-panel.ts deleted file mode 100644 index 946949ae..00000000 --- a/src/pages/commons/help-panel.ts +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: MIT-0 -import React, { createContext, useContext } from 'react'; - -const HelpPanelContext = createContext<((newContent: React.ReactNode) => void) | null>(null); - -export const HelpPanelProvider = HelpPanelContext.Provider; - -export function useHelpPanel() { - const ctx = useContext(HelpPanelContext); - if (!ctx) { - throw new Error('Missing HelpPanelProvider'); - } - return ctx; -} diff --git a/src/pages/commons/navigation.tsx b/src/pages/commons/navigation.tsx index c85ae947..52437f76 100644 --- a/src/pages/commons/navigation.tsx +++ b/src/pages/commons/navigation.tsx @@ -2,9 +2,9 @@ // SPDX-License-Identifier: MIT-0 import React from 'react'; +import { Button, Input } from '@cloudscape-design/components'; import SideNavigation, { SideNavigationProps } from '@cloudscape-design/components/side-navigation'; -const navHeader = { text: 'Service', href: '#/' }; export const navItems: SideNavigationProps['items'] = [ { type: 'section', @@ -43,9 +43,88 @@ interface NavigationProps { export function Navigation({ activeHref, - header = navHeader, items = navItems, onFollowHandler = defaultOnFollowHandler, }: NavigationProps) { - return ; + return ; +} + +export function FUllCustomTopNav() { + const [value, setValue] = React.useState(''); + return ( +
    +
    + +
    + setValue(detail.value)} + placeholder="What are you looking for?" + type="search" + /> +
    + +
    + +
    +
    +
    + ); } diff --git a/src/pages/commons/table-config.tsx b/src/pages/commons/table-config.jsx similarity index 81% rename from src/pages/commons/table-config.tsx rename to src/pages/commons/table-config.jsx index 0ace99a0..540ed513 100644 --- a/src/pages/commons/table-config.tsx +++ b/src/pages/commons/table-config.jsx @@ -2,21 +2,19 @@ // SPDX-License-Identifier: MIT-0 import React from 'react'; -import Autosuggest from '@cloudscape-design/components/autosuggest'; -import ButtonDropdown from '@cloudscape-design/components/button-dropdown'; -import CollectionPreferences, { - CollectionPreferencesProps, -} from '@cloudscape-design/components/collection-preferences'; -import Input from '@cloudscape-design/components/input'; -import Link from '@cloudscape-design/components/link'; -import Select from '@cloudscape-design/components/select'; -import StatusIndicator from '@cloudscape-design/components/status-indicator'; -import { TableProps } from '@cloudscape-design/components/table'; +import { + Autosuggest, + ButtonDropdown, + CollectionPreferences, + Input, + Link, + Select, + StatusIndicator, +} from '@cloudscape-design/components'; -import { Distribution } from '../../fake-server/types'; import { createTableSortLabelFn } from '../../i18n-strings'; -const rawColumns: TableProps.ColumnDefinition[] = [ +const rawColumns = [ { id: 'id', sortingField: 'id', @@ -99,10 +97,7 @@ const rawColumns: TableProps.ColumnDefinition[] = [ }, ]; -export const COLUMN_DEFINITIONS = rawColumns.map(column => ({ - ...column, - ariaLabel: createTableSortLabelFn(column), -})); +export const COLUMN_DEFINITIONS = rawColumns.map(column => ({ ...column, ariaLabel: createTableSortLabelFn(column) })); export const serverSideErrorsStore = new Map(); @@ -113,7 +108,7 @@ export const serverSideErrorsStore = new Map(); export const domainNameRegex = /^(?:[\w_-]+\.){1,3}(?:com|net|org)$/i; export const INVALID_DOMAIN_MESSAGE = 'Valid domain name ends with .com, .org, or .net.'; -const editableColumns: Record>> = { +const editableColumns = { state: { minWidth: 200, editConfig: { @@ -134,7 +129,7 @@ const editableColumns: Record { setValue(event.detail.selectedOption.value); }} - selectedOption={options.find(option => option.value === (currentValue ?? item.state)) ?? null} + selectedOption={options.find(option => option.value === (currentValue ?? item.state))} /> ); }, @@ -215,17 +210,17 @@ const editableColumns: Record { - if (editableColumns[column.id!]) { + if (editableColumns[column.id]) { return { ...column, - minWidth: Math.max(Number(column.minWidth) || 0, 176), - ...editableColumns[column.id!], + minWidth: Math.max(column.minWidth || 0, 176), + ...editableColumns[column.id], }; } return column; }); -const CONTENT_DISPLAY_OPTIONS: CollectionPreferencesProps.ContentDisplayOption[] = [ +const CONTENT_DISPLAY_OPTIONS = [ { id: 'id', label: 'Distribution ID', alwaysVisible: true }, { id: 'state', label: 'State' }, { id: 'domainName', label: 'Domain name' }, @@ -237,13 +232,13 @@ const CONTENT_DISPLAY_OPTIONS: CollectionPreferencesProps.ContentDisplayOption[] { id: 'actions', label: 'Actions' }, ]; -export const PAGE_SIZE_OPTIONS: CollectionPreferencesProps.PageSizePreference['options'] = [ +export const PAGE_SIZE_OPTIONS = [ { value: 10, label: '10 Distributions' }, { value: 30, label: '30 Distributions' }, { value: 50, label: '50 Distributions' }, ]; -export const DEFAULT_PREFERENCES: CollectionPreferencesProps.Preferences = { +export const DEFAULT_PREFERENCES = { pageSize: 30, contentDisplay: [ { id: 'id', visible: true }, @@ -262,20 +257,13 @@ export const DEFAULT_PREFERENCES: CollectionPreferencesProps.Preferences = { stickyColumns: { first: 0, last: 1 }, }; -export interface PreferencesProps { - preferences: CollectionPreferencesProps['preferences']; - setPreferences: (preferences: CollectionPreferencesProps['preferences']) => void; - disabled?: boolean; - pageSizeOptions?: CollectionPreferencesProps.PageSizePreference['options']; - contentDisplayOptions?: CollectionPreferencesProps.ContentDisplayOption[]; -} export const Preferences = ({ preferences, setPreferences, disabled, pageSizeOptions = PAGE_SIZE_OPTIONS, contentDisplayOptions = CONTENT_DISPLAY_OPTIONS, -}: PreferencesProps) => ( +}) => ( { + let rendered = true; + loadCallback().then(items => { + if (rendered) { + setItems(items); + setLoading(false); + } + }); + return () => { + rendered = false; + }; + }, []); + + return [items, loading]; +} diff --git a/src/pages/commons/use-async-data.ts b/src/pages/commons/use-async-data.ts deleted file mode 100644 index fd848d87..00000000 --- a/src/pages/commons/use-async-data.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: MIT-0 -import { useEffect, useState } from 'react'; - -export function useAsyncData(loadCallback: () => Promise) { - const [items, setItems] = useState([]); - const [loading, setLoading] = useState(true); - useEffect(() => { - let rendered = true; - loadCallback().then(items => { - if (rendered) { - setItems(items as T[]); - setLoading(false); - } - }); - return () => { - rendered = false; - }; - /** - * Note: The `loadCallback` is not required to be memoized with `useCallback`. - * Adding it as a dependency may cause unintended behavior, such as re-triggering - * the data load on every render. - */ - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return [items, loading] as const; -} diff --git a/src/pages/commons/use-column-widths.js b/src/pages/commons/use-column-widths.js new file mode 100644 index 00000000..b604cacd --- /dev/null +++ b/src/pages/commons/use-column-widths.js @@ -0,0 +1,19 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 +import { useMemo } from 'react'; + +import { addToColumnDefinitions, mapWithColumnDefinitionIds } from '../../common/columnDefinitionsHelper'; +import { useLocalStorage } from './use-local-storage'; + +export function useColumnWidths(storageKey, columnDefinitions) { + const [widths, saveWidths] = useLocalStorage(storageKey); + + function handleWidthChange(event) { + saveWidths(mapWithColumnDefinitionIds(columnDefinitions, 'width', event.detail.widths)); + } + const memoDefinitions = useMemo(() => { + return addToColumnDefinitions(columnDefinitions, 'width', widths); + }, [widths, columnDefinitions]); + + return [memoDefinitions, handleWidthChange]; +} diff --git a/src/pages/commons/use-column-widths.ts b/src/pages/commons/use-column-widths.ts deleted file mode 100644 index 8d4ea743..00000000 --- a/src/pages/commons/use-column-widths.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: MIT-0 -import { useMemo } from 'react'; - -import { TableProps } from '@cloudscape-design/components/table'; - -import { addToColumnDefinitions, mapWithColumnDefinitionIds } from '../../common/columnDefinitionsHelper'; -import { useLocalStorage } from './use-local-storage'; - -export function useColumnWidths(storageKey: string, columnDefinitions: TableProps.ColumnDefinition[]) { - const [widths, saveWidths] = - useLocalStorage, 'width' | 'id'>[]>(storageKey); - const handleWidthChange: TableProps['onColumnWidthsChange'] = event => { - saveWidths(mapWithColumnDefinitionIds(columnDefinitions, 'width', event.detail.widths as number[])); - }; - const memoDefinitions = useMemo(() => { - return addToColumnDefinitions(columnDefinitions, 'width', widths); - }, [widths, columnDefinitions]); - - return [memoDefinitions, handleWidthChange] as const; -} diff --git a/src/pages/commons/use-column-widths.tsx b/src/pages/commons/use-column-widths.tsx deleted file mode 100644 index 32133ea5..00000000 --- a/src/pages/commons/use-column-widths.tsx +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: MIT-0 -import { useMemo } from 'react'; - -import { NonCancelableCustomEvent, TableProps } from '@cloudscape-design/components'; - -import { addToColumnDefinitions, mapWithColumnDefinitionIds } from '../../common/columnDefinitionsHelper'; -import { useLocalStorage } from './use-local-storage'; - -export function useColumnWidths(storageKey: string, columnDefinitions: TableProps.ColumnDefinition[]) { - const [widths, saveWidths] = - useLocalStorage, 'width' | 'id'>[]>(storageKey); - function handleWidthChange(event: NonCancelableCustomEvent) { - saveWidths(mapWithColumnDefinitionIds(columnDefinitions, 'width', event.detail.widths as number[])); - } - const memoDefinitions = useMemo(() => { - return addToColumnDefinitions(columnDefinitions, 'width', widths); - }, [widths, columnDefinitions]); - - return [memoDefinitions, handleWidthChange] as const; -} diff --git a/src/pages/commons/use-content-origins.ts b/src/pages/commons/use-content-origins.js similarity index 56% rename from src/pages/commons/use-content-origins.ts rename to src/pages/commons/use-content-origins.js index 023c6a98..112b5720 100644 --- a/src/pages/commons/use-content-origins.ts +++ b/src/pages/commons/use-content-origins.js @@ -2,42 +2,16 @@ // SPDX-License-Identifier: MIT-0 import { useMemo, useRef, useState } from 'react'; -import { MultiselectProps } from '@cloudscape-design/components/multiselect'; +export default function useContentOrigins() { + const requestParams = useRef({}); + const [options, setOptions] = useState([]); + const [status, setStatus] = useState('finished'); -import { ContentOriginsResource } from '../../resources/types'; - -interface RequestParams { - filteringText?: string; - currentPageIndex: number; -} - -interface LoadItemsDetail { - filteringText: string; - firstPage: boolean; - samePage: boolean; -} - -interface Handlers { - onLoadItems: (args: { detail: LoadItemsDetail }) => void; -} - -export default function useContentOrigins(): [ - { - options: ContentOriginsResource[]; - filteringText: string | undefined; - status: MultiselectProps['statusType']; - }, - Handlers -] { - const requestParams = useRef({ currentPageIndex: 1 }); - const [options, setOptions] = useState([]); - const [status, setStatus] = useState('finished'); - - async function doRequest({ filteringText, currentPageIndex }: RequestParams): Promise { + async function doRequest({ filteringText, currentPageIndex }) { setStatus('loading'); try { const { origins, hasNextPage } = await window.FakeServer.fetchContentOrigins({ - filteringText: filteringText!, + filteringText, currentPageIndex, }); if (filteringText !== requestParams.current.filteringText) { @@ -56,7 +30,7 @@ export default function useContentOrigins(): [ const handlers = useMemo(() => { return { - onLoadItems({ detail: { filteringText, firstPage, samePage } }: { detail: LoadItemsDetail }) { + onLoadItems({ detail: { filteringText, firstPage, samePage } }) { if (firstPage) { requestParams.current = { filteringText, @@ -72,7 +46,6 @@ export default function useContentOrigins(): [ doRequest(requestParams.current); }, }; - }, []); - + }, [requestParams]); return [{ options, filteringText: requestParams.current.filteringText, status }, handlers]; } diff --git a/src/pages/commons/use-content-origins.tsx b/src/pages/commons/use-content-origins.tsx deleted file mode 100644 index f8f2df19..00000000 --- a/src/pages/commons/use-content-origins.tsx +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: MIT-0 -import { useMemo, useRef, useState } from 'react'; - -import { DropdownStatusProps } from '@cloudscape-design/components/internal/components/dropdown-status'; - -import { ContentOriginsResource } from '../../resources/types'; - -interface RequestParams { - filteringText?: string; - currentPageIndex: number; -} - -interface LoadItemsDetail { - filteringText: string; - firstPage: boolean; - samePage: boolean; -} - -interface Handlers { - onLoadItems: (args: { detail: LoadItemsDetail }) => void; -} - -export default function useContentOrigins(): [ - { - options: ContentOriginsResource[]; - filteringText: string | undefined; - status: DropdownStatusProps['statusType']; - }, - Handlers -] { - const requestParams = useRef({ currentPageIndex: 1 }); - const [options, setOptions] = useState([]); - const [status, setStatus] = useState('finished'); - - async function doRequest({ filteringText, currentPageIndex }: RequestParams): Promise { - setStatus('loading'); - try { - const { origins, hasNextPage } = await window.FakeServer.fetchContentOrigins({ - filteringText: filteringText!, - currentPageIndex, - }); - if (filteringText !== requestParams.current.filteringText) { - return; - } - if (currentPageIndex === 1) { - setOptions(origins); - } else { - setOptions(oldOptions => [...oldOptions, ...origins]); - } - setStatus(hasNextPage ? 'pending' : 'finished'); - } catch (error) { - setStatus('error'); - } - } - - const handlers = useMemo(() => { - return { - onLoadItems({ detail: { filteringText, firstPage, samePage } }: { detail: LoadItemsDetail }) { - if (firstPage) { - requestParams.current = { - filteringText, - currentPageIndex: 1, - }; - setOptions([]); - } else if (!samePage) { - requestParams.current = { - ...requestParams.current, - currentPageIndex: requestParams.current.currentPageIndex + 1, - }; - } - doRequest(requestParams.current); - }, - }; - }, []); - - return [{ options, filteringText: requestParams.current.filteringText, status }, handlers]; -} diff --git a/src/pages/commons/use-local-storage.ts b/src/pages/commons/use-local-storage.ts index 884a04fa..f47f819e 100644 --- a/src/pages/commons/use-local-storage.ts +++ b/src/pages/commons/use-local-storage.ts @@ -5,15 +5,10 @@ import { useCallback, useState } from 'react'; import { load, remove, save } from '../../common/localStorage'; export function useLocalStorage(key: string, defaultValue?: T) { - const [value, setValue] = useState(() => load(key) ?? defaultValue); + const [value, setValue] = useState(() => load(key) ?? defaultValue); const handleValueChange = useCallback( - (newValue: T | undefined) => { - if (newValue === undefined) { - remove(key); - return; - } - + (newValue: T) => { setValue(newValue); save(key, newValue); }, diff --git a/src/pages/dashboard/components/side-navigation.tsx b/src/pages/dashboard/components/side-navigation.tsx index 35a55da5..06dc2fd4 100644 --- a/src/pages/dashboard/components/side-navigation.tsx +++ b/src/pages/dashboard/components/side-navigation.tsx @@ -2,194 +2,85 @@ // 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 - - - - ), + text: 'Home', + href: '#/', }, - { type: 'link', text: 'Tags', href: '#/tags' }, - { type: 'link', text: 'Reports', href: '#/reports' }, - { type: 'link', text: 'Limits', href: '#/limits' }, + { type: 'divider' }, { - text: 'Instances', - type: 'section', - defaultExpanded: true, + title: 'Knowledge', + type: 'section-group', + 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 - - - - ), + text: 'Learning center', + href: '#/instances', }, - { 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 - - - - ), + text: 'Content library', + href: '#/instances', + }, + { + type: 'link', + text: 'Events', + href: '#/instances', }, - { 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' }, ], }, + { type: 'divider' }, { - text: ' Network & security', - type: 'section', - defaultExpanded: false, + title: 'Socialize', + type: 'section-group', + 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' }, + { type: 'link', text: 'Connections', href: '#/instances' }, + { type: 'link', text: 'Groups', href: '#/instances' }, + { type: 'link', text: 'Forums', href: '#/instances' }, + { type: 'link', text: 'Community programs', href: '#/instances' }, ], }, + { type: 'divider' }, { - text: 'Load balancing', - type: 'section', - defaultExpanded: false, + title: 'Elastic block store', + type: 'section-group', + items: [ - { type: 'link', text: 'Load balancers', href: '#/load_balancers' }, - { type: 'link', text: 'Target groups', href: '#/target_groups' }, + { + type: 'link', + text: 'Wishlist', + href: '#/launch_templates', + }, + { type: 'link', text: 'Code sharing', href: '#/snapshots' }, ], }, + { type: 'divider' }, + { type: 'link', text: 'Notifications', href: '#/security_groups' }, + { type: 'link', text: 'Chat', href: '#/security_groups' }, + { type: 'link', text: 'Resources', href: '#/security_groups' }, + { type: 'divider' }, { - text: 'Auto scaling', - type: 'section', - defaultExpanded: false, + title: 'Popular communities', + type: 'section-group', + items: [ - { type: 'link', text: 'Launch configurations', href: '#/launch_configurations' }, - { type: 'link', text: 'Auto scaling groups', href: '#/auto_scaling_groups' }, + { type: 'link', text: 'Cloud computing', href: '#/load_balancers' }, + { type: 'link', text: 'AI', href: '#/target_groups' }, + { type: 'link', text: 'Serverless', href: '#/target_groups' }, + { type: 'link', text: 'BigData', href: '#/target_groups' }, + { type: 'link', text: 'QuantumComputing', href: '#/target_groups' }, ], }, { type: 'divider' }, - { - type: 'link', - href: '#/density_settings', - text: 'Density settings', - }, + { type: 'link', text: 'Discover communities', href: '#/target_groups' }, ]; export function DashboardSideNavigation() { diff --git a/src/pages/dashboard/index.tsx b/src/pages/dashboard/index.tsx index 03ef22a5..47b6a98e 100644 --- a/src/pages/dashboard/index.tsx +++ b/src/pages/dashboard/index.tsx @@ -3,8 +3,12 @@ import React from 'react'; import { createRoot } from 'react-dom/client'; +import { applyTheme } from '@cloudscape-design/components/theming'; + +import customTheme from '../../common/theme-definition'; import { App } from './app'; import '../../styles/base.scss'; +applyTheme({ theme: customTheme }); createRoot(document.getElementById('app')!).render(); diff --git a/src/pages/delete-with-additional-confirmation/app.tsx b/src/pages/delete-with-additional-confirmation/app.tsx index b9014714..b4444665 100644 --- a/src/pages/delete-with-additional-confirmation/app.tsx +++ b/src/pages/delete-with-additional-confirmation/app.tsx @@ -2,6 +2,9 @@ // SPDX-License-Identifier: MIT-0 import React, { useEffect, useState } from 'react'; +import { applyTheme } from '@cloudscape-design/components/theming'; + +import customTheme from '../../common/theme-definition'; import INSTANCES from '../../resources/ec2-instances'; import { EC2Instance } from '../../resources/types'; import fakeDelay from '../commons/fake-delay'; @@ -10,6 +13,7 @@ import useNotifications from '../delete-with-simple-confirmation/use-notificatio import { DeleteModal } from './components/delete-modal'; import { InstanceDetailsPage } from './components/instance-details-page'; import { InstancesPage } from './components/instances-page'; +applyTheme({ theme: customTheme }); const delay = 3000; const failingInstances = [INSTANCES[0]]; diff --git a/src/pages/details-hub/index.tsx b/src/pages/details-hub/index.tsx index 03ef22a5..47b6a98e 100644 --- a/src/pages/details-hub/index.tsx +++ b/src/pages/details-hub/index.tsx @@ -3,8 +3,12 @@ import React from 'react'; import { createRoot } from 'react-dom/client'; +import { applyTheme } from '@cloudscape-design/components/theming'; + +import customTheme from '../../common/theme-definition'; import { App } from './app'; import '../../styles/base.scss'; +applyTheme({ theme: customTheme }); createRoot(document.getElementById('app')!).render(); diff --git a/src/pages/details/common-components.jsx b/src/pages/details/common-components.jsx new file mode 100644 index 00000000..3a9a0864 --- /dev/null +++ b/src/pages/details/common-components.jsx @@ -0,0 +1,449 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 +import React, { useState } from 'react'; + +import { CodeView } from '@cloudscape-design/code-view'; +import jsonHighlight from '@cloudscape-design/code-view/highlight/json'; +import xmlHighlight from '@cloudscape-design/code-view/highlight/xml'; +import yamlHighlight from '@cloudscape-design/code-view/highlight/yaml'; +import { useCollection } from '@cloudscape-design/collection-hooks'; +import { + Box, + BreadcrumbGroup, + Button, + ButtonDropdown, + Container, + CopyToClipboard, + Header, + KeyValuePairs, + Link, + ProgressBar, + SpaceBetween, + StatusIndicator, + Table, + Tabs, + TextFilter, +} from '@cloudscape-design/components'; + +import { resourceDetailBreadcrumbs } from '../../common/breadcrumbs'; +import { baseTableAriaLabels, getHeaderCounterText, getTextFilterCounterText } from '../../i18n-strings'; +import articleImage from '../commons/article-image.png'; +import authorAvatar from '../commons/avatar-b.png'; +import { InfoLink, TableEmptyState } from '../commons/common-components'; +import DataProvider from '../commons/data-provider'; +import { useAsyncData } from '../commons/use-async-data'; +import { codeSnippets } from './details-code-snippets'; +import { BEHAVIORS_COLUMN_DEFINITIONS, ORIGINS_COLUMN_DEFINITIONS, TAGS_COLUMN_DEFINITIONS } from './details-config'; + +export const DEMO_DISTRIBUTION = { + id: 'SLCCSMWOHOFUY0', + domainName: 'abcdef01234567890.cloudfront.net', + arn: 'arn:aws:cloudfront::abcdef01234567890.cloudfront.net/SLCCSMWOHOFUY0', + priceClass: 'Use only US, Canada, Europe, and Asia', + sslCertificate: 'Default CloudFront SSL certificate', + logging: 'Off', +}; + +export const Breadcrumbs = () => ( + +); + +export const PageHeader = ({ buttons }) => { + return ( +
    + {buttons.map((button, key) => + !button.items ? ( + + ) : ( + + {button.text} + + ) + )} + + } + > + {DEMO_DISTRIBUTION.id} +
    + ); +}; + +export const GeneralConfig = () => ( + General configuration}> + Available, + }, + { + label: 'Pending maintenance', + value: 'None', + }, + ]} + /> + +); + +const Article = () => ( + + +
    + +
    + +
    +
    + Rob Andrews + + Software engineer at LinkedIn + + + 5h ago + +
    +
    + +
    + + + Tips on building a chef robot + + + AWSome Quiz is an interactive, web-based game designed to test and enhance users' understanding of Amazon Web + Services (AWS). It features a modern, space-themed interface with an animated starfield background that + delivers an immersive user experience. + + + +
    +
    +); + +export const ForYou = () => ( + + +
    +
    +
    +
    + + + + + +); + +export const SettingsDetails = ({ distribution = DEMO_DISTRIBUTION, isInProgress }) => ( + + ), + }, + ], + }, + { + type: 'group', + items: [ + { + label: distribution.state ? '' : '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', + }, + ], + }, + ]} + /> +); + +export const EmptyTable = props => { + const resourceType = props.title || 'Tag'; + const colDefs = props.columnDefinitions || TAGS_COLUMN_DEFINITIONS; + return ( +
    } + columnDefinitions={colDefs} + items={[]} + header={ +
    + + + + + } + >{`${resourceType}s`}
    + } + /> + ); +}; + +export const DistributionDetails = () => { + return ( + Distribution configuration details}> + , + }, + { + label: 'YAML', + id: 'yaml', + content: , + }, + { + label: 'XML', + id: 'xml', + content: , + }, + ]} + /> + + ); +}; + +const originsSelectionLabels = { + ...baseTableAriaLabels, + itemSelectionLabel: (data, row) => `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={ +
    + + + + + } + > + Origins +
    + } + /> + ); +} + +const behaviorsSelectionLabels = { + ...baseTableAriaLabels, + itemSelectionLabel: (data, row) => `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={ +
    + + + + + } + > + Cache behavior settings +
    + } + /> + ); +} + +export function TagsTable({ loadHelpPanelContent }) { + const [tags, tagsLoading] = useAsyncData(async () => { + const { ResourceTagMappingList } = await window.FakeServer.GetResources(); + return ResourceTagMappingList.reduce((tags, resourceTagMapping) => [...tags, ...resourceTagMapping.Tags], []); + }); + + const { items, collectionProps, filteredItemsCount, filterProps, actions } = useCollection(tags, { + filtering: { + noMatch: ( + + + No matches + + + No tags matched the search text. + + + + ), + }, + sorting: {}, + }); + + return ( +
    + } + header={ +
    loadHelpPanelContent(2)} />} + actions={} + 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/src/pages/form/app.tsx b/src/pages/form/app.tsx index 868f95d7..01202c2f 100644 --- a/src/pages/form/app.tsx +++ b/src/pages/form/app.tsx @@ -4,11 +4,14 @@ import React, { useRef, useState } from 'react'; import { AppLayoutProps } from '@cloudscape-design/components/app-layout'; import BreadcrumbGroup from '@cloudscape-design/components/breadcrumb-group'; +import { applyTheme } from '@cloudscape-design/components/theming'; import { resourceCreateBreadcrumbs } from '../../common/breadcrumbs'; +import customTheme from '../../common/theme-definition'; import { CustomAppLayout, Navigation, Notifications } from '../commons/common-components'; import { FormFull, FormHeader } from './components/form'; import ToolsContent from './components/tools-content'; +applyTheme({ theme: customTheme }); const Breadcrumbs = () => ( diff --git a/src/pages/table-select-filter/index.tsx b/src/pages/table-select-filter/index.tsx deleted file mode 100644 index f7e11d3e..00000000 --- a/src/pages/table-select-filter/index.tsx +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: MIT-0 -import React from 'react'; -import { createRoot } from 'react-dom/client'; - -import { App } from './root'; - -createRoot(document.getElementById('app')!).render(); diff --git a/src/pages/table-select-filter/root.tsx b/src/pages/table-select-filter/root.tsx deleted file mode 100644 index 8d38c1b6..00000000 --- a/src/pages/table-select-filter/root.tsx +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: MIT-0 -import React, { useLayoutEffect, useRef, useState } from 'react'; - -import { useCollection } from '@cloudscape-design/collection-hooks'; -import { AppLayoutProps } from '@cloudscape-design/components/app-layout'; -import Button from '@cloudscape-design/components/button'; -import ButtonDropdown from '@cloudscape-design/components/button-dropdown'; -import Input from '@cloudscape-design/components/input'; -import LiveRegion from '@cloudscape-design/components/live-region'; -import Pagination from '@cloudscape-design/components/pagination'; -import Select from '@cloudscape-design/components/select'; -import { SelectProps } from '@cloudscape-design/components/select'; -import SpaceBetween from '@cloudscape-design/components/space-between'; -import Table from '@cloudscape-design/components/table'; - -import { getHeaderCounterText, getTextFilterCounterText, renderAriaLive } from '../../i18n-strings'; -import DATA, { Instance } from '../../resources/instances'; -import { FullPageHeader } from '../commons'; -import { CustomAppLayout, Notifications, TableEmptyState, TableNoMatchState } from '../commons/common-components'; -import { Preferences } from '../commons/table-config'; -import { useColumnWidths } from '../commons/use-column-widths'; -import { useLocalStorage } from '../commons/use-local-storage'; -import { Breadcrumbs, Navigation, ToolsContent } from './table-select-filter-components'; -import { - COLUMN_DEFINITIONS, - CONTENT_DISPLAY_OPTIONS, - PREFERENCES, - SEARCHABLE_COLUMNS, -} from './table-select-filter-config'; - -import '../../styles/table-select.scss'; - -function prepareSelectOptions(field: keyof Instance, defaultOption: SelectProps.Option): SelectProps.Options { - const optionSet: string[] = []; - // Building a non redundant list of the field passed as parameter. - - DATA.forEach(item => { - const itemValue = String(item[field]); - if (optionSet.indexOf(itemValue) === -1) { - optionSet.push(itemValue); - } - }); - optionSet.sort(); - - // The first element is the default one. - const options: SelectProps.Option[] = [defaultOption]; - - // Adding the other element of the list. - optionSet.forEach((item, index) => options.push({ label: item, value: (index + 1).toString() })); - return options; -} - -const defaultEngine = { value: '0', label: 'Any Engine' }; -const defaultClass = { value: '0', label: 'Any Class' }; -const selectEngineOptions = prepareSelectOptions('engine', defaultEngine); -const selectClassOptions = prepareSelectOptions('class', defaultClass); - -function matchesEngine(item: Instance, selectedEngine: SelectProps.Option) { - return selectedEngine === defaultEngine || item.engine === selectedEngine.label; -} - -function matchesClass(item: Instance, selectedClass: SelectProps.Option) { - return selectedClass === defaultClass || item.class === selectedClass.label; -} - -interface TableSelectFilter { - loadHelpPanelContent: () => void; -} - -function TableSelectFilter({ loadHelpPanelContent }: TableSelectFilter) { - const [columnDefinitions, saveWidths] = useColumnWidths('React-TableSelectFilter-Widths', COLUMN_DEFINITIONS); - const [engine, setEngine] = useState(defaultEngine); - const [instanceClass, setInstanceClass] = useState(defaultClass); - const [preferences, setPreferences] = useLocalStorage('React-TableSelectFilter-Preferences', PREFERENCES); - const { items, actions, filteredItemsCount, collectionProps, filterProps, paginationProps } = useCollection(DATA, { - filtering: { - empty: , - - // ClearFilter also depends on setFiltering returned from this hook - // eslint-disable-next-line @typescript-eslint/no-use-before-define - noMatch: , - filteringFunction: (item, filteringText) => { - if (!matchesEngine(item, engine)) { - return false; - } - if (!matchesClass(item, instanceClass)) { - return false; - } - const filteringTextLowerCase = filteringText.toLowerCase(); - - return SEARCHABLE_COLUMNS.map(key => item[key]).some( - value => typeof value === 'string' && value.toLowerCase().indexOf(filteringTextLowerCase) > -1 - ); - }, - }, - pagination: { pageSize: preferences?.pageSize }, - sorting: { defaultState: { sortingColumn: columnDefinitions[0] } }, - selection: {}, - }); - useLayoutEffect(() => { - collectionProps.ref.current?.scrollToTop(); - }, [instanceClass, engine, collectionProps.ref, filterProps.filteringText]); - - function clearFilter() { - actions.setFiltering(''); - setEngine(defaultEngine); - setInstanceClass(defaultClass); - } - - return ( -
    `Select DB instance ${row.id}`, - allItemsSelectionLabel: () => 'Select all DB instances', - selectionGroupLabel: 'Instances selection', - }} - renderAriaLive={renderAriaLive} - header={ - - - Instance actions - - - - - } - onInfoLinkClick={loadHelpPanelContent} - /> - } - filter={ -
    -
    - { - actions.setFiltering(event.detail.value); - }} - ariaLabel="Find instances" - placeholder="Find instances" - clearAriaLabel="clear" - /> -
    -
    - { - setInstanceClass(event.detail.selectedOption); - }} - expandToViewport={true} - /> -
    - - {(filterProps.filteringText || engine !== defaultEngine || instanceClass !== defaultClass) && ( - {getTextFilterCounterText(filteredItemsCount ?? 0)} - )} - -
    - } - pagination={} - preferences={ - - } - /> - ); -} - -export function App() { - const [toolsOpen, setToolsOpen] = useState(false); - const appLayout = useRef(null); - - return ( - } - notifications={} - breadcrumbs={} - content={ - { - setToolsOpen(true); - appLayout.current?.focusToolsClose(); - }} - /> - } - contentType="table" - tools={} - toolsOpen={toolsOpen} - onToolsChange={({ detail }) => setToolsOpen(detail.open)} - /> - ); -} diff --git a/src/pages/table-select-filter/table-select-filter-components.tsx b/src/pages/table-select-filter/table-select-filter-components.tsx deleted file mode 100644 index d38478d2..00000000 --- a/src/pages/table-select-filter/table-select-filter-components.tsx +++ /dev/null @@ -1,98 +0,0 @@ -// 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 HelpPanel from '@cloudscape-design/components/help-panel'; -import SideNavigation from '@cloudscape-design/components/side-navigation'; -import { SideNavigationProps } from '@cloudscape-design/components/side-navigation'; -import StatusIndicator from '@cloudscape-design/components/status-indicator'; - -import { Instance } from '../../resources/instances'; -import { ExternalLinkGroup } from '../commons'; - -import '../../styles/table-select.scss'; - -export interface StatusComponentProps { - status: Instance['status']; -} - -export const StatusComponent = ({ status }: StatusComponentProps) => { - if (status === 'available') { - return Available; - } else { - return Unavailable; - } -}; - -const header = { text: 'Service', href: '#/' }; - -const items: SideNavigationProps.Item[] = [ - { type: 'link', text: 'Dashboard', href: '#/dashboard' }, - { type: 'link', text: 'Instances', href: '#/instances' }, - { type: 'link', text: 'Clusters', href: '#/clusters' }, - { type: 'link', text: 'Performance Insights', href: '#/perfomance' }, - { type: 'link', text: 'Snapshots', href: '#/usage' }, - { type: 'link', text: 'Reserved instances', href: '#/reservedinstances' }, - { type: 'divider' }, - { type: 'link', text: 'Subnet groups', href: '#/subnetgroups' }, - { type: 'link', text: 'Parameter groups', href: '#/paramgroups' }, - { type: 'link', text: 'Option groups', href: '#/optiongroups' }, - { type: 'link', text: 'Events', href: '#/event' }, - { type: 'link', text: 'Event subscriptions', href: '#/eventsub' }, -]; - -export interface NavigationProps { - activeHref: string; -} - -export const Navigation = ({ activeHref }: NavigationProps) => { - const onFollowHandler: SideNavigationProps['onFollow'] = event => { - // keep the locked href for our demo pages - event.preventDefault(); - }; - - return ; -}; - -export const Breadcrumbs = () => ( - -); - -export const ToolsContent = () => ( - Instances} - footer={ - - } - > -

    - View your current DB instances and related information such as the engine, status, connections, class, and more. - You can filter your instances by engine or class. To drill down even further into the details, choose the name of - an individual instance. -

    -

    - The status of a DB instance indicates the health of the DB instance. When you first create a DB instance, it has a - status of Creating until the instance is ready to use. When the state changes to Available, you can - connect to the instance. -

    -
    -); diff --git a/src/pages/table-select-filter/table-select-filter-config.tsx b/src/pages/table-select-filter/table-select-filter-config.tsx deleted file mode 100644 index e122b5d6..00000000 --- a/src/pages/table-select-filter/table-select-filter-config.tsx +++ /dev/null @@ -1,124 +0,0 @@ -// 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 { CollectionPreferencesProps } from '@cloudscape-design/components/collection-preferences'; -import Link from '@cloudscape-design/components/link'; -import { TableProps } from '@cloudscape-design/components/table'; - -import { formatReadOnlyRegion } from '../../common/aws-region-utils'; -import { createTableSortLabelFn } from '../../i18n-strings'; -import { Instance } from '../../resources/instances'; -import { StatusComponent } from './table-select-filter-components'; - -const rawColumns: TableProps.ColumnDefinition[] = [ - { - id: 'id', - header: 'DB instance', - cell: item => {item.id}, - sortingComparator: (item1, item2) => Number(item1.id.substring(13)) - Number(item2.id.substring(13)), - }, - { - id: 'status', - header: 'Status', - cell: item => , - sortingField: 'status', - }, - { - id: 'engine', - header: 'Engine', - cell: item => item.engine, - sortingField: 'engine', - }, - { - id: 'version', - header: 'Engine version', - cell: item => item.version, - sortingField: 'version', - }, - { - id: 'activity', - header: 'Active connections', - cell: item => {item.activity}, - sortingField: 'activity', - }, - { - id: 'maint', - header: 'Maintenance', - cell: item => item.maint, - sortingField: 'maint', - }, - { - id: 'class', - header: 'Class', - cell: item => item.class, - sortingField: 'class', - }, - { - id: 'zone', - header: 'Zone', - cell: item => formatReadOnlyRegion(item.zone), - sortingField: 'zone', - }, - { - id: 'iops', - header: 'IOPS', - cell: item => {item.iops}, - sortingField: 'iops', - }, -]; - -export const COLUMN_DEFINITIONS = rawColumns.map(column => ({ - ...column, - ariaLabel: createTableSortLabelFn(column), -})); - -export const SEARCHABLE_COLUMNS = [ - 'id', - 'engine', - 'version', - 'status', - 'class', - 'activity', - 'zone', - 'iops', - 'maint', -] as const; - -export const CONTENT_DISPLAY_OPTIONS = [ - { id: 'id', label: 'DB instance', alwaysVisible: true }, - { id: 'status', label: 'Status' }, - { id: 'engine', label: 'Engine' }, - { id: 'version', label: 'Engine version' }, - { id: 'activity', label: 'Active connections' }, - { id: 'maint', label: 'Maintenance' }, - { id: 'class', label: 'Class' }, - { id: 'zone', label: 'Zone' }, - { id: 'iops', label: 'IOPS' }, -]; - -export const PAGE_SIZE_OPTIONS = [ - { value: 10, label: '10 Instances' }, - { value: 30, label: '30 Instances' }, - { value: 50, label: '50 Instances' }, -]; - -export const PREFERENCES: CollectionPreferencesProps.Preferences = { - pageSize: 30, - contentDisplay: [ - { id: 'id', visible: true }, - { id: 'engine', visible: true }, - { id: 'version', visible: true }, - { id: 'status', visible: true }, - { id: 'activity', visible: true }, - { id: 'maint', visible: false }, - { id: 'class', visible: true }, - { id: 'zone', visible: false }, - { id: 'iops', visible: false }, - ], - wrapLines: false, - stripedRows: false, - contentDensity: 'comfortable', - custom: 'table', -}; diff --git a/src/pages/wizard/root.tsx b/src/pages/wizard/root.tsx index a5530f48..cda80bbd 100644 --- a/src/pages/wizard/root.tsx +++ b/src/pages/wizard/root.tsx @@ -4,8 +4,10 @@ import React, { useCallback, useRef, useState } from 'react'; import { AppLayoutProps } from '@cloudscape-design/components/app-layout'; import HelpPanel from '@cloudscape-design/components/help-panel'; +import { applyTheme } from '@cloudscape-design/components/theming'; import Wizard, { WizardProps } from '@cloudscape-design/components/wizard'; +import customTheme from '../../common/theme-definition'; import { ExternalLinkGroup, InfoLink, Notifications } from '../commons'; import { CustomAppLayout } from '../commons/common-components'; import { ToolsContent, WizardState } from './interfaces'; @@ -15,6 +17,7 @@ import Advanced from './stepComponents/step3'; import Review from './stepComponents/step4'; import { DEFAULT_STEP_INFO, TOOLS_CONTENT } from './steps-config'; import { Breadcrumbs, Navigation } from './wizard-components'; +applyTheme({ theme: customTheme }); import '../../styles/wizard.scss'; diff --git a/src/styles/base.scss b/src/styles/base.scss index 0f1f3fa5..c7080bf3 100644 --- a/src/styles/base.scss +++ b/src/styles/base.scss @@ -8,7 +8,6 @@ body { } .custom-main-header { - display: block; position: sticky; top: 0; left: 0; @@ -29,12 +28,12 @@ ul.menu-list { list-style: none; font-size: 14px; - & > li { + &>li { padding: 0; margin: 0; margin-right: 8px; - > a { + >a { padding: 0 6px; } @@ -88,3 +87,76 @@ ul.menu-list { } } } + + +//////// Custom styles //////// +.side-nav { + h3 { + font-size: 20px !important; + } +} + +.custom-style-tabs { + .awsui_tabs-tab-label_14rmt_1ebqu_348:not(#\9) { + font-size: 16px; + font-weight: 500; + } +} + +.awsui-dark-mode { + .service-logo path { + fill: white; + } +} + + +.input-container { + display: flex; + flex-wrap: wrap; + align-items: flex-end; + order: 0; + flex-grow: 10; + margin-inline-end: 0; + margin-block-end: cs.$space-scaled-xxs; + + > *:not(:empty) { + margin-inline-end: cs.$space-scaled-m; + margin-block-start: cs.$space-scaled-m; + } +} + +.input-filter { + order: 0; + flex-grow: 6; + inline-size: auto; + max-inline-size: 400px; + input {min-height: 36px;} +} + +.select-filter { + max-inline-size: 200px; + flex-grow: 2; + inline-size: auto; +} + +.filtering-results { + display: block; + padding-block-end: calc(1px + #{cs.$space-scaled-xxs}); + color: cs.$color-text-form-default; + align-self: center; +} + +@media (max-width: 1152px) { + .input-container { + margin-inline-end: calc(-1 * #{cs.$space-scaled-m}); + } + + .select-filter { + max-inline-size: none; + } + + .input-filter { + inline-size: 100%; + max-inline-size: none; + } +} diff --git a/src/styles/product-page.scss b/src/styles/product-page.scss index 83c87bdd..4eb572c9 100644 --- a/src/styles/product-page.scss +++ b/src/styles/product-page.scss @@ -3,7 +3,8 @@ @use '~@cloudscape-design/design-tokens' as cs; @use './base'; -$viewport-breakpoint-s: 912px; +$viewport-breakpoint-s: 980px; +$viewport-breakpoint-xxs: 690px; body { // Note: This token will be themed (see the product page index.tsx) @@ -14,7 +15,7 @@ body { display: grid; grid-template-columns: 3fr 1fr; grid-template-rows: 0 auto 0; - margin-block-start: cs.$space-static-xxl; + margin-block-start: cs.$space-static-s; } .on-this-page--mobile { @@ -29,6 +30,7 @@ body { grid-column: 2 / 3; padding-inline-start: calc(#{cs.$space-scaled-xxxl} /2); } + .product-page-content { grid-row: 2; grid-column: 1 / 2; @@ -46,20 +48,88 @@ body { inset-block-start: 40px; } +//// Custom style for CustomNavBar component +.nav-grid-wrapper { + display: grid; + grid-template-columns: auto 1fr auto; + align-items: center; + gap: 1rem; + width: 100%; + justify-items: center; + + .header-search-wrapper { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + max-width: 480px; + .header-search-input { + width: 100%; + } + } + + .header-buttons-wrapper { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 12px; + + .search-button { + display: none !important; + } + } +} + + +@media only screen and (max-width: $viewport-breakpoint-xxs) { + div:has(> .nav-grid-wrapper) { + //border: 1px solid red; + margin-block-end: 0 !important; + } + + .header-logo-wrapper { + display: flex; + align-items: center; + + .service-logo { + scale: 0.8; + transform: translateY(2px); + } + } + +} + @media only screen and (max-width: $viewport-breakpoint-s) { .product-page-content-grid { grid-template-columns: 100%; grid-template-rows: auto auto auto; } + .on-this-page--mobile { display: block; } + .product-page-mobile { display: block; } + .product-page-aside { display: none; } + + + //// + .nav-grid-wrapper { + .header-buttons-wrapper { + .search-button { + display: flex !important; + } + } + + .header-search-input { + display: none !important; + } + } } /* Simple separator */ @@ -122,4 +192,4 @@ hr { list-style-type: none; margin: 0; padding: 0; -} +} \ No newline at end of file