diff --git a/dashboard/package.json b/dashboard/package.json index fc5e975..5dd27b5 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -22,6 +22,7 @@ "@mui/x-charts": "^7.7.1", "@radix-ui/react-collapsible": "^1.1.0", "@radix-ui/react-checkbox": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-navigation-menu": "^1.2.0", @@ -44,6 +45,7 @@ "react-intl": "^6.6.8", "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", + "vaul": "^0.9.1", "vite": "^5.3.1" }, "devDependencies": { diff --git a/dashboard/pnpm-lock.yaml b/dashboard/pnpm-lock.yaml index 6e3ba84..d32b0f2 100644 --- a/dashboard/pnpm-lock.yaml +++ b/dashboard/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: '@radix-ui/react-collapsible': specifier: ^1.1.0 version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dialog': + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-dropdown-menu': specifier: ^2.1.1 version: 2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -92,6 +95,9 @@ importers: tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7(tailwindcss@3.4.4) + vaul: + specifier: ^0.9.1 + version: 0.9.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) vite: specifier: ^5.3.1 version: 5.3.3(@types/node@20.14.10)(terser@5.31.2) @@ -1353,6 +1359,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-dialog@1.1.1': + resolution: {integrity: sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-direction@1.1.0': resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==} peerDependencies: @@ -4883,6 +4902,12 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vaul@0.9.1: + resolution: {integrity: sha512-fAhd7i4RNMinx+WEm6pF3nOl78DFkAazcN04ElLPFF9BMCNGbY/kou8UMhIcicm0rJCNePJP0Yyza60gGOD0Jw==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + vite@5.3.3: resolution: {integrity: sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A==} engines: {node: ^18.0.0 || >=20.0.0} @@ -6368,6 +6393,28 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-dialog@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-portal': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.3.1) + aria-hidden: 1.2.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.5.7(@types/react@18.3.3)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + '@radix-ui/react-direction@1.1.0(@types/react@18.3.3)(react@18.3.1)': dependencies: react: 18.3.1 @@ -10360,6 +10407,15 @@ snapshots: vary@1.1.2: {} + vaul@0.9.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@radix-ui/react-dialog': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + vite@5.3.3(@types/node@20.14.10)(terser@5.31.2): dependencies: esbuild: 0.21.5 diff --git a/dashboard/src/components/Filter/Drawer.tsx b/dashboard/src/components/Filter/Drawer.tsx new file mode 100644 index 0000000..ab01492 --- /dev/null +++ b/dashboard/src/components/Filter/Drawer.tsx @@ -0,0 +1,117 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; + +import { IoClose } from 'react-icons/io5'; + +import { Button } from '../ui/button'; + +import { + Drawer as UIDrawer, + DrawerContent, + DrawerFooter, + DrawerClose, + DrawerTrigger, +} from '../ui/drawer'; +import { Separator } from '../ui/separator'; + +interface IDrawerLink { + treeURL: string; + onRefresh: () => void; +} + +interface IFilterDrawer extends IDrawerLink { + children?: React.ReactNode; + onCancel: () => void; + onFilter: () => void; +} + +const DrawerHeader = (): JSX.Element => { + return ( +
+
+ + + + + + +
+ +
+ ); +}; + +const DrawerLink = ({ treeURL, onRefresh }: IDrawerLink): JSX.Element => { + return ( +
+
+ + + + + {treeURL} + +
+ +
+ ); +}; + +const Drawer = ({ + treeURL, + children, + onRefresh, + onCancel, + onFilter, +}: IFilterDrawer): JSX.Element => { + return ( + + + + + + + +
+ +
+ {React.Children.map(children, (child, idx) => ( + <> + {idx != 0 && } +
{child}
+ + ))} +
+
+ + + + + + + + + +
+
+ ); +}; + +export default Drawer; diff --git a/dashboard/src/components/Filter/SummarySection.tsx b/dashboard/src/components/Filter/SummarySection.tsx new file mode 100644 index 0000000..492e8f1 --- /dev/null +++ b/dashboard/src/components/Filter/SummarySection.tsx @@ -0,0 +1,42 @@ +import { useMemo } from 'react'; + +interface ISummarySectionColumn { + title: string; + value: string; +} + +export interface ISummarySection { + title: string; + columns: ISummarySectionColumn[]; +} + +const SummarySectioncolumn = ({ + title, + value, +}: ISummarySectionColumn): JSX.Element => { + return ( +
+
{title}
+ {value} +
+ ); +}; + +const SummarySection = ({ title, columns }: ISummarySection): JSX.Element => { + const columnComponents = useMemo( + () => + columns.map(column => ( + + )), + [columns], + ); + + return ( +
+

{title}

+
{columnComponents}
+
+ ); +}; + +export default SummarySection; diff --git a/dashboard/src/components/Filter/TimeRangeSection.tsx b/dashboard/src/components/Filter/TimeRangeSection.tsx index 19f51f1..33288ee 100644 --- a/dashboard/src/components/Filter/TimeRangeSection.tsx +++ b/dashboard/src/components/Filter/TimeRangeSection.tsx @@ -3,6 +3,8 @@ import { FormattedMessage } from 'react-intl'; import { Input } from '../ui/input'; interface TimeRangeSection { + title: string; + subtitle: string; min: number; max: number; onMinChange: (e: React.FormEvent) => void; @@ -13,13 +15,17 @@ const inputContainerClass = 'flex gap-x-4 items-center'; const inputClass = 'max-w-20'; const TimeRangeSection = ({ + title, + subtitle, min, max, onMinChange, onMaxChange, }: TimeRangeSection): JSX.Element => { return ( -
+
+

{title}

+

{subtitle}

: diff --git a/dashboard/src/components/Filter/CheckboxSection.stories.tsx b/dashboard/src/components/Filter/stories/CheckboxSection.stories.tsx similarity index 95% rename from dashboard/src/components/Filter/CheckboxSection.stories.tsx rename to dashboard/src/components/Filter/stories/CheckboxSection.stories.tsx index a457599..a0d6e22 100644 --- a/dashboard/src/components/Filter/CheckboxSection.stories.tsx +++ b/dashboard/src/components/Filter/stories/CheckboxSection.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import { fn } from '@storybook/test'; -import CheckboxSection from './CheckboxSection'; +import CheckboxSection from '../CheckboxSection'; const ActionsData = { onClickItem: fn(), diff --git a/dashboard/src/components/Filter/stories/FilterDrawer.stories.tsx b/dashboard/src/components/Filter/stories/FilterDrawer.stories.tsx new file mode 100644 index 0000000..eafbaf7 --- /dev/null +++ b/dashboard/src/components/Filter/stories/FilterDrawer.stories.tsx @@ -0,0 +1,85 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { fn } from '@storybook/test'; + +import { IntlProvider } from 'react-intl'; +import { flatten } from 'flat'; + +import { LOCALES } from '../../../locales/constants'; + +import { messages } from '../../../locales/messages'; + +import FilterDrawer from '../Drawer'; + +import CheckboxSection from '../CheckboxSection'; +import TimeRangeSection from '../TimeRangeSection'; +import SummarySection from '../SummarySection'; + +const ActionsData = { + onRefresh: fn(), + onCancel: fn(), + onFilter: fn(), +}; + +const meta: Meta = { + title: 'FilterDrawer', + component: FilterDrawer, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + decorators: [ + (): JSX.Element => ( + + + + + + + + + ), + ], +}; + +const summarySectionProps = { + title: 'Tree', + columns: [ + { title: 'Tree', value: 'stable-rc' }, + { title: 'Matainer', value: 'Shannon Nelson' }, + { + title: 'Commit/tag', + value: '5.15.150-rc1 - 3ab4d9c9e190217ee7e974c70b96795cd2f74611', + }, + ], +}; + +const checkboxSectionProps = { + items: ['linux-5.15.y', 'Status:failed', 'Status: Warnings'], + title: 'Branch', + subtitle: 'Please select one or more Branches', + onClickItem: (idx: number, isChecked: boolean): void => + console.log(idx, isChecked), +}; + +const onChangeM = (e: React.FormEvent): void => + console.log(e.target); +const timeRangeSectionProps = { + title: 'Timing', + subtitle: 'Please select a range of timing:', + min: 0, + max: 100, + onMinChange: onChangeM, + onMaxChange: onChangeM, +}; diff --git a/dashboard/src/components/Filter/TimeRangeSection.stories.tsx b/dashboard/src/components/Filter/stories/TimeRangeSection.stories.tsx similarity index 74% rename from dashboard/src/components/Filter/TimeRangeSection.stories.tsx rename to dashboard/src/components/Filter/stories/TimeRangeSection.stories.tsx index 9a449d9..ba7d99c 100644 --- a/dashboard/src/components/Filter/TimeRangeSection.stories.tsx +++ b/dashboard/src/components/Filter/stories/TimeRangeSection.stories.tsx @@ -4,11 +4,11 @@ import { flatten } from 'flat'; import { fn } from '@storybook/test'; -import { LOCALES } from '../../locales/constants'; +import { LOCALES } from '../../../locales/constants'; -import { messages } from '../../locales/messages'; +import { messages } from '../../../locales/messages'; -import TimeRangeSection from './TimeRangeSection'; +import TimeRangeSection from '../TimeRangeSection'; const ActionsData = { onMinChange: fn(), @@ -31,7 +31,12 @@ export default meta; type Story = StoryObj; export const Default: Story = { - args: { min: 0, max: 10 }, + args: { + title: 'Timing', + subtitle: 'Please select a range of timing:', + min: 0, + max: 10, + }, decorators: [ (story): JSX.Element => ( ) => ( + +) +Drawer.displayName = "Drawer" + +const DrawerTrigger = DrawerPrimitive.Trigger + +const DrawerPortal = DrawerPrimitive.Portal + +const DrawerClose = DrawerPrimitive.Close + +const DrawerOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName + +const DrawerContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + +
+ {children} + + +)) +DrawerContent.displayName = "DrawerContent" + +const DrawerHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DrawerHeader.displayName = "DrawerHeader" + +const DrawerFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DrawerFooter.displayName = "DrawerFooter" + +const DrawerTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerTitle.displayName = DrawerPrimitive.Title.displayName + +const DrawerDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerDescription.displayName = DrawerPrimitive.Description.displayName + +export { + Drawer, + DrawerPortal, + DrawerOverlay, + DrawerTrigger, + DrawerClose, + DrawerContent, + DrawerHeader, + DrawerFooter, + DrawerTitle, + DrawerDescription, +} diff --git a/dashboard/src/locales/messages/index.ts b/dashboard/src/locales/messages/index.ts index 16a7003..da937d9 100644 --- a/dashboard/src/locales/messages/index.ts +++ b/dashboard/src/locales/messages/index.ts @@ -27,6 +27,9 @@ export const messages = { filter: { min: 'Min', max: 'Max', + filtering: 'Filtering', + treeURL: 'Tree URL', + refresh: 'Refresh', }, }, }; diff --git a/dashboard/tailwind.config.js b/dashboard/tailwind.config.js index 6e75cf3..6b935bd 100644 --- a/dashboard/tailwind.config.js +++ b/dashboard/tailwind.config.js @@ -32,14 +32,16 @@ module.exports = { "accordion-up": "accordion-up 0.2s ease-out", }, colors: { - 'bgSecondary' : '#343638', - 'lightBlue': '#11B3E6', + 'bgSecondary': '#343638', + 'lightBlue': '#11B3E6', 'onSecondary-10': '#FFFFFF1A', "white": '#FFFFFF', "lightGray": '#F4F4F4', "mediumGray": '#EAEAEA', "darkGray": '#D6D6D6', + "darkGray2": '#767676', "dimGray": '#454545', + "dimBlack": '#333333', "black": '#000000', "lightRed": '#FFBBBB', "yellow": '#FFD27C',