Skip to content

Commit

Permalink
chore: Internal visual mode for table inline editable cell (#2060)
Browse files Browse the repository at this point in the history
  • Loading branch information
pan-kot authored Mar 15, 2024
1 parent 2d9f41f commit 6e03e44
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 46 deletions.
5 changes: 5 additions & 0 deletions pages/table/inline-editor.permutations.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const options = ['A', 'B', 'C', 'D', 'E', 'F'].map(value => ({ value, label: `Op

interface PermutationProps extends TableProps.EditConfig<unknown> {
isEditing: boolean;
interactiveCell: boolean;
successfulEdit?: boolean;
disabledReason?: () => string;
}
Expand All @@ -43,6 +44,7 @@ const editPermutations = createPermutations<PermutationProps>([
constraintText: [undefined, 'This requirement needs to be met.'],
validation: [undefined, () => 'There was an error!'],
isEditing: [true],
interactiveCell: [false],
},
{
ariaLabel: ['Editable column'],
Expand All @@ -52,6 +54,7 @@ const editPermutations = createPermutations<PermutationProps>([
constraintText: [undefined],
validation: [undefined],
isEditing: [false],
interactiveCell: [false, true],
successfulEdit: [false, true],
},
{
Expand All @@ -62,6 +65,7 @@ const editPermutations = createPermutations<PermutationProps>([
constraintText: [undefined],
validation: [undefined],
isEditing: [false],
interactiveCell: [false, true],
disabledReason: [() => 'Disabled reason popover content'],
},
]);
Expand All @@ -84,6 +88,7 @@ export default function InlineEditorPermutations() {
activateEditLabel: column => `Edit ${column.header}`,
cancelEditLabel: column => `Cancel editing ${column.header}`,
submitEditLabel: column => `Submit edit ${column.header}`,
successfulEditLabel: () => 'Edit successful',
}}
item={{}}
column={{ ...baseColumnDefinition, editConfig: permutation }}
Expand Down
41 changes: 23 additions & 18 deletions src/table/body-cell/disabled-inline-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export function DisabledInlineEditor<ItemType>({
onEditEnd,
editDisabledReason,
isVisualRefresh,
interactiveCell = true,
...rest
}: DisabledInlineEditorProps<ItemType>) {
const clickAwayRef = useClickAway(() => {
Expand All @@ -39,7 +40,7 @@ export function DisabledInlineEditor<ItemType>({

const [hasHover, setHasHover] = useState(false);
const [hasFocus, setHasFocus] = useState(false);
const showIcon = hasHover || hasFocus || isEditing;
const showIcon = hasHover || hasFocus || isEditing || !interactiveCell;

const iconRef = useRef(null);
const buttonRef = useRef<HTMLButtonElement>(null);
Expand Down Expand Up @@ -70,32 +71,36 @@ export function DisabledInlineEditor<ItemType>({
className={clsx(
className,
styles['body-cell-editable'],
styles['body-cell-disabled-edit'],
interactiveCell && styles['body-cell-interactive'],
isEditing && styles['body-cell-edit-disabled-popover'],
isVisualRefresh && styles['is-visual-refresh']
)}
onClick={!isEditing ? onClick : undefined}
onClick={interactiveCell && !isEditing ? onClick : undefined}
onMouseEnter={() => setHasHover(true)}
onMouseLeave={() => setHasHover(false)}
ref={clickAwayRef}
>
{column.cell(item)}

<button
ref={buttonRef}
tabIndex={tabIndex}
className={styles['body-cell-editor']}
aria-label={ariaLabels?.activateEditLabel?.(column, item)}
aria-haspopup="dialog"
aria-disabled="true"
onFocus={() => setHasFocus(true)}
onBlur={() => setHasFocus(false)}
onKeyDown={handleEscape}
{...targetProps}
>
{showIcon && <Icon name="lock-private" variant="normal" __internalRootRef={iconRef} />}
{descriptionEl}
</button>
<div className={styles['body-cell-editor-wrapper']}>
<button
ref={buttonRef}
tabIndex={tabIndex}
className={clsx(styles['body-cell-editor'], styles['body-cell-editor-disabled'])}
aria-label={ariaLabels?.activateEditLabel?.(column, item)}
aria-haspopup="dialog"
aria-disabled="true"
onClick={!interactiveCell && !isEditing ? onClick : undefined}
onFocus={() => setHasFocus(true)}
onBlur={() => setHasFocus(false)}
onKeyDown={handleEscape}
{...targetProps}
>
{showIcon && <Icon name="lock-private" variant="normal" __internalRootRef={iconRef} />}
{descriptionEl}
</button>
</div>

{isEditing && (
<span ref={portalRef}>
<Portal>
Expand Down
32 changes: 20 additions & 12 deletions src/table/body-cell/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export interface TableBodyCellProps<ItemType> extends TableTdElementProps {
onEditEnd: (cancelled: boolean) => void;
submitEdit?: TableProps.SubmitEditFunction<ItemType>;
ariaLabels: TableProps['ariaLabels'];
interactiveCell?: boolean;
}

function TableCellEditable<ItemType>({
Expand All @@ -39,6 +40,7 @@ function TableCellEditable<ItemType>({
ariaLabels,
isVisualRefresh,
successfulEdit = false,
interactiveCell = true,
...rest
}: TableBodyCellProps<ItemType>) {
const i18n = useInternalI18n('table');
Expand All @@ -57,7 +59,7 @@ function TableCellEditable<ItemType>({
// To improve the initial page render performance we only show the edit icon when necessary.
const [hasHover, setHasHover] = useState(false);
const [hasFocus, setHasFocus] = useState(false);
const showIcon = hasHover || hasFocus;
const showIcon = hasHover || hasFocus || !interactiveCell;

const prevSuccessfulEdit = usePrevious(successfulEdit);
const prevHasFocus = usePrevious(hasFocus);
Expand All @@ -83,11 +85,12 @@ function TableCellEditable<ItemType>({
className={clsx(
className,
styles['body-cell-editable'],
interactiveCell && styles['body-cell-interactive'],
isEditing && styles['body-cell-edit-active'],
showSuccessIcon && showIcon && styles['body-cell-has-success'],
isVisualRefresh && styles['is-visual-refresh']
)}
onClick={!isEditing ? onEditStart : undefined}
onClick={interactiveCell && !isEditing ? onEditStart : undefined}
onMouseEnter={() => setHasHover(true)}
onMouseLeave={() => setHasHover(false)}
>
Expand All @@ -106,6 +109,7 @@ function TableCellEditable<ItemType>({
) : (
<>
{column.cell(item)}

{showSuccessIcon && showIcon && (
<>
<span
Expand All @@ -125,16 +129,20 @@ function TableCellEditable<ItemType>({
</LiveRegion>
</>
)}
<button
className={styles['body-cell-editor']}
aria-label={ariaLabels?.activateEditLabel?.(column, item)}
ref={editActivateRef}
onFocus={() => setHasFocus(true)}
onBlur={() => setHasFocus(false)}
tabIndex={editActivateTabIndex}
>
{showIcon && <Icon name="edit" />}
</button>

<div className={styles['body-cell-editor-wrapper']}>
<button
className={styles['body-cell-editor']}
aria-label={ariaLabels?.activateEditLabel?.(column, item)}
ref={editActivateRef}
onClick={!interactiveCell && !isEditing ? onEditStart : undefined}
onFocus={() => setHasFocus(true)}
onBlur={() => setHasFocus(false)}
tabIndex={editActivateTabIndex}
>
{showIcon && <Icon name="edit" />}
</button>
</div>
</>
)}
</TableTdElement>
Expand Down
48 changes: 32 additions & 16 deletions src/table/body-cell/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ $success-icon-padding-right: calc(#{$edit-button-padding-right} + #{$icon-width-
padding-block-start: $cell-vertical-padding;
}

&-editor {
&-editor-wrapper {
padding-block: 0;
padding-inline-start: 0;
padding-inline-end: $edit-button-padding-right;
Expand All @@ -232,24 +232,34 @@ $success-icon-padding-right: calc(#{$edit-button-padding-right} + #{$icon-width-
}

&-success,
&-editor {
&-editor-wrapper {
inset-block: 0;
inset-inline-end: 0;
position: absolute;

display: flex;
align-items: center;
justify-content: flex-end;

}
&-editor {
// Reset some native <button> styles
cursor: pointer;
outline: 0;
background: 0;
border-block: 0;
border-inline: 0;
padding-block: 0;
padding-inline: 0;

color: awsui.$color-text-button-normal-default;
// This gives the editor button a small area even when the icon is not rendered.
// That is to allow programmatic interaction in tests.
min-block-size: 10px;
min-inline-size: 10px;

color: awsui.$color-text-button-normal-default;
&-disabled {
color: awsui.$color-text-disabled-inline-edit;
}
&:hover {
color: awsui.$color-text-button-normal-hover;
}
Expand Down Expand Up @@ -285,13 +295,8 @@ $success-icon-padding-right: calc(#{$edit-button-padding-right} + #{$icon-width-
}
}

&.body-cell-disabled-edit > .body-cell-editor {
color: awsui.$color-text-disabled-inline-edit;
}

&.body-cell-editable {
position: relative;
cursor: pointer;

&.sticky-cell {
position: sticky;
Expand All @@ -305,32 +310,43 @@ $success-icon-padding-right: calc(#{$edit-button-padding-right} + #{$icon-width-
}

&:not(.body-cell-edit-active) {
cursor: pointer;

@mixin focused-editor-styles {
padding-inline-end: calc(#{$cell-horizontal-padding} + #{awsui.$space-l});
& > .body-cell-editor {
& > .body-cell-editor-wrapper {
opacity: 1;
}
& > .body-cell-success {
opacity: 1;
}
}
& > .body-cell-editor {
& > .body-cell-editor-wrapper {
opacity: 0;
}

// Showing focus outline for the cell.
// We don't use our focus-visible polyfill here because it doesn't work properly with screen readers.
// These edit buttons are special because they are visually hidden (opacity: 0), but exposed to assistive technology.
// It's therefore important to display the focus outline, even when a keyboard user wasn't detected.
// For example, when an edit button is selected from the VoiceOver rotor menu.
&:focus-within {
&.body-cell-interactive:focus-within {
@include styles.focus-highlight(
(
'vertical': calc(-1 * #{awsui.$space-scaled-xxs}),
'horizontal': calc(-1 * #{awsui.$space-scaled-xxs}),
)
);
}
// When a cell is not interactive the focus outline must be present for the editor button.
&:not(.body-cell-interactive) > .body-cell-editor-wrapper > .body-cell-editor {
@include focus-visible.when-visible {
@include styles.focus-highlight(awsui.$space-button-inline-icon-focus-outline-gutter);
}
}

&:focus-within,
&:not(.body-cell-interactive),
&:focus-within:focus-within,
&.body-cell-edit-disabled-popover {
padding-inline-end: calc(#{$cell-horizontal-padding} + #{awsui.$space-l});
&.body-cell-has-success {
Expand All @@ -343,7 +359,7 @@ $success-icon-padding-right: calc(#{$edit-button-padding-right} + #{$icon-width-
}
}

&:hover {
&.body-cell-interactive:hover {
background-color: awsui.$color-background-dropdown-item-hover;
border-block: awsui.$border-divider-list-width solid awsui.$color-border-editable-cell-hover;
border-inline: awsui.$border-divider-list-width solid awsui.$color-border-editable-cell-hover;
Expand All @@ -354,7 +370,7 @@ $success-icon-padding-right: calc(#{$edit-button-padding-right} + #{$icon-width-
inset-inline: 0;
}

& > .body-cell-editor {
& > .body-cell-editor-wrapper {
padding-inline-end: calc(#{$edit-button-padding-right} - (2 * #{awsui.$border-divider-list-width}));
}
& > .body-cell-success {
Expand Down Expand Up @@ -388,7 +404,7 @@ $success-icon-padding-right: calc(#{$edit-button-padding-right} + #{$icon-width-
border-end-end-radius: awsui.$border-radius-item;
}
&.body-cell-first-row > .body-cell-success,
&.body-cell-first-row > .body-cell-editor {
&.body-cell-first-row > .body-cell-editor-wrapper {
padding-block-start: awsui.$border-divider-list-width;
}
}
Expand Down

0 comments on commit 6e03e44

Please sign in to comment.