diff --git a/packages/odyssey-storybook/applitools.config.js b/packages/odyssey-storybook/applitools.config.js index 7210a87c89..8270c58dd3 100644 --- a/packages/odyssey-storybook/applitools.config.js +++ b/packages/odyssey-storybook/applitools.config.js @@ -39,6 +39,7 @@ const applitoolsConfig = { runInDocker: true, serverUrl: "https://oktaeyes.applitools.com", testConcurrency: 20, + waitBeforeCapture: 1000, }; module.exports = applitoolsConfig; diff --git a/packages/odyssey-storybook/src/guidelines/Roadmap/RoadmapTable.tsx b/packages/odyssey-storybook/src/guidelines/Roadmap/RoadmapTable.tsx index 12b4572475..0f8683dc11 100644 --- a/packages/odyssey-storybook/src/guidelines/Roadmap/RoadmapTable.tsx +++ b/packages/odyssey-storybook/src/guidelines/Roadmap/RoadmapTable.tsx @@ -10,9 +10,10 @@ * See the License for the specific language governing permissions and limitations under the License. */ -import { DataFilter } from "@okta/odyssey-react-mui/labs"; -import { DataTable, DataTableSortingState } from "@okta/odyssey-react-mui"; -import { columns, data, OdysseyComponent } from "./roadmapData"; +/* eslint-disable import/no-extraneous-dependencies */ +import { memo, useCallback } from "react"; +import { Box, DataTable, DataTableGetDataType } from "@okta/odyssey-react-mui"; +import { useColumns, data, OdysseyComponent } from "./roadmapData"; import { Callout, CssBaseline, @@ -23,202 +24,28 @@ import { import { ThemeProvider as StorybookThemeProvider } from "@storybook/theming"; import * as odysseyTokens from "@okta/odyssey-design-tokens"; -const processData = ({ - initialData, - page = 1, - resultsPerPage = 100, - search, - filters, - sort, -}: { - initialData: OdysseyComponent[]; - page?: number; - resultsPerPage?: number; - search?: string; - filters?: DataFilter[]; - sort?: DataTableSortingState; -}) => { - let filteredData = [...initialData]; - - // Implement text-based query filtering - if (search) { - filteredData = filteredData.filter((row) => - Object.values(row).some((value) => - value.toString().toLowerCase().includes(search.toLowerCase()), - ), - ); - } - - // Implement column-specific filtering - if (filters) { - filteredData = filteredData.filter((row) => { - return filters.every(({ id, value }) => { - // If filter value is null or undefined, skip this filter - if (value === null || value === undefined) { - return true; - } - - // General filtering for other columns - return row[id as keyof OdysseyComponent] - ?.toString() - .includes(value.toString()); - }); - }); - } - - function parseCustomDate(dateStr: string): Date { - if (dateStr.length <= 0) { - return new Date(2999, 0); - } - - const months = [ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - ]; - const [monthStr, yearStr] = dateStr.split(" "); - - const month = months.indexOf(monthStr); - const year = parseInt(yearStr.replace("'", ""), 10) + 2000; // Adjust for century - - return new Date(year, month); - } - - // Implement sorting - if (sort && sort.length > 0) { - filteredData.sort((a, b) => { - for (const { id, desc } of sort) { - let aValue: string | Date = a[id as keyof OdysseyComponent]; - let bValue: string | Date = b[id as keyof OdysseyComponent]; - - if ( - id === "startDate" || - id === "labsRelease" || - id === "fullRelease" - ) { - aValue = parseCustomDate(aValue); - bValue = parseCustomDate(bValue); - } - - if (aValue < bValue) return desc ? 1 : -1; - if (aValue > bValue) return desc ? -1 : 1; - } - - return 0; - }); - } - // Implement pagination - const startIdx = (page - 1) * resultsPerPage; - const endIdx = startIdx + resultsPerPage; - const paginatedData = filteredData.slice(startIdx, endIdx); - - return paginatedData; -}; - export const InnerRoadmapTable = () => { - // Constants for filter options - - const typeOptions = [ - { label: "Component", value: "Component" }, - { label: "Pattern", value: "Pattern" }, - ]; - - const statusOptions = [ - { label: "In Progress", value: "In progress" }, - { label: "In Labs", value: "In labs" }, - { label: "Released", value: "Released" }, - { label: "Not Started", value: "Not started" }, - ]; - - const expectedOptions = [ - { label: "FY24", value: "FY24" }, - { label: "TBD", value: "TBD" }, - { label: "Q1 FY25", value: "Q1 FY25" }, - { label: "Q2 FY25", value: "Q2 FY25" }, - { label: "Q3 FY25", value: "Q3 FY25" }, - { label: "Q4 FY25", value: "Q4 FY25" }, - { label: "Q1 FY26", value: "Q1 FY26" }, - { label: "Q2 FY26", value: "Q2 FY26" }, - { label: "Q3 FY26", value: "Q3 FY26" }, - { label: "Q4 FY26", value: "Q4 FY26" }, - ]; - - const fetchData = ({ - page, - resultsPerPage, - search, - filters, - sort, + const columns = useColumns(); // Use the hook to get columns + const filterData = ({ + data, }: { - page?: number; - resultsPerPage?: number; - search?: string; - filters?: DataFilter[]; - sort?: DataTableSortingState; - }) => { - return processData({ - initialData: data, - page: page, - resultsPerPage: resultsPerPage, - search: search, - filters: filters, - sort: sort, - }); + data: OdysseyComponent[]; + } & DataTableGetDataType) => { + const filteredData = data; + + return filteredData; }; - return ( - name} - getData={fetchData} - hasChangeableDensity={false} - hasColumnResizing={false} - hasColumnVisibility={false} - hasFilters - filters={[ - { - id: "type", - label: "Type", - variant: "select", - options: typeOptions, - }, - { - id: "status", - label: "Status", - variant: "select", - options: statusOptions, - }, - { - id: "deliverableTiming", - label: "Deliverable timing", - variant: "autocomplete", - options: expectedOptions, - }, - ]} - resultsPerPage={100} - hasPagination={false} - hasRowSelection={false} - hasRowReordering={false} - searchDelayTime={0} - hasSearch - hasSorting - /> - ); + const fetchData = useCallback(({ ...props }: DataTableGetDataType) => { + return filterData({ data, ...props }); + }, []); + + return ; }; const WrappedRoadmapTable = () => { const odysseyTheme = createOdysseyMuiTheme({ odysseyTokens }); - + const MemoizedInnerRoadmapTable = memo(InnerRoadmapTable); return ( {/* @ts-expect-error type mismatch on "typography" */} @@ -233,7 +60,15 @@ const WrappedRoadmapTable = () => { functionality, and you should not rely on them to make your purchase decisions. - + + + diff --git a/packages/odyssey-storybook/src/guidelines/Roadmap/roadmap.json b/packages/odyssey-storybook/src/guidelines/Roadmap/roadmap.json index 60bc6c0be7..f16812c3ea 100644 --- a/packages/odyssey-storybook/src/guidelines/Roadmap/roadmap.json +++ b/packages/odyssey-storybook/src/guidelines/Roadmap/roadmap.json @@ -5,7 +5,7 @@ "status": "Released", "define": "Complete", "design": "Complete", - "develop": "In progress", + "develop": "Complete", "deliverableTiming": "Q2 FY25" }, { @@ -65,11 +65,11 @@ { "name": "Card", "type": "Component", - "status": "In Labs", - "define": "In Labs", - "design": "In Labs", - "develop": "In Labs", - "deliverableTiming": "TBD" + "status": "In progress", + "define": "Complete", + "design": "Complete", + "develop": "Complete", + "deliverableTiming": "Q2 FY25" }, { "name": "Checkbox and checkbox group", @@ -96,15 +96,24 @@ "define": "Not started", "design": "Not started", "develop": "Not started", + "deliverableTiming": "Q3 FY25" + }, + { + "name": "Date picker", + "type": "Component", + "status": "In Labs", + "define": "Complete", + "design": "Complete", + "develop": "In progress", "deliverableTiming": "Q2 FY25" }, { - "name": "Data stack (data list, resource list?)", + "name": "Data stack", "type": "Component", - "status": "In progress", - "define": "In progress", - "design": "Not started", - "develop": "Not started", + "status": "In Labs", + "define": "Complete", + "design": "Complete", + "develop": "Complete", "deliverableTiming": "Q2 FY25" }, { @@ -117,11 +126,11 @@ "deliverableTiming": "Q1 FY25" }, { - "name": "Date picker", + "name": "Data view", "type": "Component", "status": "In Labs", "define": "Complete", - "design": "In progress", + "design": "Complete", "develop": "In progress", "deliverableTiming": "Q2 FY25" }, @@ -144,18 +153,18 @@ "deliverableTiming": "Q1 FY25" }, { - "name": "Duration picker\n(or fieldset)", + "name": "Duration' picker\n(or fieldset)", "type": "Component", - "status": "Not started", - "define": "Not started", - "design": "Not started", + "status": "In progress", + "define": "In progress", + "design": "In progress", "develop": "Not started", - "deliverableTiming": "Q2 FY25" + "deliverableTiming": "Q4 FY25" }, { "name": "Empty states v1 (no illustration)", "type": "Component", - "status": "In progress", + "status": "Released", "define": "Complete", "design": "Complete", "develop": "Complete", @@ -164,9 +173,9 @@ { "name": "Empty states v2 (illustration)", "type": "Component", - "status": "Not started", - "define": "Not started", - "design": "Not started", + "status": "In progress", + "define": "Complete", + "design": "In progress", "develop": "Not started", "deliverableTiming": "Q3 FY25" }, @@ -198,14 +207,32 @@ "deliverableTiming": "FY24" }, { - "name": "Form Layout (Advanced)", + "name": "Form layout (Advanced)", "type": "Component", "status": "In progress", + "define": "Complete", + "design": "In progress", + "develop": "In progress", + "deliverableTiming": "Q3 FY25" + }, + { + "name": "Group picker", + "type": "Component", + "status": "In Labs", "define": "In progress", - "design": "-", - "develop": "-", + "design": "In progress", + "develop": "In progress", "deliverableTiming": "Q2 FY25" }, + { + "name": "Inline text field", + "type": "Component", + "status": "In progress", + "define": "Complete", + "design": "Complete", + "develop": "In progress", + "deliverableTiming": "Q3 FY25" + }, { "name": "Link", "type": "Component", @@ -222,7 +249,7 @@ "define": "Not started", "design": "Not started", "develop": "Not started", - "deliverableTiming": "Q3 FY25" + "deliverableTiming": "Q4 FY25" }, { "name": "Menu button", @@ -236,7 +263,7 @@ { "name": "Multi-select dropdown", "type": "Component", - "status": "-", + "status": "Not started", "define": "-", "design": "-", "develop": "-", @@ -254,18 +281,18 @@ { "name": "Policy Recommender\n(AI Pattern)", "type": "Pattern", - "status": "Not started", - "define": "Not started", + "status": "In progress", + "define": "In progress", "design": "Not started", "develop": "Not started", - "deliverableTiming": "Q3 FY25" + "deliverableTiming": "Q4 FY25" }, { - "name": "Progress Bar", + "name": "Progress bar", "type": "Component", - "status": "Not started", - "define": "Not started", - "design": "Not started", + "status": "In progress", + "define": "In progress", + "design": "In progress", "develop": "Not started", "deliverableTiming": "Q3 FY25" }, @@ -278,6 +305,15 @@ "develop": "Complete", "deliverableTiming": "FY24" }, + { + "name": "Search input", + "type": "Component", + "status": "In progress", + "define": "Complete", + "design": "Complete", + "develop": "Complete", + "deliverableTiming": "Q2 FY25" + }, { "name": "Search field", "type": "Pattern", @@ -285,7 +321,7 @@ "define": "Not started", "design": "Not started", "develop": "Not started", - "deliverableTiming": "TBD" + "deliverableTiming": "Q3 FY25" }, { "name": "Select", @@ -318,10 +354,10 @@ "name": "Switch", "type": "Component", "status": "In Labs", - "define": "In Labs", + "define": "Complete", "design": "In Labs", "develop": "In Labs", - "deliverableTiming": "Q1 FY25" + "deliverableTiming": "Q2 FY25" }, { "name": "Tabs", @@ -351,13 +387,13 @@ "deliverableTiming": "FY24" }, { - "name": "Time Picker\n(or field set)", + "name": "Date Time Picker (name tbd)\n(or field set)", "type": "Component", "status": "Not started", "define": "Not started", "design": "Not started", "develop": "Not started", - "deliverableTiming": "Q2 FY25" + "deliverableTiming": "Q3 FY25" }, { "name": "Toast", diff --git a/packages/odyssey-storybook/src/guidelines/Roadmap/roadmapData.tsx b/packages/odyssey-storybook/src/guidelines/Roadmap/roadmapData.tsx index ec544499c0..6fee43629f 100644 --- a/packages/odyssey-storybook/src/guidelines/Roadmap/roadmapData.tsx +++ b/packages/odyssey-storybook/src/guidelines/Roadmap/roadmapData.tsx @@ -10,6 +10,8 @@ * See the License for the specific language governing permissions and limitations under the License. */ +/* eslint-disable import/no-extraneous-dependencies */ +import { useMemo } from "react"; import { DataTableRowData, Status, @@ -19,7 +21,6 @@ import { import { DataTableColumn } from "@okta/odyssey-react-mui"; import rawData from "./roadmap.json"; -export const data: OdysseyComponent[] = rawData as OdysseyComponent[]; export type OdysseyComponent = { name: string; @@ -31,86 +32,109 @@ export type OdysseyComponent = { deliverableTiming: string; }; -export const columns: DataTableColumn[] = [ - { - accessorKey: "name", - header: "Name", - enableHiding: false, - size: 325, - }, - { - accessorKey: "type", - header: "Type", - enableHiding: true, - size: 200, - }, - { - accessorKey: "status", - header: "Status", - size: 200, - Cell: ({ cell, row }) => { - const statusValue = cell.getValue(); - const defineValue = row.original.define; - const designValue = row.original.design; - const developValue = row.original.develop; - const severityMap = new Map< - string, - (typeof statusSeverityValues)[number] - >([ - ["Released", "success"], - ["In Labs", "warning"], - ["In progress", "default"], - ["Not started", "error"], - ]); - const severity = severityMap.get(statusValue) || "default"; +export const data: OdysseyComponent[] = rawData as OdysseyComponent[]; + +const severityMap = new Map([ + ["Released", "success"], + ["In Labs", "warning"], + ["In progress", "default"], + ["Not started", "error"], +]); + +const getTooltipText = ( + defineValue: string, + designValue: string, + developValue: string, +): string => { + let text = ""; + if (defineValue === "In progress") { + text += "Project definition in progress"; + } + if (["Complete", "In progress"].includes(designValue)) { + text += (text ? " " : "") + "Design: " + designValue; + } + if (["Complete", "In progress"].includes(developValue)) { + text += (text ? " " : "") + "Develop: " + developValue; + } + return text.trim(); +}; + +type CellProps = { + cell: { getValue: () => string }; + row: { original: OdysseyComponent }; +}; - // First priority: Check if the define stage is "In Progress" - if (defineValue === "In Progress") { - // Return a Tooltip specifically for this condition and do nothing else - return ( - - - - ); - } +const StatusCell: React.FC = ({ cell, row }) => { + const statusValue = cell.getValue(); + const { + define: defineValue, + design: designValue, + develop: developValue, + } = row.original; - // If defineValue is not "In Progress", then proceed with this logic: - let tooltipText = ""; - if (defineValue === "In progress") { - tooltipText += "Project definition in progress"; - } - if (["Complete", "In progress"].includes(designValue)) { - tooltipText += "Design: " + designValue + " "; - } - if (["Complete", "In progress"].includes(developValue)) { - tooltipText += "Develop: " + developValue; - } + const severity = useMemo( + () => severityMap.get(statusValue) || "default", + [statusValue], + ); + const tooltipText = useMemo( + () => getTooltipText(defineValue, designValue, developValue), + [defineValue, designValue, developValue], + ); - // Only show the tooltip if there's relevant information to display - if (tooltipText && statusValue !== "Released") { - return ( - - - - ); - } + if (defineValue === "In Progress") { + return ( + + + + ); + } - // If there is no relevant tooltip text, just show the status without any tooltip - return ; - }, - }, + if (tooltipText && statusValue !== "Released") { + return ( + + + + ); + } - { - accessorKey: "deliverableTiming", - header: "Deliverable timing", - enableHiding: false, - size: 200, - }, -]; + return ; +}; + +export const useColumns = (): DataTableColumn[] => { + return useMemo( + () => + [ + { + accessorKey: "name", + header: "Name", + enableHiding: false, + size: 290, + }, + { + accessorKey: "type", + header: "Type", + enableHiding: true, + size: 110, + }, + { + accessorKey: "status", + header: "Status", + size: 115, + Cell: ({ cell, row }: CellProps) => ( + + ), + }, + { + accessorKey: "deliverableTiming", + header: "Deliverable timing", + enableHiding: false, + size: 155, + }, + ] as DataTableColumn[], + [], + ); +};