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 {