diff --git a/package-lock.json b/package-lock.json index 29c14b951..9ad951bc5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,9 @@ "name": "code-sandbox", "version": "0.0.0", "dependencies": { - "@abgov/react-components": "6.8.0-alpha.3", - "@abgov/ui-components-common": "1.8.0-alpha.3", - "@abgov/web-components": "1.38.0-alpha.5", + "@abgov/react-components": "6.9.0-alpha.2", + "@abgov/ui-components-common": "1.9.0-alpha.1", + "@abgov/web-components": "1.39.0-alpha.2", "@faker-js/faker": "^8.3.1", "highlight.js": "^11.8.0", "js-cookie": "^3.0.5", @@ -68,9 +68,9 @@ } }, "node_modules/@abgov/react-components": { - "version": "6.8.0-alpha.3", - "resolved": "https://registry.npmjs.org/@abgov/react-components/-/react-components-6.8.0-alpha.3.tgz", - "integrity": "sha512-BMEMhhJiNF/QaVHXG4yCO/fQaLUy6d7nYZS0h6cKFHCeCmd5ZZVpuZI3E/wdWDO1HgZH+HLpNtUyi7G7BpYfkA==", + "version": "6.9.0-alpha.2", + "resolved": "https://registry.npmjs.org/@abgov/react-components/-/react-components-6.9.0-alpha.2.tgz", + "integrity": "sha512-mBlGM/u/wsHLTPpZxiVhdES9Ra+Hj6JaVGzQuWm3JI+Jqz5wwCRLeGzLZD03fAlfnEhMshXpPuKtQxphVLQNPg==", "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", @@ -78,14 +78,14 @@ } }, "node_modules/@abgov/ui-components-common": { - "version": "1.8.0-alpha.3", - "resolved": "https://registry.npmjs.org/@abgov/ui-components-common/-/ui-components-common-1.8.0-alpha.3.tgz", - "integrity": "sha512-RW53rqOfP0gd8W2OoWOZnj8U8Q5L4KL9V2staZswqm+O7D3AXQUAslmXImGUPd922Xn8hkPNzDEq1CPuoAcaXw==" + "version": "1.9.0-alpha.1", + "resolved": "https://registry.npmjs.org/@abgov/ui-components-common/-/ui-components-common-1.9.0-alpha.1.tgz", + "integrity": "sha512-5ZSV7XuVJ+NMNCJd9LTQzAGwLERGUig/L1WlIn+1n0OOxonYo4df4jhinj2VyJRQ/XiNYa1vYlh59NRlAyu8pg==" }, "node_modules/@abgov/web-components": { - "version": "1.38.0-alpha.5", - "resolved": "https://registry.npmjs.org/@abgov/web-components/-/web-components-1.38.0-alpha.5.tgz", - "integrity": "sha512-YqVXvd8KkCc/oy2rcfiuL89RB0kJ+x/y94vm0azPLL1iKyhw1xCQBIXhJIkeGeEcLGZHUNYGV2DIPupHHOnb5Q==" + "version": "1.39.0-alpha.2", + "resolved": "https://registry.npmjs.org/@abgov/web-components/-/web-components-1.39.0-alpha.2.tgz", + "integrity": "sha512-eY66yYlJTkqoDDQWj6p0rcywcjvwExa6mDJoBpVwp325Guki10l59xUeC78WwYaVTFZP7nmTB1OXKuV3+Vnkhg==" }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", diff --git a/package.json b/package.json index 6feeb98df..c4e5befb4 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,9 @@ "prettier": "npx prettier . --write" }, "dependencies": { - "@abgov/react-components": "6.8.0-alpha.3", - "@abgov/ui-components-common": "1.8.0-alpha.3", - "@abgov/web-components": "1.38.0-alpha.5", + "@abgov/react-components": "6.9.0-alpha.2", + "@abgov/ui-components-common": "1.9.0-alpha.1", + "@abgov/web-components": "1.39.0-alpha.2", "@faker-js/faker": "^8.3.1", "highlight.js": "^11.8.0", "js-cookie": "^3.0.5", diff --git a/src/examples/app-header/AppHeaderExamples.tsx b/src/examples/app-header/AppHeaderExamples.tsx index 160267ff8..8bafb11e0 100644 --- a/src/examples/app-header/AppHeaderExamples.tsx +++ b/src/examples/app-header/AppHeaderExamples.tsx @@ -1,5 +1,7 @@ import { HeaderWithNavigation } from "@examples/header-with-navigation.tsx"; import { HeaderWithMenuClickEvent } from "@examples/header-with-menu-click-event.tsx"; +import { HeaderSignIn } from "@examples/header-with-sign-in.tsx"; +import { HeaderLoggedInMenu } from "@examples/header-logged-in-menu.tsx"; import { SandboxHeader } from "@components/sandbox/sandbox-header/sandboxHeader.tsx"; export const AppHeaderExamples = () => { @@ -16,6 +18,18 @@ export const AppHeaderExamples = () => { figmaExample=""> + + + + + + + + ); }; diff --git a/src/examples/display-numbers-in-a-table-so-they-can-be-scanned-easily.tsx b/src/examples/display-numbers-in-a-table-so-they-can-be-scanned-easily.tsx index ba87d5975..4e57cf46c 100644 --- a/src/examples/display-numbers-in-a-table-so-they-can-be-scanned-easily.tsx +++ b/src/examples/display-numbers-in-a-table-so-they-can-be-scanned-easily.tsx @@ -15,14 +15,24 @@ export const DisplayNumbersInATableSoTheyCanBeScannedEasily = () => { - Item 1 - Item 2 - 54 + Christian + Batz + 54356456 - Item 1 - Item 2 - 4567 + Brian + Wisozk + 23212321 + + + Neha + Jones + 23197213 + + + Tristan + Buckridge + 76312313 diff --git a/src/examples/filter-data-in-a-table.tsx b/src/examples/filter-data-in-a-table.tsx index e683be1c9..c1036e1e5 100644 --- a/src/examples/filter-data-in-a-table.tsx +++ b/src/examples/filter-data-in-a-table.tsx @@ -9,6 +9,9 @@ import { GoabInput, GoabTable, GoabText, + GoabPopover, + GoabRadioGroup, + GoabRadioItem } from "@abgov/react-components"; import type { GoabBadgeType, @@ -17,10 +20,12 @@ import type { } from "@abgov/ui-components-common"; import { LanguageVersionContext } from "@contexts/LanguageVersionContext.tsx"; import { CodeSnippet } from "@components/code-snippet/CodeSnippet.tsx"; +import { GoabRadioGroupOnChangeDetail } from "@abgov/ui-components-common"; export const FilterDataInATable = () => { const { version } = useContext(LanguageVersionContext); + const [selectedFilter, setSelectedFilter] = useState(null); const [typedChips, setTypedChips] = useState([]); const [inputValue, setInputValue] = useState(""); const [inputError, setInputError] = useState(""); @@ -63,6 +68,12 @@ export const FilterDataInATable = () => { ); const [dataFiltered, setDataFiltered] = useState(data); + const target = ( + + Filter + + ); + const handleInputChange = (detail: GoabInputOnChangeDetail) => { const newValue = detail.value.trim(); setInputValue(newValue); @@ -103,31 +114,43 @@ export const FilterDataInATable = () => { ); }, []); + function radioGroupOnChange(event: GoabRadioGroupOnChangeDetail) { + setSelectedFilter(event.value); + } + const getFilteredData = useCallback( - (typedChips: string[]) => { - if (typedChips.length === 0) { - return data; + (typedChips: string[], selectedFilter: string | null) => { + let filteredData = data; + + if (typedChips.length > 0) { + filteredData = filteredData.filter((item: any) => + typedChips.every(chip => checkNested(item, chip)) + ); + } + + if (selectedFilter) { + filteredData = filteredData.filter( + (item: any) => item.status && item.status.text === selectedFilter + ); } - const filteredData = data.filter((item: object) => - typedChips.every(chip => checkNested(item, chip)) - ); return filteredData; }, [checkNested, data] ); + useEffect(() => { - setDataFiltered(getFilteredData(typedChips)); - }, [getFilteredData, typedChips]); + setDataFiltered(getFilteredData(typedChips, selectedFilter)); + }, [getFilteredData, typedChips, selectedFilter]); return ( // NOTE: Input onKeyPress functionality breaks when wrapped in Sandbox component // <> - - + + { onChange={handleInputChange} onKeyPress={handleInputKeyPress} /> - - Filter - + + +
+ + + + + + + +
+
- {typedChips.length > 0 && ( + {(typedChips.length > 0 || selectedFilter) && (
- + Filter: - {typedChips.length > 0 && - typedChips.map((typedChip, index) => ( - removeTypedChip(typedChip)} - /> - ))} - setTypedChips([])}> - Clear all - + {typedChips.map((typedChip, index) => ( + removeTypedChip(typedChip)} + /> + ))} + {selectedFilter && ( + { + setSelectedFilter(null); + }} + /> + )} + {(typedChips.length > 0 || selectedFilter) && ( + { + setTypedChips([]); + setSelectedFilter(null); + }}> + Clear all + + )}
)} - + Status @@ -198,102 +246,123 @@ export const FilterDataInATable = () => { tags="angular" allowCopy={true} code={` -export class TableComponent { - typedChips: string[] = []; - inputValue = ""; - inputError = ""; - readonly errorEmpty = "Empty filter"; - readonly errorDuplicate = "Enter a unique filter"; - readonly data = [ - { - status: { type: "information", text: "In progress" }, - name: "Ivan Schmidt", - id: "7838576954", - }, - { - status: { type: "success", text: "Completed" }, - name: "Luz Lakin", - id: "8576953364", - }, - { - status: { type: "information", text: "In progress" }, - name: "Keith McGlynn", - id: "9846041345", - }, - { - status: { type: "success", text: "Completed" }, - name: "Melody Frami", - id: "7385256175", - }, - { - status: { type: "important", text: "Updated" }, - name: "Frederick Skiles", - id: "5807570418", - }, - { - status: { type: "success", text: "Completed" }, - name: "Dana Pfannerstill", - id: "5736306857", - }, - ]; - dataFiltered = this.getFilteredData(this.typedChips); + export class TablePopoverComponent { + typedChips: string[] = []; - handleInputChange(event: Event): void { - const newValue = (event.target as HTMLInputElement).value.trim(); - this.inputValue = newValue; - } + inputValue = ''; + inputError = ''; + readonly errorEmpty = 'Empty filter'; + readonly errorDuplicate = 'Enter a unique filter'; - handleInputKeyPress(event: KeyboardEvent): void { - if (event.key === "Enter") { - this.applyFilter(); - } - } + // Radio filter state + selectedFilter: string | null = null; - applyFilter() { - if (this.inputValue === "") { - this.inputError = this.errorEmpty; - return; - } - if (this.typedChips.includes(this.inputValue)) { - this.inputError = this.errorDuplicate; - return; - } - this.typedChips = [...this.typedChips, this.inputValue]; - this.inputValue = ""; - this.inputError = ""; - this.dataFiltered = this.getFilteredData(this.typedChips); - } + // Table data + popoverValues: PopoverValue[] = [ + { + status: { type: "information", text: "In progress" }, + name: "Ivan Schmidt", + id: "7838576954", + }, + { + status: { type: "success", text: "Completed" }, + name: "Luz Lakin", + id: "8576953364", + }, + { + status: { type: "information", text: "In progress" }, + name: "Keith McGlynn", + id: "9846041345", + }, + { + status: { type: "success", text: "Completed" }, + name: "Melody Frami", + id: "7385256175", + }, + { + status: { type: "important", text: "Updated" }, + name: "Frederick Skiles", + id: "5807570418", + }, + { + status: { type: "success", text: "Completed" }, + name: "Dana Pfannerstill", + id: "5736306857", + }, + ]; - removeTypedChip(chip: string) { - this.typedChips = this.typedChips.filter((c) => c !== chip); - this.dataFiltered = this.getFilteredData(this.typedChips); - this.inputError = ""; - } + get filteredData(): PopoverValue[] { + let filtered = this.popoverValues; - removeAllTypedChips() { - this.typedChips = []; - this.dataFiltered = this.getFilteredData(this.typedChips); - this.inputError = ""; - } + // Apply radio filter + if (this.selectedFilter) { + filtered = filtered.filter(item => item.status === this.selectedFilter); + } - getFilteredData(typedChips: string[]) { - if (typedChips.length === 0) { - return this.data; - } - const filteredData = this.data.filter((item) => - typedChips.every((chip) => this.checkNested(item, chip)), - ); - return filteredData; - } + // Apply chip filters (all chips must match) + if (this.typedChips.length > 0) { + filtered = filtered.filter(item => + this.typedChips.every(chip => + Object.values(item).some(val => + typeof val === 'string' && val.toLowerCase().includes(chip.toLowerCase()) + ) + ) + ); + } - checkNested(obj: object, chip: string): boolean { - return Object.values(obj).some((value) => - typeof value === "object" && value !== null - ? this.checkNested(value, chip) - : typeof value === "string" && value.toLowerCase().includes(chip.toLowerCase()), - ); - } -} + return filtered; + } + + handleInputChange(event: any): void { + this.inputValue = event.target.value.trim(); + } + + handleInputKeyPress(event: KeyboardEvent): void { + if (event.key === 'Enter') { + this.applyFilter(); + } + } + + applyFilter() { + if (this.inputValue === '') { + this.inputError = this.errorEmpty; + return; + } + if (this.typedChips.includes(this.inputValue)) { + this.inputError = this.errorDuplicate; + return; + } + this.typedChips = [...this.typedChips, this.inputValue]; + this.inputValue = ''; + this.inputError = ''; + } + + removeTypedChip(chip: string) { + this.typedChips = this.typedChips.filter(c => c !== chip); + this.inputError = ''; + } + + removeAllTypedChips() { + this.typedChips = []; + this.inputError = ''; + } + + radioGroupOnChange(value: string) { + this.selectedFilter = value; + } + + clearRadioFilter() { + this.selectedFilter = null; + } + + checkNested(obj: object, chip: string): boolean { + return Object.values(obj).some((value) => + typeof value === "object" && value !== null + ? this.checkNested(value, chip) + : typeof value === "string" && value.toLowerCase().includes(chip.toLowerCase()), + ); + } + } `} /> - - - - - Filter - - - - - - - Filter: - - - - Clear all - - - - - - - Status - Name - ID Number - - - - - - - - {{ item.name }} - {{ item.id }} - - - - - - No results found - + + + + + +
+ + + + + + + + + Remove filter + +
+
+ + Filter + +
+
+ + Filter + +
+
+ + + + Filter: + + + + + Clear all + + + + + + + Status + Text + Number + + + + + + + + Lorem ipsum + {{ u.key }} + + + + + + No results found + `} /> ([]); - const [inputValue, setInputValue] = useState(""); - const [inputError, setInputError] = useState(""); - const errorEmpty = "Empty filter"; - const errorDuplicate = "Enter a unique filter"; - const data = useMemo( - () => [ - { - status: { type: "information" as GoABadgeType, text: "In progress" }, - name: "Ivan Schmidt", - id: "7838576954", - }, - { - status: { type: "success" as GoABadgeType, text: "Completed" }, - name: "Luz Lakin", - id: "8576953364", - }, - { - status: { type: "information" as GoABadgeType, text: "In progress" }, - name: "Keith McGlynn", - id: "9846041345", - }, - { - status: { type: "success" as GoABadgeType, text: "Completed" }, - name: "Melody Frami", - id: "7385256175", - }, - { - status: { type: "important" as GoABadgeType, text: "Updated" }, - name: "Frederick Skiles", - id: "5807570418", - }, - { - status: { type: "success" as GoABadgeType, text: "Completed" }, - name: "Dana Pfannerstill", - id: "5736306857", - }, - ], - [], - ); - const [dataFiltered, setDataFiltered] = useState(data); + const [selectedFilter, setSelectedFilter] = useState(null); + const [typedChips, setTypedChips] = useState([]); + const [inputValue, setInputValue] = useState(""); + const [inputError, setInputError] = useState(""); + const errorEmpty = "Empty filter"; + const errorDuplicate = "Enter a unique filter"; + const data = useMemo( + () => [ + { + status: { type: "information" as GoABadgeType, text: "In progress" }, + name: "Ivan Schmidt", + id: "7838576954", + }, + { + status: { type: "success" as GoABadgeType, text: "Completed" }, + name: "Luz Lakin", + id: "8576953364", + }, + { + status: { type: "information" as GoABadgeType, text: "In progress" }, + name: "Keith McGlynn", + id: "9846041345", + }, + { + status: { type: "success" as GoABadgeType, text: "Completed" }, + name: "Melody Frami", + id: "7385256175", + }, + { + status: { type: "important" as GoABadgeType, text: "Updated" }, + name: "Frederick Skiles", + id: "5807570418", + }, + { + status: { type: "success" as GoABadgeType, text: "Completed" }, + name: "Dana Pfannerstill", + id: "5736306857", + }, + ], + [], + ); + const [dataFiltered, setDataFiltered] = useState(data); - const handleInputChange = (_name: string, value: string) => { - const newValue = value.trim(); - setInputValue(newValue); - }; + const target = ( + + Filter + + ); - const handleInputKeyPress = (_name: string, _value: string, key: string) => { - if (key === "Enter") { - applyFilter(); - } - }; + const handleInputChange = (_name: string, value: string) => { + const newValue = value.trim(); + setInputValue(newValue); + }; - const applyFilter = () => { - if (inputValue === "") { - setInputError(errorEmpty); - return; - } - if (typedChips.length > 0 && typedChips.includes(inputValue)) { - setInputError(errorDuplicate); - return; - } - setTypedChips([...typedChips, inputValue]); - setTimeout(() => { - setInputValue(""); - }, 0); - setInputError(""); - }; + const handleInputKeyPress = (_name: string, _value: string, key: string) => { + if (key === "Enter") { + applyFilter(); + } + }; - const removeTypedChip = (chip: string) => { - setTypedChips(typedChips.filter((c) => c !== chip)); - setInputError(""); - }; + const applyFilter = () => { + if (inputValue === "") { + setInputError(errorEmpty); + return; + } + if (typedChips.length > 0 && typedChips.includes(inputValue)) { + setInputError(errorDuplicate); + return; + } + setTypedChips([...typedChips, inputValue]); + setTimeout(() => { + setInputValue(""); + }, 0); + setInputError(""); + }; - const checkNested = useCallback((obj: object, chip: string): boolean => { - return Object.values(obj).some((value) => - typeof value === "object" && value !== null - ? checkNested(value, chip) - : typeof value === "string" && value.toLowerCase().includes(chip.toLowerCase()), - ); - }, []); + const removeTypedChip = (chip: string) => { + setTypedChips(typedChips.filter((c) => c !== chip)); + setInputError(""); + }; - const getFilteredData = useCallback( - (typedChips: string[]) => { - if (typedChips.length === 0) { - return data; - } - const filteredData = data.filter((item: object) => - typedChips.every((chip) => checkNested(item, chip)), - ); + const checkNested = useCallback((obj: object, chip: string): boolean => { + return Object.values(obj).some((value) => + typeof value === "object" && value !== null + ? checkNested(value, chip) + : typeof value === "string" && value.toLowerCase().includes(chip.toLowerCase()), + ); + }, []); - return filteredData; - }, - [checkNested, data], - ); + const handleInputChange = (_name: string, value: string) => { + setSelectedFilter(value); + }; - useEffect(() => { - setDataFiltered(getFilteredData(typedChips)); - }, [getFilteredData, typedChips]); + const getFilteredData = useCallback( + (typedChips: string[], selectedFilter: string | null) => { + let filteredData = data; + + if (typedChips.length > 0) { + filteredData = filteredData.filter((item: any) => + typedChips.every(chip => checkNested(item, chip)) + ); + } + + if (selectedFilter) { + filteredData = filteredData.filter( + (item: any) => item.status && item.status.text === selectedFilter + ); + } + + return filteredData; + }, + [checkNested, data] + ); + + useEffect(() => { + setDataFiltered(getFilteredData(typedChips, selectedFilter)); + }, [getFilteredData, typedChips, selectedFilter]); `} /> @@ -476,78 +583,96 @@ export class TableComponent { tags="react" allowCopy={true} code={` - <> - - - - - Filter - - - - - {typedChips.length > 0 && ( -
- - Filter: - - {typedChips.length > 0 && - typedChips.map((typedChip, index) => ( - removeTypedChip(typedChip)} - /> - ))} - setTypedChips([])} - > - Clear all - -
- )} + + + + + + +
+ + + + + + + +
+
+
+
- - - - Status - Name - ID Number - - - - {dataFiltered.map((item) => ( - - - - - {item.name} - {item.id} - - ))} - - + {(typedChips.length > 0 || selectedFilter) && ( +
+ + Filter: + + {typedChips.map((typedChip, index) => ( + removeTypedChip(typedChip)} + /> + ))} + {selectedFilter && ( + { + setSelectedFilter(null); + }} + /> + )} + {(typedChips.length > 0 || selectedFilter) && ( + { + setTypedChips([]); + setSelectedFilter(null); + }}> + Clear all + + )} +
+ )} - {dataFiltered.length === 0 && data.length > 0 && ( - No results found - )} - + + + + Status + Name + ID Number + + + + {dataFiltered.map(item => ( + + + + + {item.name} + {item.id} + + ))} + + + + {dataFiltered.length === 0 && data.length > 0 && ( + + No results found + + )} +
`} /> @@ -560,102 +685,125 @@ export class TableComponent { tags="angular" allowCopy={true} code={` -export class TableComponent { - typedChips: string[] = []; - inputValue = ""; - inputError = ""; - readonly errorEmpty = "Empty filter"; - readonly errorDuplicate = "Enter a unique filter"; - readonly data = [ - { - status: { type: "information" as GoabBadgeType, text: "In progress" }, - name: "Ivan Schmidt", - id: "7838576954", - }, - { - status: { type: "success" as GoabBadgeType, text: "Completed" }, - name: "Luz Lakin", - id: "8576953364", - }, - { - status: { type: "information" as GoabBadgeType, text: "In progress" }, - name: "Keith McGlynn", - id: "9846041345", - }, - { - status: { type: "success" as GoabBadgeType, text: "Completed" }, - name: "Melody Frami", - id: "7385256175", - }, - { - status: { type: "important" as GoabBadgeType, text: "Updated" }, - name: "Frederick Skiles", - id: "5807570418", - }, - { - status: { type: "success" as GoabBadgeType, text: "Completed" }, - name: "Dana Pfannerstill", - id: "5736306857", - }, - ]; - dataFiltered = this.getFilteredData(this.typedChips); + + export class TablePopoverComponent { + typedChips: string[] = []; - handleInputChange(detail: GoabInputOnChangeDetail): void { - const newValue = detail.value.trim(); - this.inputValue = newValue; - } + inputValue = ''; + inputError = ''; + readonly errorEmpty = 'Empty filter'; + readonly errorDuplicate = 'Enter a unique filter'; - handleInputKeyPress(detail: GoabInputOnKeyPressDetail): void { - if (detail.key === "Enter") { - this.applyFilter(); - } - } + // Radio filter state + selectedFilter: string | null = null; - applyFilter() { - if (this.inputValue === "") { - this.inputError = this.errorEmpty; - return; - } - if (this.typedChips.includes(this.inputValue)) { - this.inputError = this.errorDuplicate; - return; - } - this.typedChips = [...this.typedChips, this.inputValue]; - this.inputValue = ""; - this.inputError = ""; - this.dataFiltered = this.getFilteredData(this.typedChips); - } + // Table data + popoverValues: PopoverValue[] = [ + { + status: { type: "information" as GoabBadgeType, text: "In progress" }, + name: "Ivan Schmidt", + id: "7838576954", + }, + { + status: { type: "success" as GoabBadgeType, text: "Completed" }, + name: "Luz Lakin", + id: "8576953364", + }, + { + status: { type: "information" as GoabBadgeType, text: "In progress" }, + name: "Keith McGlynn", + id: "9846041345", + }, + { + status: { type: "success" as GoabBadgeType, text: "Completed" }, + name: "Melody Frami", + id: "7385256175", + }, + { + status: { type: "important" as GoabBadgeType, text: "Updated" }, + name: "Frederick Skiles", + id: "5807570418", + }, + { + status: { type: "success" as GoabBadgeType, text: "Completed" }, + name: "Dana Pfannerstill", + id: "5736306857", + }, + ]; - removeTypedChip(chip: string) { - this.typedChips = this.typedChips.filter((c) => c !== chip); - this.dataFiltered = this.getFilteredData(this.typedChips); - this.inputError = ""; - } + get filteredData(): PopoverValue[] { + let filtered = this.popoverValues; - removeAllTypedChips() { - this.typedChips = []; - this.dataFiltered = this.getFilteredData(this.typedChips); - this.inputError = ""; - } + // Apply radio filter + if (this.selectedFilter) { + filtered = filtered.filter(item => item.status === this.selectedFilter); + } - getFilteredData(typedChips: string[]) { - if (typedChips.length === 0) { - return this.data; - } - const filteredData = this.data.filter((item) => - typedChips.every((chip) => this.checkNested(item, chip)), - ); - return filteredData; - } + // Apply chip filters (all chips must match) + if (this.typedChips.length > 0) { + filtered = filtered.filter(item => + this.typedChips.every(chip => + Object.values(item).some(val => + typeof val === 'string' && val.toLowerCase().includes(chip.toLowerCase()) + ) + ) + ); + } - checkNested(obj: object, chip: string): boolean { - return Object.values(obj).some((value) => - typeof value === "object" && value !== null - ? this.checkNested(value, chip) - : typeof value === "string" && value.toLowerCase().includes(chip.toLowerCase()), - ); - } -} + return filtered; + } + + handleInputChange(detail: GoabInputOnChangeDetail): void { + const newValue = detail.value.trim(); + this.inputValue = newValue; + } + + handleInputKeyPress(detail: GoabInputOnKeyPressDetail): void { + if (detail.key === "Enter") { + this.applyFilter(); + } + } + + applyFilter() { + if (this.inputValue === '') { + this.inputError = this.errorEmpty; + return; + } + if (this.typedChips.includes(this.inputValue)) { + this.inputError = this.errorDuplicate; + return; + } + this.typedChips = [...this.typedChips, this.inputValue]; + this.inputValue = ''; + this.inputError = ''; + } + + removeTypedChip(chip: string) { + this.typedChips = this.typedChips.filter(c => c !== chip); + this.inputError = ''; + } + + removeAllTypedChips() { + this.typedChips = []; + this.inputError = ''; + } + + radioGroupOnChange(event: GoabRadioGroupOnChangeDetail) { + this.selectedFilter = event.value; + } + + clearRadioFilter() { + this.selectedFilter = null; + } + + checkNested(obj: object, chip: string): boolean { + return Object.values(obj).some((value) => + typeof value === "object" && value !== null + ? this.checkNested(value, chip) + : typeof value === "string" && value.toLowerCase().includes(chip.toLowerCase()), + ); + } + } `} /> - - - - - Filter - - - - - - - Filter: - - - - Clear all - - - - - - - Status - Name - ID Number - - - - - - - - {{ item.name }} - {{ item.id }} - - - - - - No results found - + + + + + +
+ + + + + + + +
+
+ + Filter + +
+
+ + Filter + +
+
+ + + + Filter: + + + + + Clear all + + + + + + + Status + Text + Number + + + + + + + + Lorem ipsum + {{ u.key }} + + + + + + No results found + `} /> ([]); - const [inputValue, setInputValue] = useState(""); - const [inputError, setInputError] = useState(""); - const errorEmpty = "Empty filter"; - const errorDuplicate = "Enter a unique filter"; - const data = useMemo( - () => [ - { - status: { type: "information" as GoabBadgeType, text: "In progress" }, - name: "Ivan Schmidt", - id: "7838576954", - }, - { - status: { type: "success" as GoabBadgeType, text: "Completed" }, - name: "Luz Lakin", - id: "8576953364", - }, - { - status: { type: "information" as GoabBadgeType, text: "In progress" }, - name: "Keith McGlynn", - id: "9846041345", - }, - { - status: { type: "success" as GoabBadgeType, text: "Completed" }, - name: "Melody Frami", - id: "7385256175", - }, - { - status: { type: "important" as GoabBadgeType, text: "Updated" }, - name: "Frederick Skiles", - id: "5807570418", - }, - { - status: { type: "success" as GoabBadgeType, text: "Completed" }, - name: "Dana Pfannerstill", - id: "5736306857", - }, - ], - [] - ); - const [dataFiltered, setDataFiltered] = useState(data); + const [selectedFilter, setSelectedFilter] = useState(null); + const [typedChips, setTypedChips] = useState([]); + const [inputValue, setInputValue] = useState(""); + const [inputError, setInputError] = useState(""); + const errorEmpty = "Empty filter"; + const errorDuplicate = "Enter a unique filter"; + const data = useMemo( + () => [ + { + status: { type: "information" as GoabBadgeType, text: "In progress" }, + name: "Ivan Schmidt", + id: "7838576954", + }, + { + status: { type: "success" as GoabBadgeType, text: "Completed" }, + name: "Luz Lakin", + id: "8576953364", + }, + { + status: { type: "information" as GoabBadgeType, text: "In progress" }, + name: "Keith McGlynn", + id: "9846041345", + }, + { + status: { type: "success" as GoabBadgeType, text: "Completed" }, + name: "Melody Frami", + id: "7385256175", + }, + { + status: { type: "important" as GoabBadgeType, text: "Updated" }, + name: "Frederick Skiles", + id: "5807570418", + }, + { + status: { type: "success" as GoabBadgeType, text: "Completed" }, + name: "Dana Pfannerstill", + id: "5736306857", + }, + ], + [] + ); + const [dataFiltered, setDataFiltered] = useState(data); - const handleInputChange = (detail: GoabInputOnChangeDetail) => { - const newValue = detail.value.trim(); - setInputValue(newValue); - }; + const target = ( + + Filter + + ); - const handleInputKeyPress = (detail: GoabInputOnKeyPressDetail) => { - if (detail.key === "Enter") { - applyFilter(); - } - }; + const handleInputChange = (detail: GoabInputOnChangeDetail) => { + const newValue = detail.value.trim(); + setInputValue(newValue); + }; - const applyFilter = () => { - if (inputValue === "") { - setInputError(errorEmpty); - return; - } - if (typedChips.length > 0 && typedChips.includes(inputValue)) { - setInputError(errorDuplicate); - return; - } - setTypedChips([...typedChips, inputValue]); - setTimeout(() => { - setInputValue(""); - }, 0); - setInputError(""); - }; + const handleInputKeyPress = (detail: GoabInputOnKeyPressDetail) => { + if (detail.key === "Enter") { + applyFilter(); + } + }; - const removeTypedChip = (chip: string) => { - setTypedChips(typedChips.filter(c => c !== chip)); - setInputError(""); - }; + const applyFilter = () => { + if (inputValue === "") { + setInputError(errorEmpty); + return; + } + if (typedChips.length > 0 && typedChips.includes(inputValue)) { + setInputError(errorDuplicate); + return; + } + setTypedChips([...typedChips, inputValue]); + setTimeout(() => { + setInputValue(""); + }, 0); + setInputError(""); + }; - const checkNested = useCallback((obj: object, chip: string): boolean => { - return Object.values(obj).some(value => - typeof value === "object" && value !== null - ? checkNested(value, chip) - : typeof value === "string" && value.toLowerCase().includes(chip.toLowerCase()) - ); - }, []); + const removeTypedChip = (chip: string) => { + setTypedChips(typedChips.filter(c => c !== chip)); + setInputError(""); + }; - const getFilteredData = useCallback( - (typedChips: string[]) => { - if (typedChips.length === 0) { - return data; - } - const filteredData = data.filter((item: object) => - typedChips.every(chip => checkNested(item, chip)) - ); + const checkNested = useCallback((obj: object, chip: string): boolean => { + return Object.values(obj).some(value => + typeof value === "object" && value !== null + ? checkNested(value, chip) + : typeof value === "string" && value.toLowerCase().includes(chip.toLowerCase()) + ); + }, []); - return filteredData; - }, - [checkNested, data] - ); + function radioGroupOnChange(event: GoabRadioGroupOnChangeDetail) { + setSelectedFilter(event.value); + } - useEffect(() => { - setDataFiltered(getFilteredData(typedChips)); - }, [getFilteredData, typedChips]); + const getFilteredData = useCallback( + (typedChips: string[], selectedFilter: string | null) => { + let filteredData = data; + + if (typedChips.length > 0) { + filteredData = filteredData.filter((item: any) => + typedChips.every(chip => checkNested(item, chip)) + ); + } + + if (selectedFilter) { + filteredData = filteredData.filter( + (item: any) => item.status && item.status.text === selectedFilter + ); + } + + return filteredData; + }, + [checkNested, data] + ); + + + useEffect(() => { + setDataFiltered(getFilteredData(typedChips, selectedFilter)); + }, [getFilteredData, typedChips, selectedFilter]); `} /> @@ -838,72 +1022,96 @@ export class TableComponent { tags="react" allowCopy={true} code={` - <> - - - - - Filter - - - - - {typedChips.length > 0 && ( -
- - Filter: - - {typedChips.length > 0 && - typedChips.map((typedChip, index) => ( - removeTypedChip(typedChip)} - /> - ))} - setTypedChips([])}> - Clear all - -
- )} + + + + + + +
+ + + + + + + +
+
+
+
- - - - Status - Name - ID Number - - - - {dataFiltered.map(item => ( - - - - - {item.name} - {item.id} - - ))} - - + {(typedChips.length > 0 || selectedFilter) && ( +
+ + Filter: + + {typedChips.map((typedChip, index) => ( + removeTypedChip(typedChip)} + /> + ))} + {selectedFilter && ( + { + setSelectedFilter(null); + }} + /> + )} + {(typedChips.length > 0 || selectedFilter) && ( + { + setTypedChips([]); + setSelectedFilter(null); + }}> + Clear all + + )} +
+ )} - {dataFiltered.length === 0 && data.length > 0 && ( - No results found - )} - + + + + Status + Name + ID Number + + + + {dataFiltered.map(item => ( + + + + + {item.name} + {item.id} + + ))} + + + + {dataFiltered.length === 0 && data.length > 0 && ( + + No results found + + )} +
`} /> diff --git a/src/examples/header-logged-in-menu.tsx b/src/examples/header-logged-in-menu.tsx new file mode 100644 index 000000000..fa8bea433 --- /dev/null +++ b/src/examples/header-logged-in-menu.tsx @@ -0,0 +1,126 @@ +import { Sandbox } from "@components/sandbox"; +import { CodeSnippet } from "@components/code-snippet/CodeSnippet.tsx"; +import { GoabAppHeader, GoabAppHeaderMenu, GoabMicrositeHeader } from "@abgov/react-components"; +import { useContext } from "react"; +import { LanguageVersionContext } from "@contexts/LanguageVersionContext.tsx"; + +export const HeaderLoggedInMenu = () => { + const {version} = useContext(LanguageVersionContext); + return ( + + + + Services + + Manage account + Request new staff account + System admin + Sign out + + + + {version === "old" && ( + + + Services + + Manage account + Request new staff account + System admin + Sign out + + + `} + /> + )} + + {version === "new" && ( + + + Services + + Manage account + Request new staff account + System admin + Sign out + + + `} + /> + )} + + {version === "old" && ( + + + Services + + Manage account + Request new staff account + System admin + Sign out + + + `} + /> + )} + {version === "new" && ( + + + Services + + Manage account + Request new staff account + System admin + Sign out + + + `} + /> + )} + + ) +} + +export default HeaderLoggedInMenu; diff --git a/src/examples/header-with-sign-in.tsx b/src/examples/header-with-sign-in.tsx new file mode 100644 index 000000000..db671ea77 --- /dev/null +++ b/src/examples/header-with-sign-in.tsx @@ -0,0 +1,94 @@ +import { Sandbox } from "@components/sandbox"; +import { CodeSnippet } from "@components/code-snippet/CodeSnippet.tsx"; +import { GoabAppHeader, GoabMicrositeHeader } from "@abgov/react-components"; +import { useContext } from "react"; +import { LanguageVersionContext } from "@contexts/LanguageVersionContext.tsx"; + +export const HeaderSignIn = () => { + const {version} = useContext(LanguageVersionContext); + return ( + + + + Sign in + + + {version === "old" && ( + + + Sign in + + `} + /> + )} + + {version === "new" && ( + + + Sign in + + `} + /> + )} + + {version === "old" && ( + + + Sign in + + `} + /> + )} + {version === "new" && ( + + + Sign in + + `} + /> + )} + + ) +} + +export default HeaderSignIn; diff --git a/src/examples/tables/TablesExamples.tsx b/src/examples/tables/TablesExamples.tsx index 6bdf55899..50bbb70f9 100644 --- a/src/examples/tables/TablesExamples.tsx +++ b/src/examples/tables/TablesExamples.tsx @@ -1,28 +1,29 @@ import SortDataInATable from "@examples/sort-data-in-a-table.tsx"; -import DisplayNumbersInATableSoTheyCanBeScannedEasily from "@examples/display-numbers-in-a-table-so-they-can-be-scanned-easily.tsx"; +import ZebraStripesInATable from "@examples/zebra-stripes-in-a-table.tsx"; import FilterDataInATable from "@examples/filter-data-in-a-table.tsx"; import { SandboxHeader } from "@components/sandbox/sandbox-header/sandboxHeader.tsx"; +import './tables-page-examples.css'; export const TablesExamples = () => { return ( <> + exampleTitle="Table with filters" + figmaExample="https://www.figma.com/design/aIRjvBzpIUH0GbkffjbL04/%E2%9D%96-Patterns-library-%7C-DDD?node-id=7104-1626357&t=WrSJODVw0mryQrrA-4"> - + + exampleTitle="Table with zebra stripes" + figmaExample=""> - + + exampleTitle="Table with sortable columns" + figmaExample="https://www.figma.com/design/aIRjvBzpIUH0GbkffjbL04/%E2%9D%96-Patterns-library-%7C-DDD?node-id=6312-97462&t=X0IQW5flDDaj8Vyg-4"> - + ); }; diff --git a/src/examples/tables/tables-page-examples.css b/src/examples/tables/tables-page-examples.css new file mode 100644 index 000000000..d34b17df1 --- /dev/null +++ b/src/examples/tables/tables-page-examples.css @@ -0,0 +1,3 @@ +.goa-table-zebra-stripes > tr:nth-child(even) { + background-color: var(--goa-color-greyscale-50); +} diff --git a/src/examples/zebra-stripes-in-a-table.tsx b/src/examples/zebra-stripes-in-a-table.tsx new file mode 100644 index 000000000..806c602f7 --- /dev/null +++ b/src/examples/zebra-stripes-in-a-table.tsx @@ -0,0 +1,88 @@ +import { + GoabButton, + GoabBadge, + GoabTable +} from "@abgov/react-components"; +import type { + GoabBadgeType, +} from "@abgov/ui-components-common"; +import { Sandbox } from "@components/sandbox"; +import { CodeSnippet } from "@components/code-snippet/CodeSnippet.tsx"; + +export const ZebraStripesInATable = () => { + const filteredData = [ + { + status: { type: "information" as GoabBadgeType, text: "In progress" }, + name: "Ivan Schmidt", + id: "76954", + }, + { + status: { type: "success" as GoabBadgeType, text: "Completed" }, + name: "Luz Lakin", + id: "53364", + }, + { + status: { type: "information" as GoabBadgeType, text: "In progress" }, + name: "Keith McGlynn", + id: "41345", + }, + { + status: { type: "success" as GoabBadgeType, text: "Completed" }, + name: "Melody Frami", + id: "56175", + }, + { + status: { type: "important" as GoabBadgeType, text: "Updated" }, + name: "Frederick Skiles", + id: "70418", + }, + { + status: { type: "success" as GoabBadgeType, text: "Completed" }, + name: "Dana Pfannerstill", + id: "06857", + }, + ]; + + return ( + <> + + {/*CSS Code Snippet*/} + tr:nth-child(even) { + background-color: var(--goa-color-greyscale-50); + } + `} + /> + + + + Status + Assigned to + Number + Actions + + + + {filteredData.map((item) => ( + + + + + {item.name} + {item.id} + + Action + + + ))} + + + + + ); +}; + +export default ZebraStripesInATable; diff --git a/src/global-constants.ts b/src/global-constants.ts index a5c6ac850..79fbc368e 100644 --- a/src/global-constants.ts +++ b/src/global-constants.ts @@ -1,5 +1,5 @@ export const MAX_CONTENT_WIDTH = "1440px"; -export const DEFAULT_VERSION = "old"; +export const DEFAULT_VERSION = "new"; export const DEFAULT_LANGUAGE = "react"; // Array of 'New' components diff --git a/src/routes/components/AppHeader.tsx b/src/routes/components/AppHeader.tsx index dcb0ea00c..0818fb92d 100644 --- a/src/routes/components/AppHeader.tsx +++ b/src/routes/components/AppHeader.tsx @@ -231,7 +231,7 @@ export default function AppHeaderPage() { heading={ <> Examples - + } > diff --git a/src/routes/components/Dropdown.tsx b/src/routes/components/Dropdown.tsx index af111a26a..da729e00e 100644 --- a/src/routes/components/Dropdown.tsx +++ b/src/routes/components/Dropdown.tsx @@ -1,4 +1,4 @@ -import { useContext, useState } from "react"; +import { useContext, useEffect, useState } from "react"; import { GoabBadge, GoabDropdown, @@ -81,16 +81,43 @@ export default function DropdownPage() { name: "width", value: "", }, + { + label: "Max width", + type: "string", + name: "maxWidth", + value: "", + hidden: version === "old", // Hide in LTS (old) version + }, { label: "Disabled", type: "boolean", name: "disabled", value: false }, { label: "Error", type: "boolean", name: "error", value: false }, { label: "Native", type: "boolean", name: "native", value: false }, { label: "Filterable", type: "boolean", name: "filterable", value: false }, { label: "ARIA label", type: "string", name: "ariaLabel", value: "" }, ]); + const { formItemBindings, formItemProps, onFormItemChange } = useSandboxFormItem({ label: "Basic dropdown", }); + // Hide the maxWidth control and remove the prop when LTS version is selected + useEffect(() => { + setDropdownBindings(prev => + prev.map(b => { + if (b.name === "maxWidth" && b.type === "string") { + return { ...b, hidden: version === "old", value: version === "old" ? "" : b.value }; + } + return b; + }) + ); + + if (version === "old") { + setDropdownProps(prev => { + const { maxWidth, ...rest } = prev as unknown as Record; + return rest as ComponentPropsType; + }); + } + }, [version]); + const oldDropdownProperties: ComponentProperty[] = [ { name: "name", @@ -252,6 +279,11 @@ export default function DropdownPage() { type: "string", description: "Overrides the autosized menu width. Non-native only.", }, + { + name: "maxWidth", + type: "string", + description: "Maximum width of the dropdown. Non-native only.", + }, { name: "disabled", type: "boolean", diff --git a/src/routes/components/MenuButton.tsx b/src/routes/components/MenuButton.tsx index 199c44601..70218b81e 100644 --- a/src/routes/components/MenuButton.tsx +++ b/src/routes/components/MenuButton.tsx @@ -7,6 +7,7 @@ import { GoabTab, GoabTabs, } from "@abgov/react-components"; +import { GoabMenuButtonOnActionDetail } from "@abgov/ui-components-common"; import { Category, ComponentHeader } from "@components/component-header/ComponentHeader.tsx"; import { ComponentContent } from "@components/component-content/ComponentContent"; import { @@ -82,7 +83,7 @@ export default function MenuButtonPage() { TestIdProperty, { name: "onAction", - type: "(action: string) => void", + type: "(detail: GoabMenuButtonOnActionDetail) => void", description: "Callback fired when a menu action is selected. Receives the action identifier from the clicked item.", }, @@ -118,8 +119,8 @@ export default function MenuButtonPage() { setMenuButtonProps(props as CastingType); } - function handleAction(action: string) { - console.log("Last action: ", action); + function handleAction(detail: GoabMenuButtonOnActionDetail) { + console.log("Last action: ", detail.action); } return ( diff --git a/src/routes/components/TextArea.tsx b/src/routes/components/TextArea.tsx index f737f4eea..3d18a5eb2 100644 --- a/src/routes/components/TextArea.tsx +++ b/src/routes/components/TextArea.tsx @@ -1,4 +1,4 @@ -import { useContext, useState } from "react"; +import { useContext, useEffect, useState } from "react"; import { ComponentBinding, Sandbox } from "@components/sandbox"; import { ComponentProperties, @@ -88,6 +88,13 @@ export default function TextAreaPage() { name: "maxCount", type: "number", }, + { + label: "Max width", + name: "maxWidth", + type: "string", + value: "", + hidden: version === "old", // Hide in LTS (old) version + }, { name: "value", type: "string", @@ -97,10 +104,30 @@ export default function TextAreaPage() { label: "Value", }, ]); + const { formItemBindings, formItemProps, onFormItemChange } = useSandboxFormItem({ label: "Basic", }); + // Hide the maxWidth control and remove the prop when LTS version is selected + useEffect(() => { + setTextAreaBindings(prev => + prev.map(b => { + if (b.name === "maxWidth" && b.type === "string") { + return { ...b, hidden: version === "old", value: version === "old" ? "" : b.value }; + } + return b; + }) + ); + + if (version === "old") { + setComponentProps(prev => { + const { maxWidth, ...rest } = prev as unknown as Record; + return rest as CastingType; + }); + } + }, [version]); + const oldComponentProperties: ComponentProperty[] = [ { name: "name", @@ -328,6 +355,11 @@ export default function TextAreaPage() { type: "(event: GoabTextAreaOnKeyPressDetail) => void", description: "Function invoked when a key is pressed", }, + { + name: "onBlur", + type: "(event: GoabTextAreaOnBlurDetail) => void", + description: "Callback when the textarea loses focus", + }, MarginProperty, ]; diff --git a/src/routes/components/Tooltip.tsx b/src/routes/components/Tooltip.tsx index 8a1997568..1b1f35838 100644 --- a/src/routes/components/Tooltip.tsx +++ b/src/routes/components/Tooltip.tsx @@ -1,6 +1,6 @@ import { Category, ComponentHeader } from "@components/component-header/ComponentHeader.tsx"; import { ComponentBinding, Sandbox } from "@components/sandbox"; -import { useState } from "react"; +import { useContext, useEffect, useState } from "react"; import { ComponentProperties, ComponentProperty, @@ -23,6 +23,7 @@ import { import { TooltipExamples } from "@examples/tooltip/TooltipExamples.tsx"; import { DesignEmpty } from "@components/empty-states/design-empty/DesignEmpty.tsx"; import { AccessibilityEmpty } from "@components/empty-states/accessibility-empty/AccessibilityEmpty.tsx"; +import { LanguageVersionContext } from "@contexts/LanguageVersionContext.tsx"; // == Page props == @@ -42,6 +43,7 @@ type CastingType = { }; export default function TooltipPage() { + const { version } = useContext(LanguageVersionContext); const [componentProps, setComponentProps] = useState({ content: "Tooltip", @@ -68,8 +70,36 @@ export default function TooltipPage() { options: ["left", "center", "right"], value: "", }, + { + label: "Max width", + type: "string", + name: "maxWidth", + value: "", + hidden: version === "old", // ensure hidden on initial render when LTS selected + }, ]); + // Hide the maxWidth control and remove the prop when LTS version is selected + useEffect(() => { + // Toggle the visibility of the Max width binding + setComponentBindings(prev => + prev.map(b => { + if (b.name === "maxWidth" && b.type === "string") { + return { ...b, hidden: version === "old", value: version === "old" ? "" : b.value }; + } + return b; + }) + ); + + // Ensure the component props do not include maxWidth when LTS is selected + if (version === "old") { + setComponentProps(prev => { + const { maxWidth, ...rest } = prev as Record; + return rest as ComponentPropsType; + }); + } + }, [version]); + const oldComponentProperties: ComponentProperty[] = [ { name: "content", @@ -122,6 +152,11 @@ export default function TooltipPage() { description: "Horizontal alignment to the child element", defaultValue: "center", }, + { + name: "maxWidth", + type: "string", + description: "Maximum width of the tooltip", + }, TestIdProperty, MarginProperty ]; @@ -143,7 +178,6 @@ export default function TooltipPage() { /> -

Playground