From 42fc1a14673f7b87b0ed7565df7dca693f133ae4 Mon Sep 17 00:00:00 2001 From: Siddharth Kshetrapal Date: Thu, 25 Jan 2024 01:04:24 +0530 Subject: [PATCH 01/10] simplify mock data --- src/drafts/SelectPanel2/stories/mock-data.ts | 69 -------------------- 1 file changed, 69 deletions(-) diff --git a/src/drafts/SelectPanel2/stories/mock-data.ts b/src/drafts/SelectPanel2/stories/mock-data.ts index 75f86fd3c77..e10ad1f868d 100644 --- a/src/drafts/SelectPanel2/stories/mock-data.ts +++ b/src/drafts/SelectPanel2/stories/mock-data.ts @@ -50,315 +50,246 @@ const data = { id: 'MDU6TGFiZWw4Mzk2MzgxMTU=', name: 'bug', description: "Something isn't working", - createdAt: '2018-02-17T00:09:05Z', }, { color: 'cfd3d7', id: 'MDU6TGFiZWw4Mzk2MzgxMTY=', name: 'duplicate', description: 'This issue or pull request already exists', - createdAt: '2018-02-17T00:09:05Z', }, { color: 'a2eeef', id: 'MDU6TGFiZWw4Mzk2MzgxMTc=', name: 'enhancement', description: 'New feature or request', - createdAt: '2018-02-17T00:09:05Z', }, { color: 'd876e3', id: 'MDU6TGFiZWw4Mzk2MzgxMjE=', name: 'futher info needed', description: 'Further information is requested', - createdAt: '2018-02-17T00:09:05Z', }, { color: '7057ff', id: 'MDU6TGFiZWw4Mzk2MzgxMTk=', name: 'good first issue', description: 'Good for newcomers', - createdAt: '2018-02-17T00:09:05Z', }, { color: '008672', id: 'MDU6TGFiZWw4Mzk2MzgxMTg=', name: 'up for grabs', description: "anyone can take on this work, it's ready to go", - createdAt: '2018-02-17T00:09:05Z', }, { color: 'ffffff', id: 'MDU6TGFiZWw4Mzk2MzgxMjI=', name: 'wontfix', description: 'This will not be worked on', - createdAt: '2018-02-17T00:09:05Z', }, { color: 'FFD2ED', id: 'MDU6TGFiZWw4ODkxNjUwNzU=', name: '💓collab', description: 'a vibrant hub of collaboration', - createdAt: '2018-04-03T21:03:53Z', }, { color: '9eea90', id: 'MDU6TGFiZWw5NDAyMzcyOTU=', name: 'status: wip', - description: '', - createdAt: '2018-05-21T19:11:15Z', }, { color: 'eae658', id: 'MDU6TGFiZWw5NDAyMzc1NzY=', name: 'status: review needed', - description: '', - createdAt: '2018-05-21T19:11:36Z', }, { color: 'a9abe8', id: 'MDU6TGFiZWw5NDA1MDc5NzQ=', name: 'type: discussion', - description: '', - createdAt: '2018-05-22T02:06:49Z', }, { color: '86181d', id: 'MDU6TGFiZWw5NDg2NDM1MzI=', name: '🚧 blocked', description: 'Someone or something is preventing this from moving forward', - createdAt: '2018-05-29T22:02:54Z', }, { color: '1d76db', id: 'MDU6TGFiZWwxMDI2NDE1MTYw', name: 'docs', description: 'Documentation', - createdAt: '2018-08-16T16:35:43Z', }, { color: 'fcc0dc', id: 'MDU6TGFiZWwxMDU3MjcwNzA4', name: 'patch release', description: 'bug fixes, docs, housekeeping', - createdAt: '2018-09-14T20:27:34Z', }, { color: 'ff73b4', id: 'MDU6TGFiZWwxMDU3MjcxMDUz', name: 'minor release', description: 'new features', - createdAt: '2018-09-14T20:28:01Z', }, { color: 'e3006a', id: 'MDU6TGFiZWwxMDU3MjcxNDE5', name: 'major release', description: 'breaking changes', - createdAt: '2018-09-14T20:28:30Z', }, { color: '107d93', id: 'MDU6TGFiZWwxMDU3MjczMjkz', name: 'deployment', - description: '', - createdAt: '2018-09-14T20:31:10Z', }, { color: 'd4c5f9', id: 'MDU6TGFiZWwxMzgyNTYyNjQ0', name: 'accessibility', - description: '', - createdAt: '2019-05-29T19:56:35Z', }, { color: '0366d6', id: 'MDU6TGFiZWwxNjE2MTQ5MDE2', name: 'dependencies', description: 'Pull requests that update a dependency file', - createdAt: '2019-10-14T18:19:49Z', }, { color: 'e1e4e8', id: 'MDU6TGFiZWwxNjU3MjI0NTcw', name: 'fr-skip', description: 'Remove this from the Design Systems first responder list', - createdAt: '2019-11-04T18:18:03Z', }, { color: 'fcba03', id: 'MDU6TGFiZWwxNzkwMDY4Mzk5', name: 'developer experience', - description: '', - createdAt: '2020-01-15T22:30:35Z', }, { color: '68f9cc', id: 'MDU6TGFiZWwxNzkwMDY4NDg1', name: 'contributor experience', - description: '', - createdAt: '2020-01-15T22:30:41Z', }, { color: 'a1b220', id: 'MDU6TGFiZWwxNzkwMDcyODY5', name: 'API', - description: '', - createdAt: '2020-01-15T22:34:09Z', }, { color: '6494f4', id: 'MDU6TGFiZWwxNzkxNjM5MzM1', name: 'new component', - description: '', - createdAt: '2020-01-16T16:13:35Z', }, { color: 'f48b96', id: 'MDU6TGFiZWwxOTg5MDAwNjk0', name: 'experimental', - description: '', - createdAt: '2020-04-15T20:52:13Z', }, { color: 'db9360', id: 'MDU6TGFiZWwxOTkzNTcwNzUx', name: 'design', - description: '', - createdAt: '2020-04-17T15:08:45Z', }, { color: 'F9D0C4', id: 'MDU6TGFiZWwyNjkzNTE0OTQw', name: 'coverage', - description: '', - createdAt: '2021-01-27T22:25:23Z', }, { color: '077CA4', id: 'MDU6TGFiZWwyNjkzNTE5NTc1', name: 'epic', - description: '', - createdAt: '2021-01-27T22:27:57Z', }, { color: 'F9D0C4', id: 'MDU6TGFiZWwyNjkzNTMwODUy', name: 'behaviors', - description: '', - createdAt: '2021-01-27T22:34:49Z', }, { color: '1d76db', id: 'MDU6TGFiZWwyNzkzMjYwMTgz', name: 'typescript', - description: '', - createdAt: '2021-03-04T20:17:59Z', }, { color: 'DBEDFF', id: 'MDU6TGFiZWwzMzA5MjY0Nzcz', name: 'size: sand', - description: '', - createdAt: '2021-08-31T02:08:34Z', }, { color: 'DBEDFF', id: 'MDU6TGFiZWwzMzA5MjY1MDM2', name: 'size: pebble', - description: '', - createdAt: '2021-08-31T02:08:42Z', }, { color: 'DBEDFF', id: 'MDU6TGFiZWwzMzA5MjY1MTc4', name: 'size: rock', - description: '', - createdAt: '2021-08-31T02:08:49Z', }, { color: 'DBEDFF', id: 'MDU6TGFiZWwzMzA5MjY1NTg3', name: 'size: boulder', - description: '', - createdAt: '2021-08-31T02:09:00Z', }, { color: 'ededed', id: 'MDU6TGFiZWwzMzE4NjA1ODgy', name: 'Stale', description: null, - createdAt: '2021-09-02T22:04:15Z', }, { color: '0052CC', id: 'LA_kwDOB0K8ws7Oq_eD', name: 'react', - description: '', - createdAt: '2021-10-19T16:48:08Z', }, { color: 'eeeeee', id: 'LA_kwDOB0K8ws7O4h4u', name: 'skip changeset', - description: '', - createdAt: '2021-10-20T16:15:21Z', }, { color: 'D93F0B', id: 'LA_kwDOB0K8ws7WuYLd', name: 'do not merge', - description: '', - createdAt: '2021-12-01T19:02:44Z', }, { color: 'e5534b', id: 'LA_kwDOB0K8ws73bq-W', name: 'needs triage', - description: '', - createdAt: '2022-05-20T13:41:47Z', }, { color: 'B60205', id: 'LA_kwDOB0K8ws8AAAABA3IfBw', name: 'a11y-eng-secondary', - description: '', - createdAt: '2022-07-22T10:04:29Z', }, { color: 'ffffff', id: 'LA_kwDOB0K8ws8AAAABBfG7EQ', name: 'support', description: 'Tasks where the team is supporting and helping other teams', - createdAt: '2022-08-04T15:33:56Z', }, { color: 'DDF4FF', id: 'LA_kwDOB0K8ws8AAAABG14Q1Q', name: 'component: tree view', description: 'Issues related to the TreeView component', - createdAt: '2022-11-02T18:49:40Z', }, { color: 'DDF4FF', id: 'LA_kwDOB0K8ws8AAAABHLXdxw', name: 'component: SplitPageLayout', description: 'Issues related to the PageLayout and SplitPageLayout components', - createdAt: '2022-11-08T18:57:47Z', }, { color: 'DDF4FF', id: 'LA_kwDOB0K8ws8AAAABHLXucw', name: 'component: nav list', description: 'Issues related to the NavList component', - createdAt: '2022-11-08T18:58:49Z', }, { color: 'DDF4FF', id: 'LA_kwDOB0K8ws8AAAABHLYCfQ', name: 'component: button', description: 'Issues related to the Button component', - createdAt: '2022-11-08T19:00:13Z', }, ], users: [ From 5ad8740dacb8bc1b32ae6565c3e0340819d29732 Mon Sep 17 00:00:00 2001 From: Siddharth Kshetrapal Date: Thu, 25 Jan 2024 01:05:16 +0530 Subject: [PATCH 02/10] dont shrink footer --- src/drafts/SelectPanel2/SelectPanel.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/drafts/SelectPanel2/SelectPanel.tsx b/src/drafts/SelectPanel2/SelectPanel.tsx index 82e122a4181..6e81fa5b02a 100644 --- a/src/drafts/SelectPanel2/SelectPanel.tsx +++ b/src/drafts/SelectPanel2/SelectPanel.tsx @@ -429,6 +429,7 @@ const SelectPanelFooter = ({...props}) => { Date: Thu, 25 Jan 2024 01:05:27 +0530 Subject: [PATCH 03/10] prepare empty message for button --- src/drafts/SelectPanel2/SelectPanel.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/drafts/SelectPanel2/SelectPanel.tsx b/src/drafts/SelectPanel2/SelectPanel.tsx index 6e81fa5b02a..a13d0ade603 100644 --- a/src/drafts/SelectPanel2/SelectPanel.tsx +++ b/src/drafts/SelectPanel2/SelectPanel.tsx @@ -557,7 +557,7 @@ const SelectPanelMessage: React.FC = ({ flexGrow: 1, height: '100%', gap: 1, - paddingX: 4, + paddingX: 3, textAlign: 'center', a: {color: 'inherit', textDecoration: 'underline'}, }} @@ -566,7 +566,11 @@ const SelectPanelMessage: React.FC = ({ ) : null} {title} - {children} + + {children} + ) } else { From c519449eb46417bcf00bc5bcc83768815d8ca8d5 Mon Sep 17 00:00:00 2001 From: Siddharth Kshetrapal Date: Thu, 25 Jan 2024 01:05:53 +0530 Subject: [PATCH 04/10] possible mock for create new label --- .../stories/SelectPanel.examples.stories.tsx | 137 +++++++++++++++++- 1 file changed, 136 insertions(+), 1 deletion(-) diff --git a/src/drafts/SelectPanel2/stories/SelectPanel.examples.stories.tsx b/src/drafts/SelectPanel2/stories/SelectPanel.examples.stories.tsx index 3eaf75e9060..90c330ef7e1 100644 --- a/src/drafts/SelectPanel2/stories/SelectPanel.examples.stories.tsx +++ b/src/drafts/SelectPanel2/stories/SelectPanel.examples.stories.tsx @@ -1,7 +1,14 @@ import React from 'react' import {SelectPanel} from '../SelectPanel' import {ActionList, ActionMenu, Avatar, Box, Button, Text} from '../../../index' -import {ArrowRightIcon, EyeIcon, GitBranchIcon, TriangleDownIcon, GearIcon} from '@primer/octicons-react' +import { + ArrowRightIcon, + EyeIcon, + GitBranchIcon, + TriangleDownIcon, + GearIcon, + PlusCircleIcon, +} from '@primer/octicons-react' import data from './mock-data' export default { @@ -640,6 +647,134 @@ export const ShortSelectPanel = () => { ) } +export const CreateNewRow = () => { + const initialSelectedLabels = data.issue.labelIds // mock initial state: has selected labels + const [selectedLabelIds, setSelectedLabelIds] = React.useState(initialSelectedLabels) + + /* Selection */ + const onLabelSelect = (labelId: string) => { + if (!selectedLabelIds.includes(labelId)) setSelectedLabelIds([...selectedLabelIds, labelId]) + else setSelectedLabelIds(selectedLabelIds.filter(id => id !== labelId)) + } + const onClearSelection = () => { + setSelectedLabelIds([]) + } + + const onSubmit = () => { + data.issue.labelIds = selectedLabelIds // pretending to persist changes + } + + /* Filtering */ + const [filteredLabels, setFilteredLabels] = React.useState(data.labels) + const [query, setQuery] = React.useState('') + + const onSearchInputChange: React.ChangeEventHandler = event => { + const query = event.currentTarget.value + setQuery(query) + + if (query === '') setFilteredLabels(data.labels) + else { + setFilteredLabels( + data.labels + .map(label => { + if (label.name.toLowerCase().startsWith(query)) return {priority: 1, label} + else if (label.name.toLowerCase().includes(query)) return {priority: 2, label} + else if (label.description?.toLowerCase().includes(query)) return {priority: 3, label} + else return {priority: -1, label} + }) + .filter(result => result.priority > 0) + .map(result => result.label), + ) + } + } + + const sortingFn = (itemA: {id: string}, itemB: {id: string}) => { + const initialSelectedIds = data.issue.labelIds + if (initialSelectedIds.includes(itemA.id) && initialSelectedIds.includes(itemB.id)) return 1 + else if (initialSelectedIds.includes(itemA.id)) return -1 + else if (initialSelectedIds.includes(itemB.id)) return 1 + else return 1 + } + + const itemsToShow = query ? filteredLabels : data.labels.sort(sortingFn) + + const createNewLabel = (name: string) => { + const id = `new-${name}` + + // pretending to persist changes + data.labels.unshift({id, name, color: 'fcba03'}) + + setQuery('') // clear search input + onLabelSelect(id) // select newly created label + } + + return ( + <> + { + /* optional callback, for example: for multi-step overlay or to fire sync actions */ + // eslint-disable-next-line no-console + console.log('panel was closed') + }} + onClearSelection={onClearSelection} + > + Assign label + + + + + + {itemsToShow.length === 0 ? ( + + Select the button below to create this label + + + ) : ( + <> + + {itemsToShow.map(label => ( + onLabelSelect(label.id)} + selected={selectedLabelIds.includes(label.id)} + > + + + + {label.name} + {label.description} + + ))} + + {query && ( + + + + )} + + )} + + + Edit labels + + + + ) +} + // ----- Suspense implementation details ---- const cache = new Map() From 14ff8658fa719617f6911d06fc2b5873737bad1c Mon Sep 17 00:00:00 2001 From: Siddharth Kshetrapal Date: Thu, 25 Jan 2024 01:32:56 +0530 Subject: [PATCH 05/10] include button in focus zone --- src/drafts/SelectPanel2/SelectPanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/drafts/SelectPanel2/SelectPanel.tsx b/src/drafts/SelectPanel2/SelectPanel.tsx index a13d0ade603..96fe3688ea3 100644 --- a/src/drafts/SelectPanel2/SelectPanel.tsx +++ b/src/drafts/SelectPanel2/SelectPanel.tsx @@ -156,7 +156,7 @@ const Panel: React.FC = ({ const {containerRef: listContainerRef} = useFocusZone( { bindKeys: FocusKeys.ArrowVertical | FocusKeys.HomeAndEnd | FocusKeys.PageUpDown, - focusableElementFilter: element => element.tagName === 'LI', + focusableElementFilter: element => element.tagName === 'LI' || element.tagName === 'BUTTON', }, [internalOpen], ) From acf99576e493588bdd9620a22f40c0354300c7dc Mon Sep 17 00:00:00 2001 From: Siddharth Kshetrapal Date: Thu, 25 Jan 2024 03:17:29 +0530 Subject: [PATCH 06/10] use controlled state for Panel --- src/drafts/SelectPanel2/SelectPanel.tsx | 2 +- .../stories/SelectPanel.examples.stories.tsx | 85 ++++++++++++++++--- 2 files changed, 72 insertions(+), 15 deletions(-) diff --git a/src/drafts/SelectPanel2/SelectPanel.tsx b/src/drafts/SelectPanel2/SelectPanel.tsx index 96fe3688ea3..7fb2fd42974 100644 --- a/src/drafts/SelectPanel2/SelectPanel.tsx +++ b/src/drafts/SelectPanel2/SelectPanel.tsx @@ -112,7 +112,7 @@ const Panel: React.FC = ({ Anchor = React.cloneElement(child, { // @ts-ignore TODO ref: anchorRef, - onClick: onAnchorClick, + onClick: child.props.onClick || onAnchorClick, 'aria-haspopup': true, 'aria-expanded': internalOpen, }) diff --git a/src/drafts/SelectPanel2/stories/SelectPanel.examples.stories.tsx b/src/drafts/SelectPanel2/stories/SelectPanel.examples.stories.tsx index 90c330ef7e1..ea301a31d9e 100644 --- a/src/drafts/SelectPanel2/stories/SelectPanel.examples.stories.tsx +++ b/src/drafts/SelectPanel2/stories/SelectPanel.examples.stories.tsx @@ -1,6 +1,7 @@ -import React from 'react' +import React, {FormEvent} from 'react' import {SelectPanel} from '../SelectPanel' -import {ActionList, ActionMenu, Avatar, Box, Button, Text} from '../../../index' +import {ActionList, ActionMenu, Avatar, Box, Button, Flash, FormControl, Text, TextInput} from '../../../index' +import {Dialog} from '../../../drafts' import { ArrowRightIcon, EyeIcon, @@ -698,29 +699,53 @@ export const CreateNewRow = () => { const itemsToShow = query ? filteredLabels : data.labels.sort(sortingFn) - const createNewLabel = (name: string) => { - const id = `new-${name}` + /* + Controlled state + Create new label Dialog + We only have to do this until https://github.com/primer/react/pull/3840 is merged + */ + const [panelOpen, setPanelOpen] = React.useState(false) + const [dialogOpen, setDialogOpen] = React.useState(false) + + const openCreateLabelDialog = () => { + setPanelOpen(false) + setDialogOpen(true) + } + + const onDialogSubmit = (event: FormEvent) => { + event.preventDefault() + + const formData = new FormData(event.target as HTMLFormElement) + const {name, color, description} = Object.fromEntries(formData) as Record // pretending to persist changes - data.labels.unshift({id, name, color: 'fcba03'}) + const id = `new-${name}` + data.labels.unshift({id, name, color, description}) + + setDialogOpen(false) + setPanelOpen(true) setQuery('') // clear search input onLabelSelect(id) // select newly created label } + const formSubmitRef = React.useRef(null) + return ( <> +

Create new item from panel

+ + Note this example is not yet ready, do not copy it. It is blocked by{' '} + primer/react/pull/3840 + + { - /* optional callback, for example: for multi-step overlay or to fire sync actions */ - // eslint-disable-next-line no-console - console.log('panel was closed') - }} + onCancel={() => setPanelOpen(false)} onClearSelection={onClearSelection} > - Assign label + setPanelOpen(true)}>Assign label @@ -729,7 +754,7 @@ export const CreateNewRow = () => { {itemsToShow.length === 0 ? ( Select the button below to create this label - + ) : ( <> @@ -757,8 +782,9 @@ export const CreateNewRow = () => { variant="invisible" leadingVisual={PlusCircleIcon} block - sx={{'[data-component="buttonContent"]': {justifyContent: 'start', fontWeight: 'normal'}}} - onClick={() => createNewLabel(query)} + alignContent="start" + sx={{'[data-component=text]': {fontWeight: 'normal'}}} + onClick={openCreateLabelDialog} > Create new label "{query}"... @@ -771,6 +797,37 @@ export const CreateNewRow = () => { Edit labels + + {dialogOpen && ( + setDialogOpen(false)} + width="medium" + footerButtons={[ + {buttonType: 'default', content: 'Cancel', onClick: () => setDialogOpen(false)}, + {type: 'submit', buttonType: 'primary', content: 'Save', onClick: () => formSubmitRef.current?.click()}, + ]} + > + + Note this Dialog is not accessible. Do not copy this. + +
+ + Name + + + + Color + + + + Description + + + +
+
+ )} ) } From be7bd6fe9515e100b331d942e17b2efeaba93932 Mon Sep 17 00:00:00 2001 From: Siddharth Kshetrapal Date: Thu, 25 Jan 2024 03:26:57 +0530 Subject: [PATCH 07/10] move new label dialog into its own component --- .../stories/SelectPanel.examples.stories.tsx | 112 +++++++++++------- 1 file changed, 67 insertions(+), 45 deletions(-) diff --git a/src/drafts/SelectPanel2/stories/SelectPanel.examples.stories.tsx b/src/drafts/SelectPanel2/stories/SelectPanel.examples.stories.tsx index ea301a31d9e..53839c34081 100644 --- a/src/drafts/SelectPanel2/stories/SelectPanel.examples.stories.tsx +++ b/src/drafts/SelectPanel2/stories/SelectPanel.examples.stories.tsx @@ -704,31 +704,21 @@ export const CreateNewRow = () => { We only have to do this until https://github.com/primer/react/pull/3840 is merged */ const [panelOpen, setPanelOpen] = React.useState(false) - const [dialogOpen, setDialogOpen] = React.useState(false) + const [newLabelDialogOpen, setNewLabelDialogOpen] = React.useState(false) const openCreateLabelDialog = () => { setPanelOpen(false) - setDialogOpen(true) + setNewLabelDialogOpen(true) } - const onDialogSubmit = (event: FormEvent) => { - event.preventDefault() - - const formData = new FormData(event.target as HTMLFormElement) - const {name, color, description} = Object.fromEntries(formData) as Record - - // pretending to persist changes - const id = `new-${name}` - data.labels.unshift({id, name, color, description}) - - setDialogOpen(false) - setPanelOpen(true) + const onNewLabelDialogSave = (id: string) => { + setNewLabelDialogOpen(false) setQuery('') // clear search input onLabelSelect(id) // select newly created label - } - const formSubmitRef = React.useRef(null) + setPanelOpen(true) + } return ( <> @@ -798,40 +788,72 @@ export const CreateNewRow = () => { - {dialogOpen && ( - setDialogOpen(false)} - width="medium" - footerButtons={[ - {buttonType: 'default', content: 'Cancel', onClick: () => setDialogOpen(false)}, - {type: 'submit', buttonType: 'primary', content: 'Save', onClick: () => formSubmitRef.current?.click()}, - ]} - > - - Note this Dialog is not accessible. Do not copy this. - -
- - Name - - - - Color - - - - Description - - - -
-
+ {newLabelDialogOpen && ( + setNewLabelDialogOpen(false)} + /> )} ) } +const CreateNewLabelDialog = ({ + initialValue, + onSave, + onCancel, +}: { + initialValue: string + onSave: (id: string) => void + onCancel: () => void +}) => { + const formSubmitRef = React.useRef(null) + + const onSubmit = (event: FormEvent) => { + event.preventDefault() + + const formData = new FormData(event.target as HTMLFormElement) + const {name, color, description} = Object.fromEntries(formData) as Record + + // pretending to persist changes + const id = `new-${name}` + data.labels.unshift({id, name, color, description}) + onSave(id) + } + + return ( + setDialogOpen(false)}, + {type: 'submit', buttonType: 'primary', content: 'Save', onClick: () => formSubmitRef.current?.click()}, + ]} + > + + Note this Dialog is not accessible. Do not copy this. + +
+ + Name + + + + Color + + + + Description + + + +
+
+ ) +} + // ----- Suspense implementation details ---- const cache = new Map() From fc976732a27a83741ceea4932133e1a53162c6d4 Mon Sep 17 00:00:00 2001 From: Siddharth Kshetrapal Date: Thu, 25 Jan 2024 04:03:17 +0530 Subject: [PATCH 08/10] generate id to allow spaces --- .../SelectPanel2/stories/SelectPanel.examples.stories.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/drafts/SelectPanel2/stories/SelectPanel.examples.stories.tsx b/src/drafts/SelectPanel2/stories/SelectPanel.examples.stories.tsx index 53839c34081..1f622b2b11b 100644 --- a/src/drafts/SelectPanel2/stories/SelectPanel.examples.stories.tsx +++ b/src/drafts/SelectPanel2/stories/SelectPanel.examples.stories.tsx @@ -817,7 +817,7 @@ const CreateNewLabelDialog = ({ const {name, color, description} = Object.fromEntries(formData) as Record // pretending to persist changes - const id = `new-${name}` + const id = Math.random().toString(26).slice(6) data.labels.unshift({id, name, color, description}) onSave(id) } @@ -828,7 +828,7 @@ const CreateNewLabelDialog = ({ onClose={onCancel} width="medium" footerButtons={[ - {buttonType: 'default', content: 'Cancel', onClick: () => setDialogOpen(false)}, + {buttonType: 'default', content: 'Cancel', onClick: onCancel}, {type: 'submit', buttonType: 'primary', content: 'Save', onClick: () => formSubmitRef.current?.click()}, ]} > From db106ec39320b6429166db1c6ad611607cd5d657 Mon Sep 17 00:00:00 2001 From: Siddharth Kshetrapal Date: Thu, 25 Jan 2024 04:19:04 +0530 Subject: [PATCH 09/10] focus newly created label --- .../SelectPanel2/stories/SelectPanel.examples.stories.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/drafts/SelectPanel2/stories/SelectPanel.examples.stories.tsx b/src/drafts/SelectPanel2/stories/SelectPanel.examples.stories.tsx index 1f622b2b11b..11564313574 100644 --- a/src/drafts/SelectPanel2/stories/SelectPanel.examples.stories.tsx +++ b/src/drafts/SelectPanel2/stories/SelectPanel.examples.stories.tsx @@ -718,6 +718,12 @@ export const CreateNewRow = () => { onLabelSelect(id) // select newly created label setPanelOpen(true) + + // focus newly created label once it renders + window.requestAnimationFrame(() => { + const newLabelElement = document.querySelector(`[data-id=${id}]`) as HTMLLIElement + newLabelElement.focus() + }) } return ( @@ -754,6 +760,7 @@ export const CreateNewRow = () => { key={label.id} onSelect={() => onLabelSelect(label.id)} selected={selectedLabelIds.includes(label.id)} + data-id={label.id} > Date: Wed, 31 Jan 2024 01:11:56 +0530 Subject: [PATCH 10/10] move focus zone logic to ActionList --- src/ActionList/ActionListContainerContext.tsx | 1 + src/ActionList/List.tsx | 12 +++++++++++- src/drafts/SelectPanel2/SelectPanel.tsx | 13 +------------ 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/ActionList/ActionListContainerContext.tsx b/src/ActionList/ActionListContainerContext.tsx index bf5c797bff0..594b15ea4e9 100644 --- a/src/ActionList/ActionListContainerContext.tsx +++ b/src/ActionList/ActionListContainerContext.tsx @@ -13,6 +13,7 @@ type ContextProps = { // to be more specific here, this is as good as (...args: any[]) => unknown // eslint-disable-next-line @typescript-eslint/ban-types afterSelect?: Function + enableFocusZone?: boolean } export const ActionListContainerContext = React.createContext({}) diff --git a/src/ActionList/List.tsx b/src/ActionList/List.tsx index ba2178d3efa..ea0156ac433 100644 --- a/src/ActionList/List.tsx +++ b/src/ActionList/List.tsx @@ -8,6 +8,8 @@ import {defaultSxProp} from '../utils/defaultSxProp' import {useSlots} from '../hooks/useSlots' import {Heading} from './Heading' import {useId} from '../hooks/useId' +import {useFocusZone, FocusKeys} from '../hooks/useFocusZone' +import {useProvidedRefOrCreate} from '../hooks' export type ActionListProps = React.PropsWithChildren<{ /** @@ -59,10 +61,18 @@ export const List = React.forwardRef( listRole, listLabelledBy, selectionVariant: containerSelectionVariant, // TODO: Remove after DropdownMenu2 deprecation + enableFocusZone, } = React.useContext(ActionListContainerContext) const ariaLabelledBy = slots.heading ? slots.heading.props.id ?? headingId : listLabelledBy + const listRef = useProvidedRefOrCreate(forwardedRef as React.RefObject) + useFocusZone({ + disabled: !enableFocusZone, + containerRef: listRef, + bindKeys: FocusKeys.ArrowVertical | FocusKeys.HomeAndEnd | FocusKeys.PageUpDown, + }) + return ( ( role={role || listRole} aria-labelledby={ariaLabelledBy} {...props} - ref={forwardedRef} + ref={listRef} > {childrenWithoutSlots} diff --git a/src/drafts/SelectPanel2/SelectPanel.tsx b/src/drafts/SelectPanel2/SelectPanel.tsx index 7fb2fd42974..9350d007131 100644 --- a/src/drafts/SelectPanel2/SelectPanel.tsx +++ b/src/drafts/SelectPanel2/SelectPanel.tsx @@ -1,6 +1,5 @@ import React from 'react' import {SearchIcon, XCircleFillIcon, XIcon, FilterRemoveIcon, AlertIcon} from '@primer/octicons-react' -import {FocusKeys} from '@primer/behaviors' import { Button, @@ -23,7 +22,6 @@ import { import {ActionListContainerContext} from '../../ActionList/ActionListContainerContext' import {useSlots} from '../../hooks/useSlots' import {useProvidedRefOrCreate, useId, useAnchoredPosition} from '../../hooks' -import {useFocusZone} from '../../hooks/useFocusZone' import {StyledOverlay, OverlayProps} from '../../Overlay/Overlay' import InputLabel from '../../internal/components/InputLabel' import {invariant} from '../../utils/invariant' @@ -152,15 +150,6 @@ const Panel: React.FC = ({ const panelId = useId(id) const [slots, childrenInBody] = useSlots(contents, {header: SelectPanelHeader, footer: SelectPanelFooter}) - /* Arrow keys navigation for list items */ - const {containerRef: listContainerRef} = useFocusZone( - { - bindKeys: FocusKeys.ArrowVertical | FocusKeys.HomeAndEnd | FocusKeys.PageUpDown, - focusableElementFilter: element => element.tagName === 'LI' || element.tagName === 'BUTTON', - }, - [internalOpen], - ) - /* Dialog */ const dialogRef = React.useRef(null) @@ -276,7 +265,6 @@ const Panel: React.FC = ({ } sx={{ flexShrink: 1, flexGrow: 1, @@ -292,6 +280,7 @@ const Panel: React.FC = ({ value={{ container: 'SelectPanel', listRole: 'listbox', + enableFocusZone: true, // Arrow keys navigation for list items selectionAttribute: 'aria-selected', selectionVariant: selectionVariant === 'instant' ? 'single' : selectionVariant, afterSelect: internalAfterSelect,