From b9abee7893060affe080b215d9feba8618f9cc10 Mon Sep 17 00:00:00 2001 From: Nikhil Tomar <63502271+2nikhiltom@users.noreply.github.com> Date: Wed, 27 Mar 2024 19:06:41 +0530 Subject: [PATCH] feat(structuredList): update to use radio button icon, radios are left aligned under featureflag (#15910) * feat(structuredList): use of radio button icon,radios are left aligned * chore: revert few typos * feat(structuredList): adds feature flag story for structuredList * feat(structuredlist): add radio button selection, styling behind a flag * fix(structuredlist): add focus ring on click * chore(StructuredList): remote JS feature flag, delete unnecessary styles * chore(StructuredList): revert story to use checkmark icons * fix(StructuredList): adds onclick focus ring with selection prop * fix(StructuredList): adds 'selection' props * fix(structuredlist): focus on click & Tab,handles clickaway events * chore(StructuredList): add typescript types on refs * chore(StructuredList): updates PublicAPI snapshot * chore: update snapshots * fix(StructuredList): focus rings only on selection prop * chore: update snaps --------- Co-authored-by: Taylor Jones Co-authored-by: Andrea N. Cardona Co-authored-by: Taylor Jones --- packages/feature-flags/feature-flags.yml | 4 + .../__snapshots__/PublicAPI-test.js.snap | 3 + .../FeatureFlags/overview.stories.mdx | 19 +- .../StructuredList.featureflag.stories.js | 239 ++++++++++++++++++ .../StructuredList/StructuredList.stories.js | 108 ++++---- .../StructuredList/StructuredList.tsx | 42 ++- packages/react/src/feature-flags.js | 1 + packages/styles/scss/_feature-flags.scss | 1 + .../structured-list/_structured-list.scss | 24 +- 9 files changed, 374 insertions(+), 67 deletions(-) create mode 100644 packages/react/src/components/StructuredList/StructuredList.featureflag.stories.js diff --git a/packages/feature-flags/feature-flags.yml b/packages/feature-flags/feature-flags.yml index db06ca3a7908..203a7057b9d1 100644 --- a/packages/feature-flags/feature-flags.yml +++ b/packages/feature-flags/feature-flags.yml @@ -42,6 +42,10 @@ feature-flags: description: > Enable the new TreeView controllable API enabled: false + - name: enable-v12-structured-list-visible-icons + description: > + Enable rendering of radio icons in the StructuredList component + enabled: false - name: enable-experimental-focus-wrap-without-sentinels description: > Enable the new focus wrap behavior that doesn't use sentinel nodes diff --git a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap index 617d0777ef85..262754c98087 100644 --- a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap +++ b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap @@ -7361,6 +7361,9 @@ Map { "onKeyDown": Object { "type": "func", }, + "selection": Object { + "type": "bool", + }, }, }, "StructuredListSkeleton" => Object { diff --git a/packages/react/src/components/FeatureFlags/overview.stories.mdx b/packages/react/src/components/FeatureFlags/overview.stories.mdx index 93588e21054a..4abcef766231 100644 --- a/packages/react/src/components/FeatureFlags/overview.stories.mdx +++ b/packages/react/src/components/FeatureFlags/overview.stories.mdx @@ -30,16 +30,19 @@ components with all feature flags turned on. ## Current feature flags -| Flag | Description | Default | Javascript flag | Sass flag | -| -------------------------------------------------- | ------------------------------------------------------------------------ | ------- | --------------- | --------- | -| `enable-v11-release` | Flag enabling the v11 features | `true` | ✅ | ✅ | -| `enable-experimental-tile-contrast` | Enable the improved styling for tiles that provides better contrast | `false` | | ✅ | -| `enable-v12-tile-default-icons` | Enable default icons for Tile components | `false` | ✅ | | -| `enable-v12-overflowmenu` | Enable the use of the v12 OverflowMenu leveraging the Menu subcomponents | `false` | ✅ | | -| `enable-v12-tile-radio-icons` | Enable rendering of default icons in the tile components | `false` | ✅ | ✅ | -| `enable-treeview-controllable` | Enable the new TreeView controllable API | `false` | ✅ | | + +| Flag | Description | Default | Javascript flag | Sass flag | +| ------------------------------------------ | ------------------------------------------------------------------------ | ------- | --------------- | --------- | +| `enable-v11-release` | Flag enabling the v11 features | `true` | ✅ | ✅ | +| `enable-experimental-tile-contrast` | Enable the improved styling for tiles that provides better contrast | `false` | | ✅ | +| `enable-v12-tile-default-icons` | Enable default icons for Tile components | `false` | ✅ | | +| `enable-v12-overflowmenu` | Enable the use of the v12 OverflowMenu leveraging the Menu subcomponents | `false` | ✅ | | +| `enable-v12-tile-radio-icons` | Enable rendering of default icons in the tile components | `false` | ✅ | ✅ | +| `enable-treeview-controllable` | Enable the new TreeView controllable API | `false` | ✅ | | +| `enable-v12-structured-list-visible-icons` | Enable icon components within StructuredList to always be visible | `false` | | ✅ | | `enable-experimental-focus-wrap-without-sentinels` | Enable the new focus wrap behavior that doesn't use sentinel nodes | `false` | ✅ | | + ## Turning on feature flags in Javascript/react Use the FeatureFlag component to turn on a feature flag for a portion of your diff --git a/packages/react/src/components/StructuredList/StructuredList.featureflag.stories.js b/packages/react/src/components/StructuredList/StructuredList.featureflag.stories.js new file mode 100644 index 000000000000..5ffccb1d6256 --- /dev/null +++ b/packages/react/src/components/StructuredList/StructuredList.featureflag.stories.js @@ -0,0 +1,239 @@ +/** + * Copyright IBM Corp. 2016, 2023 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import mdx from './StructuredList.mdx'; +import { WithLayer } from '../../../.storybook/templates/WithLayer'; +import { useFeatureFlag } from '../FeatureFlags'; + +import { + StructuredListWrapper, + StructuredListHead, + StructuredListBody, + StructuredListRow, + StructuredListInput, + StructuredListCell, +} from './'; +import StructuredListSkeleton from './StructuredList.Skeleton'; +import { WithFeatureFlags } from '../../../.storybook/templates/WithFeatureFlags'; +const experimentalClassname = 'experimental-tile'; +export default { + title: 'Experimental/Feature Flags/StructuredList', + component: StructuredListWrapper, + subcomponents: { + StructuredListHead, + StructuredListBody, + StructuredListRow, + StructuredListInput, + StructuredListCell, + }, + parameters: { + docs: { + page: mdx, + }, + }, + argTypes: { + children: { + table: { + disable: true, + }, + }, + }, + decorators: [ + (Story) => ( + + + + ), + ], +}; + +export const Default = (args) => { + return ( +
+ + + + ColumnA + ColumnB + ColumnC + + + + + Row 1 + Row 1 + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc dui + magna, finibus id tortor sed, aliquet bibendum augue. Aenean + posuere sem vel euismod dignissim. Nulla ut cursus dolor. + Pellentesque vulputate nisl a porttitor interdum. + + + + Row 2 + Row 2 + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc dui + magna, finibus id tortor sed, aliquet bibendum augue. Aenean + posuere sem vel euismod dignissim. Nulla ut cursus dolor. + Pellentesque vulputate nisl a porttitor interdum. + + + + +
+ ); +}; + +Default.args = { + isCondensed: false, + isFlush: false, +}; + +Default.argTypes = { + selection: { + control: { + disable: true, + }, + }, + isCondensed: { + control: { + type: 'boolean', + }, + }, + isFlush: { + control: { + type: 'boolean', + }, + }, +}; + +const structuredListBodyRowGenerator = (numRows) => { + return Array.apply(null, Array(numRows)).map((n, i) => ( + + Row {i} + Row {i} + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc dui magna, + finibus id tortor sed, aliquet bibendum augue. Aenean posuere sem vel + euismod dignissim. Nulla ut cursus dolor. Pellentesque vulputate nisl a + porttitor interdum. + + + + )); +}; + +export const Selection = (args) => { + return ( + + + + ColumnA + ColumnB + ColumnC + + + + {structuredListBodyRowGenerator(4)} + + + ); +}; + +Selection.argTypes = { + isFlush: { + table: { + disable: true, + }, + }, + selection: { + control: { + disable: true, + }, + }, +}; + +export const WithBackgroundLayer = () => { + const v12StructuredRadioIcons = useFeatureFlag( + 'enable-v12-structured-list-visible-icons' + ); + return ( + + + + + {v12StructuredRadioIcons && ( + + )} + ColumnA + ColumnB + ColumnC + + + + {structuredListBodyRowGenerator(4, v12StructuredRadioIcons)} + + + + ); +}; + +export const Skeleton = (args) => ( +
+ +
+); + +Skeleton.args = { + rowCount: 5, +}; + +Skeleton.argTypes = { + isFlush: { + table: { + disable: true, + }, + }, + isCondensed: { + table: { + disable: true, + }, + }, + ariaLabel: { + table: { + disable: true, + }, + }, + ['aria-label']: { + table: { + disable: true, + }, + }, + className: { + table: { + disable: true, + }, + }, + selection: { + table: { + disable: true, + }, + }, + rowCount: { + control: { + type: 'number', + }, + }, +}; diff --git a/packages/react/src/components/StructuredList/StructuredList.stories.js b/packages/react/src/components/StructuredList/StructuredList.stories.js index 77fcbbee84ad..078d96cffe8f 100644 --- a/packages/react/src/components/StructuredList/StructuredList.stories.js +++ b/packages/react/src/components/StructuredList/StructuredList.stories.js @@ -6,7 +6,6 @@ */ import React from 'react'; -import { CheckmarkFilled } from '@carbon/icons-react'; import mdx from './StructuredList.mdx'; import { WithLayer } from '../../../.storybook/templates/WithLayer'; @@ -18,9 +17,9 @@ import { StructuredListInput, StructuredListCell, } from './'; -import StructuredListSkeleton from './StructuredList.Skeleton'; - +import { CheckmarkFilled } from '@carbon/icons-react'; const prefix = 'cds'; +import StructuredListSkeleton from './StructuredList.Skeleton'; export default { title: 'Components/StructuredList', @@ -46,39 +45,41 @@ export default { }, }; -export const Default = (args) => ( - - - - ColumnA - ColumnB - ColumnC - - - - - Row 1 - Row 1 - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc dui - magna, finibus id tortor sed, aliquet bibendum augue. Aenean posuere - sem vel euismod dignissim. Nulla ut cursus dolor. Pellentesque - vulputate nisl a porttitor interdum. - - - - Row 2 - Row 2 - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc dui - magna, finibus id tortor sed, aliquet bibendum augue. Aenean posuere - sem vel euismod dignissim. Nulla ut cursus dolor. Pellentesque - vulputate nisl a porttitor interdum. - - - - -); +export const Default = (args) => { + return ( + + + + ColumnA + ColumnB + ColumnC + + + + + Row 1 + Row 1 + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc dui + magna, finibus id tortor sed, aliquet bibendum augue. Aenean posuere + sem vel euismod dignissim. Nulla ut cursus dolor. Pellentesque + vulputate nisl a porttitor interdum. + + + + Row 2 + Row 2 + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc dui + magna, finibus id tortor sed, aliquet bibendum augue. Aenean posuere + sem vel euismod dignissim. Nulla ut cursus dolor. Pellentesque + vulputate nisl a porttitor interdum. + + + + + ); +}; Default.args = { isCondensed: false, @@ -102,7 +103,6 @@ Default.argTypes = { }, }, }; - const structuredListBodyRowGenerator = (numRows) => { return Array.apply(null, Array(numRows)).map((n, i) => ( @@ -162,22 +162,24 @@ Selection.argTypes = { }, }; -export const WithBackgroundLayer = () => ( - - - - - ColumnA - ColumnB - ColumnC - - - - {structuredListBodyRowGenerator(4)} - - - -); +export const WithBackgroundLayer = () => { + return ( + + + + + ColumnA + ColumnB + ColumnC + + + + {structuredListBodyRowGenerator(4)} + + + + ); +}; export const Skeleton = (args) => (
diff --git a/packages/react/src/components/StructuredList/StructuredList.tsx b/packages/react/src/components/StructuredList/StructuredList.tsx index e01286a8b386..c82d03baa773 100644 --- a/packages/react/src/components/StructuredList/StructuredList.tsx +++ b/packages/react/src/components/StructuredList/StructuredList.tsx @@ -7,6 +7,7 @@ import React, { useState, + useRef, type HTMLAttributes, type ReactNode, type KeyboardEvent, @@ -19,6 +20,8 @@ import { useId } from '../../internal/useId'; import deprecate from '../../prop-types/deprecate'; import { usePrefix } from '../../internal/usePrefix'; import { Text } from '../Text'; +import { RadioButtonChecked, RadioButton } from '@carbon/icons-react'; +import { useOutsideClick } from '../../internal/useOutsideClick'; type DivAttrs = HTMLAttributes; @@ -156,7 +159,6 @@ export function StructuredListHead(props) { const { children, className, ...other } = props; const prefix = usePrefix(); const classes = classNames(`${prefix}--structured-list-thead`, className); - return (
{children} @@ -250,9 +252,15 @@ export interface StructuredListRowProps extends DivAttrs { * Provide a handler that is invoked on the key down event for the control */ onKeyDown?(event: KeyboardEvent): void; + + /** + * Mark if this row should be selectable + */ + selection?: boolean; } export function StructuredListRow(props: StructuredListRowProps) { - const { onKeyDown, children, className, head, onClick, ...other } = props; + const { onKeyDown, children, className, head, onClick, selection, ...other } = + props; const [hasFocusWithin, setHasFocusWithin] = useState(false); const id = useId('grid-input'); const selectedRow = React.useContext(GridSelectedRowStateContext); @@ -263,14 +271,24 @@ export function StructuredListRow(props: StructuredListRowProps) { `${prefix}--structured-list-row`, { [`${prefix}--structured-list-row--header-row`]: head, - [`${prefix}--structured-list-row--focused-within`]: hasFocusWithin, + [`${prefix}--structured-list-row--focused-within`]: + (hasFocusWithin && !selection) || + (hasFocusWithin && + selection && + (selectedRow === id || selectedRow === null)), + // Ensure focus on the first item when navigating through Tab keys and no row is selected (selectedRow === null) [`${prefix}--structured-list-row--selected`]: selectedRow === id, }, className ); - + const itemRef = useRef(null); + const handleClick = () => { + setHasFocusWithin(false); + }; + useOutsideClick(itemRef, handleClick); return head ? (
+ {selection && } {children}
) : ( @@ -280,9 +298,14 @@ export function StructuredListRow(props: StructuredListRowProps) { {...other} role="row" className={classes} + ref={itemRef} onClick={(event) => { setSelectedRow?.(id); onClick && onClick(event); + if (selection) { + // focus items only when selection is enabled + setHasFocusWithin(true); + } }} onFocus={() => { setHasFocusWithin(true); @@ -292,6 +315,12 @@ export function StructuredListRow(props: StructuredListRowProps) { }} onKeyDown={onKeyDown}> + {selection && ( + + {selectedRow === id ? : } + + )} + {children}
@@ -330,6 +359,11 @@ StructuredListRow.propTypes = { * Provide a handler that is invoked on the key down event for the control, */ onKeyDown: PropTypes.func, + + /** + * Mark if this row should be selectable + */ + selection: PropTypes.bool, }; export interface StructuredListInputProps extends DivAttrs { diff --git a/packages/react/src/feature-flags.js b/packages/react/src/feature-flags.js index 8115625cc091..da81fa8e9548 100644 --- a/packages/react/src/feature-flags.js +++ b/packages/react/src/feature-flags.js @@ -13,4 +13,5 @@ FeatureFlags.merge({ 'enable-v11-release': true, 'enable-experimental-tile-contrast': false, 'enable-v12-tile-radio-icons': false, + 'enable-v12-structured-list-visible-icons': false, }); diff --git a/packages/styles/scss/_feature-flags.scss b/packages/styles/scss/_feature-flags.scss index 5acc57360d3b..20029d6d6989 100644 --- a/packages/styles/scss/_feature-flags.scss +++ b/packages/styles/scss/_feature-flags.scss @@ -13,6 +13,7 @@ 'enable-v11-release': true, 'enable-experimental-tile-contrast': false, 'enable-v12-tile-radio-icons': false, + 'enable-v12-structured-list-visible-icons': false, ) !default ); diff --git a/packages/styles/scss/components/structured-list/_structured-list.scss b/packages/styles/scss/components/structured-list/_structured-list.scss index 3618aaa07f34..1bf9ef19d2bb 100644 --- a/packages/styles/scss/components/structured-list/_structured-list.scss +++ b/packages/styles/scss/components/structured-list/_structured-list.scss @@ -18,7 +18,7 @@ @use '../../utilities/convert'; @use '../../utilities/component-reset'; -@mixin structured-list { +@mixin structured-list($enable-v12-structured-list-visible-icons: false) { .#{$prefix}--structured-list--selection .#{$prefix}--structured-list-td, .#{$prefix}--structured-list--selection .#{$prefix}--structured-list-th { @include padding--data-structured-list; @@ -186,7 +186,6 @@ .#{$prefix}--structured-list-svg { display: inline-block; - fill: transparent; transition: all motion.$duration-fast-02 motion.motion(standard, productive); vertical-align: top; } @@ -200,6 +199,27 @@ fill: $icon-primary; } + @if not( + enabled('enable-v12-structured-list-visible-icons') and + $enable-v12-structured-list-visible-icons + ) + { + .#{$prefix}--structured-list-svg { + fill: transparent; + } + } + + @if ( + enabled('enable-v12-structured-list-visible-icons') or + $enable-v12-structured-list-visible-icons + ) { + .#{$prefix}--structured-list-input:checked + ~ .#{$prefix}--structured-list-td + .#{$prefix}--structured-list-svg { + fill: $icon-primary; + } + } + // Skeleton State .#{$prefix}--structured-list.#{$prefix}--skeleton { .#{$prefix}--structured-list-th {