Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
e30722a
add useTagGroupState
reidbarber Nov 29, 2022
d7614e3
update TagGroup to use useTagGroupState
reidbarber Nov 29, 2022
78e360e
update useTagGroup
reidbarber Nov 29, 2022
09f297a
cleanup useTag
reidbarber Nov 29, 2022
22f9f5f
cleanup stories
reidbarber Nov 29, 2022
1bec84c
add comment
reidbarber Nov 29, 2022
5e7df52
remove private from package.json
reidbarber Nov 29, 2022
022828f
lint
reidbarber Nov 29, 2022
50a723d
fix types
reidbarber Nov 29, 2022
6cc253a
lint
reidbarber Nov 29, 2022
15a8b32
fix types
reidbarber Nov 29, 2022
31ca965
update removal string
reidbarber Nov 29, 2022
a8202bf
switch to focus row instead of cell
reidbarber Nov 29, 2022
fa66c91
fix types
reidbarber Nov 29, 2022
3aad4db
add test for focus after deleting
reidbarber Nov 29, 2022
d5449f7
set focusMode: 'cell'
reidbarber Nov 29, 2022
f3f64d7
cleanup useTag
reidbarber Nov 29, 2022
f746d65
lint
reidbarber Nov 29, 2022
55d704e
update translation string punctuation
reidbarber Nov 29, 2022
72ebeef
fix type
reidbarber Nov 30, 2022
cc44d18
refactor to use useGridList
reidbarber Nov 30, 2022
e0d4bdb
update tests
reidbarber Nov 30, 2022
1df22b3
improve types
reidbarber Nov 30, 2022
3faa12f
lint
reidbarber Nov 30, 2022
8223690
update comments
reidbarber Dec 1, 2022
a2dcdc5
add test for clicking remove button
reidbarber Dec 1, 2022
45fd98f
update tests to check that onRemove is called once
reidbarber Dec 1, 2022
e4be115
cleanup onPress and clearButtonProps type
reidbarber Dec 1, 2022
2b72dbc
fix text overflow and cleanup styles
reidbarber Dec 2, 2022
c757e9f
Merge branch 'main' into add-useTagGroupState-fix-focus
reidbarber Dec 5, 2022
6463bbf
update dependencies
reidbarber Dec 5, 2022
30a808d
cleanup Tag
reidbarber Dec 5, 2022
4b4ec67
update keyboard focus tests
reidbarber Dec 5, 2022
c61e143
fix types and imports
reidbarber Dec 5, 2022
34e0046
style fix
reidbarber Dec 6, 2022
db61320
update tests
reidbarber Dec 6, 2022
9c5cd8a
only restore focus if onRemove called
reidbarber Dec 6, 2022
ee5d3aa
cleanup chain
reidbarber Dec 7, 2022
2eebbf8
Merge branch 'main' into add-useTagGroupState-fix-focus
reidbarber Dec 7, 2022
734492e
Merge branch 'main' into add-useTagGroupState-fix-focus
reidbarber Dec 9, 2022
57d796a
Merge branch 'main' into add-useTagGroupState-fix-focus
reidbarber Dec 16, 2022
274d027
formatting
reidbarber Dec 16, 2022
7137dca
lint
reidbarber Dec 16, 2022
d9fe732
fix types
reidbarber Dec 16, 2022
507195a
lint
reidbarber Dec 17, 2022
7f36829
lint
reidbarber Dec 19, 2022
4365154
lint
reidbarber Dec 19, 2022
9ee9a63
Merge branch 'main' into
reidbarber Dec 19, 2022
bad3da5
Merge branch 'main' into add-useTagGroupState-fix-focus
reidbarber Dec 20, 2022
d13c41c
formatting
reidbarber Dec 20, 2022
47fc203
Merge branch 'main' into add-useTagGroupState-fix-focus
reidbarber Dec 20, 2022
aec919e
remove unnecessary export
reidbarber Jan 3, 2023
288e8e3
Merge branch 'main' into add-useTagGroupState-fix-focus
reidbarber Jan 3, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 30 additions & 22 deletions packages/@adobe/spectrum-css-temp/components/tags/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ governing permissions and limitations under the License.
@import '../commons/index.css';

.spectrum-Tags {
display: inline-flex;
display: flex;
flex-wrap: wrap;

margin: 0;
Expand All @@ -27,7 +27,7 @@ governing permissions and limitations under the License.
--spectrum-focus-ring-border-size: var(--spectrum-tag-border-size);

display: grid;
grid-template-columns: 1fr auto;
grid-template-columns: auto 1fr auto;
grid-template-areas: "icon content action";
align-items: center;
box-sizing: border-box;
Expand Down Expand Up @@ -58,28 +58,36 @@ governing permissions and limitations under the License.
height: calc(var(--spectrum-tag-height) - (2 * var(--spectrum-tag-border-size)));
width: var(--spectrum-global-dimension-size-300);
}
}

.spectrum-Tag-icon {
grid-area: icon;
margin-inline-end: var(--spectrum-global-dimension-size-100);
}
.spectrum-Tag-cell {
overflow: hidden;
text-overflow: ellipsis;
display: flex;
align-items: center;
}

.spectrum-Tag-content {
grid-area: content;
block-size: 100%;
line-height: calc(var(--spectrum-tag-height) - calc(var(--spectrum-tag-border-size) * 2));
margin-inline-end: var(--spectrum-tag-padding-x);
flex: 1 1 auto;
font-size: var(--spectrum-tag-text-size);
cursor: default;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
outline: none;
}
.spectrum-Tag-icon {
grid-area: icon;
margin-inline-end: var(--spectrum-global-dimension-size-100);
}

.spectrum-Tag-content {
grid-area: content;
line-height: calc(var(--spectrum-tag-height) - calc(var(--spectrum-tag-border-size) * 2));
margin-inline-end: var(--spectrum-tag-padding-x);
flex: 1 1 auto;
font-size: var(--spectrum-tag-text-size);
cursor: default;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
outline: none;
}

.tags-removable {
margin-inline-end: 0;
&.is-removable {
.spectrum-Tag-content {
margin-inline-end: 0;
}
}
}

2 changes: 1 addition & 1 deletion packages/@react-aria/tag/intl/en-US.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"remove": "Remove"
"remove": "Press Space or Delete to remove tag."
}
6 changes: 3 additions & 3 deletions packages/@react-aria/tag/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@
"url": "https://github.com/adobe/react-spectrum"
},
"dependencies": {
"@react-aria/grid": "^3.5.2",
"@react-aria/gridlist": "^3.1.1",
"@react-aria/i18n": "^3.6.3",
"@react-aria/interactions": "^3.13.1",
"@react-aria/utils": "^3.14.2",
"@react-stately/grid": "^3.4.2",
"@react-types/grid": "^3.1.5",
"@react-stately/tag": "3.0.0-alpha.1",
"@react-types/button": "^3.7.0",
"@react-types/shared": "^3.16.0",
"@react-types/tag": "3.0.0-beta.1",
"@swc/helpers": "^0.4.14"
Expand Down
66 changes: 19 additions & 47 deletions packages/@react-aria/tag/src/TagKeyboardDelegate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,26 @@
* governing permissions and limitations under the License.
*/

import {GridCollection} from '@react-types/grid';
import {GridKeyboardDelegate} from '@react-aria/grid';
import {Collection, Direction, KeyboardDelegate} from '@react-types/shared';
import {Key} from 'react';

export class TagKeyboardDelegate<T> extends GridKeyboardDelegate<T, GridCollection<T>> {
getFirstKey() {
let key = this.collection.getFirstKey();
let item = this.collection.getItem(key);
export class TagKeyboardDelegate<T> implements KeyboardDelegate {
private collection: Collection<T>;
private direction: Direction;

return [...item.childNodes][0].key;
constructor(collection: Collection<T>, direction: Direction) {
this.collection = collection;
this.direction = direction;
}

getLastKey() {
let key = this.collection.getLastKey();
let item = this.collection.getItem(key);

return [...item.childNodes][0].key;
getFirstKey() {
return this.collection.getFirstKey();
}

getLastKey() {
return this.collection.getLastKey();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is likely to behave oddly when in the collapsed mode
just something to keep an eye out for, ActionGroup home/end don't do anything

}

getKeyRightOf(key: Key) {
return this.direction === 'rtl' ? this.getKeyAbove(key) : this.getKeyBelow(key);
}
Expand All @@ -43,27 +44,12 @@ export class TagKeyboardDelegate<T> extends GridKeyboardDelegate<T, GridCollecti
return;
}

// If focus was on a cell, start searching from the parent row
if (this.isCell(startItem)) {
key = startItem.parentKey;
}

// Find the next item
key = this.findNextKey(key);
key = this.collection.getKeyAfter(key);
if (key != null) {
// If focus was on a cell, focus the cell with the same index in the next row.
if (this.isCell(startItem)) {
let item = this.collection.getItem(key);

return [...item.childNodes][startItem.index].key;
}

// Otherwise, focus the next row
if (this.focusMode === 'row') {
return key;
}
return key;
} else {
return this.getFirstKey();
return this.collection.getFirstKey();
}
}

Expand All @@ -73,26 +59,12 @@ export class TagKeyboardDelegate<T> extends GridKeyboardDelegate<T, GridCollecti
return;
}

// If focus is on a cell, start searching from the parent row
if (this.isCell(startItem)) {
key = startItem.parentKey;
}

// Find the previous item
key = this.findPreviousKey(key);
key = this.collection.getKeyBefore(key);
if (key != null) {
// If focus was on a cell, focus the cell with the same index in the previous row.
if (this.isCell(startItem)) {
let item = this.collection.getItem(key);
return [...item.childNodes][startItem.index].key;
}

// Otherwise, focus the previous row
if (this.focusMode === 'row') {
return key;
}
return key;
} else {
return this.getLastKey();
return this.collection.getLastKey();
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/@react-aria/tag/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ export {useTag} from './useTag';
export {useTagGroup} from './useTagGroup';

export type {TagProps} from '@react-types/tag';
export type {AriaTagGroupProps, TagGroupAria} from './useTagGroup';
export type {TagGroupAria} from './useTagGroup';
export type {TagAria} from './useTag';
71 changes: 36 additions & 35 deletions packages/@react-aria/tag/src/useTag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,77 +10,78 @@
* governing permissions and limitations under the License.
*/

import {ButtonHTMLAttributes, KeyboardEvent} from 'react';
import {AriaButtonProps} from '@react-types/button';
import {chain, filterDOMProps, mergeProps, useId} from '@react-aria/utils';
import {DOMAttributes} from '@react-types/shared';
import {filterDOMProps, mergeProps, useId} from '@react-aria/utils';
import {GridState} from '@react-stately/grid';
// @ts-ignore
import intlMessages from '../intl/*.json';
import {KeyboardEvent} from 'react';
import type {TagGroupState} from '@react-stately/tag';
import {TagProps} from '@react-types/tag';
import {useGridCell, useGridRow} from '@react-aria/grid';
import {useGridListItem} from '@react-aria/gridlist';
import {useLocalizedStringFormatter} from '@react-aria/i18n';


export interface TagAria {
labelProps: DOMAttributes,
tagProps: DOMAttributes,
tagRowProps: DOMAttributes,
clearButtonProps: ButtonHTMLAttributes<HTMLButtonElement>
clearButtonProps: AriaButtonProps
}

export function useTag(props: TagProps<any>, state: GridState<any, any>): TagAria {
let {isFocused} = props;
const {
/**
* Provides the behavior and accessibility implementation for a tag component.
* @param props - Props to be applied to the tag.
* @param state - State for the tag group, as returned by `useTagGroupState`.
*/
export function useTag<T>(props: TagProps<T>, state: TagGroupState<T>): TagAria {
let {
isFocused,
allowsRemoving,
onRemove,
item,
tagRef,
tagRowRef
} = props;
const stringFormatter = useLocalizedStringFormatter(intlMessages);
const removeString = stringFormatter.format('remove');
const labelId = useId();
const buttonId = useId();
let stringFormatter = useLocalizedStringFormatter(intlMessages);
let removeString = stringFormatter.format('remove');
let labelId = useId();
let buttonId = useId();

let {rowProps} = useGridRow({
let {rowProps, gridCellProps} = useGridListItem({
node: item
}, state, tagRowRef);
// Don't want the row to be focusable or accessible via keyboard
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let {tabIndex, ...otherRowProps} = rowProps;

let {gridCellProps} = useGridCell({
node: [...item.childNodes][0],
focusMode: 'cell'
}, state, tagRef);
// We want the group to handle keyboard navigation between tags.
delete rowProps.onKeyDownCapture;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rowProps.onKeyDownCapture is set in useGridListItem as onKeyDown(). I was wondering if this should be a replace with the onKeyDown you're defining and not a delete, but nothing consuming this will call onKeyDownCapture, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could set it to undefined down there, but it would do the same thing. And I think we want onKeyDown here, we're just removing that handler since it would handle focusing within a cell, which we don't want.


let onRemove = chain(props.onRemove, state.onRemove);

function onKeyDown(e: KeyboardEvent) {
let onKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Delete' || e.key === 'Backspace' || e.key === ' ') {
onRemove(item.childNodes[0].key);
onRemove(item.key);
e.preventDefault();
}
}
const pressProps = {
onPress: () => onRemove?.(item.childNodes[0].key)
};

isFocused = isFocused || state.selectionManager.focusedKey === item.childNodes[0].key;
isFocused = isFocused || state.selectionManager.focusedKey === item.key;
let domProps = filterDOMProps(props);
return {
clearButtonProps: mergeProps(pressProps, {
clearButtonProps: {
'aria-label': removeString,
'aria-labelledby': `${buttonId} ${labelId}`,
id: buttonId
}),
id: buttonId,
onPress: () => allowsRemoving && onRemove ? onRemove(item.key) : null
},
labelProps: {
id: labelId
},
tagRowProps: otherRowProps,
tagRowProps: {
...rowProps,
tabIndex: (isFocused || state.selectionManager.focusedKey == null) ? 0 : -1,
onKeyDown: allowsRemoving ? onKeyDown : null
},
tagProps: mergeProps(domProps, gridCellProps, {
'aria-errormessage': props['aria-errormessage'],
'aria-label': props['aria-label'],
onKeyDown: allowsRemoving ? onKeyDown : null,
tabIndex: (isFocused || state.selectionManager.focusedKey == null) ? 0 : -1
'aria-label': props['aria-label']
})
};
}
34 changes: 24 additions & 10 deletions packages/@react-aria/tag/src/useTagGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,43 @@
* governing permissions and limitations under the License.
*/

import {DOMAttributes, DOMProps} from '@react-types/shared';
import {AriaTagGroupProps} from '@react-types/tag';
import {DOMAttributes} from '@react-types/shared';
import {filterDOMProps, mergeProps} from '@react-aria/utils';
import {ReactNode, useState} from 'react';
import {RefObject, useState} from 'react';
import type {TagGroupState} from '@react-stately/tag';
import {TagKeyboardDelegate} from './TagKeyboardDelegate';
import {useFocusWithin} from '@react-aria/interactions';

export interface AriaTagGroupProps extends DOMProps {
children: ReactNode,
isReadOnly?: boolean, // removes close button
validationState?: 'valid' | 'invalid'
}
import {useGridList} from '@react-aria/gridlist';
import {useLocale} from '@react-aria/i18n';

export interface TagGroupAria {
tagGroupProps: DOMAttributes
}

export function useTagGroup(props: AriaTagGroupProps): TagGroupAria {
/**
* Provides the behavior and accessibility implementation for a tag group component.
* Tags allow users to categorize content. They can represent keywords or people, and are grouped to describe an item or a search request.
* @param props - Props to be applied to the tag group.
* @param state - State for the tag group, as returned by `useTagGroupState`.
* @param ref - A ref to a DOM element for the tag group.
*/
export function useTagGroup<T>(props: AriaTagGroupProps<T>, state: TagGroupState<T>, ref: RefObject<HTMLElement>): TagGroupAria {
let {direction} = useLocale();
let keyboardDelegate = new TagKeyboardDelegate(state.collection, direction);
let {gridProps} = useGridList({...props, keyboardDelegate}, state, ref);

// Don't want the grid to be focusable or accessible via keyboard
delete gridProps.role;
delete gridProps.tabIndex;

let [isFocusWithin, setFocusWithin] = useState(false);
let {focusWithinProps} = useFocusWithin({
onFocusWithinChange: setFocusWithin
});
let domProps = filterDOMProps(props);
return {
tagGroupProps: mergeProps(domProps, {
tagGroupProps: mergeProps(gridProps, domProps, {
'aria-atomic': false,
'aria-relevant': 'additions',
'aria-live': isFocusWithin ? 'polite' : 'off',
Expand Down
6 changes: 1 addition & 5 deletions packages/@react-spectrum/tag/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,16 @@
},
"dependencies": {
"@react-aria/focus": "^3.10.1",
"@react-aria/grid": "^3.5.2",
"@react-aria/i18n": "^3.6.3",
"@react-aria/interactions": "^3.13.1",
"@react-aria/tag": "3.0.0-beta.1",
"@react-aria/utils": "^3.14.2",
"@react-spectrum/button": "^3.11.2",
"@react-spectrum/text": "^3.3.4",
"@react-spectrum/utils": "^3.8.1",
"@react-stately/collections": "^3.5.1",
"@react-stately/grid": "^3.4.2",
"@react-stately/list": "^3.6.1",
"@react-stately/tag": "3.0.0-alpha.1",
"@react-types/shared": "^3.16.0",
"@react-types/tag": "3.0.0-beta.1",
"@spectrum-icons/workflow": "^4.0.6",
"@swc/helpers": "^0.4.14"
},
"devDependencies": {
Expand Down
Loading