diff --git a/.all-contributorsrc b/.all-contributorsrc
index 629a9ce3d06d..9db733e7a6f9 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -1241,6 +1241,15 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "cordesmj",
+ "name": "cordesmj",
+ "avatar_url": "https://avatars.githubusercontent.com/u/7409239?v=4",
+ "profile": "https://github.com/cordesmj",
+ "contributions": [
+ "code"
+ ]
}
],
"commitConvention": "none"
diff --git a/.github/workflows/contribution-approved.yml b/.github/workflows/contribution-approved.yml
index 047d8a72d64e..6bfc9fe666fe 100644
--- a/.github/workflows/contribution-approved.yml
+++ b/.github/workflows/contribution-approved.yml
@@ -1,36 +1,26 @@
-# This workflow uses actions that are not certified by GitHub.
-# They are provided by a third-party and are governed by
-# separate terms of service, privacy policy, and support
-# documentation.
-
-# GitHub recommends pinning actions to a commit SHA.
-# To get a newer version, you will need to update the SHA.
-# You can also reference a tag or branch, but the action may change without warning
-name: Add comment on enhancement issue
+name: Contribution triage - proposal accepted and community contribution
on:
issues:
types: [labeled, opened]
permissions:
- issues: write
+ issues: write
jobs:
add-comment:
name: Add comment to with proposal accepted and community contribution
runs-on: ubuntu-latest
- if:
- ${{ (
- contains(github.event.label.name, format('proposal{0} accepted', ':')) &&
- contains(github.event.issue.labels.*.name, format('community contribution'))
- ) || (
- contains(github.event.label.name, format('community contribution')) &&
- contains(github.event.issue.labels.*.name, format('proposal{0} accepted', ':'))
- )}}
+ if:
+ ${{ ( contains(github.event.label.name, format('proposal{0} accepted',
+ ':')) && contains(github.event.issue.labels.*.name, format('community
+ contribution')) ) || ( contains(github.event.label.name, format('community
+ contribution')) && contains(github.event.issue.labels.*.name,
+ format('proposal{0} accepted', ':')) )}}
+
steps:
- name: Log github event
env:
- $GITHUB_CONTEXT_LABELS:
- ${{ toJson(github.event.label) }}
+ $GITHUB_CONTEXT_LABELS: ${{ toJson(github.event.label) }}
run: echo "$GITHUB_CONTEXT_LABELS"
- name: Add comment
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 #v6.4.1
diff --git a/.github/workflows/proposal-not-pursuing.yml b/.github/workflows/contribution-proposal-not-pursuing.yml
similarity index 57%
rename from .github/workflows/proposal-not-pursuing.yml
rename to .github/workflows/contribution-proposal-not-pursuing.yml
index 391e71e1c423..0198d5b7bdaa 100644
--- a/.github/workflows/proposal-not-pursuing.yml
+++ b/.github/workflows/contribution-proposal-not-pursuing.yml
@@ -1,28 +1,22 @@
-# This workflow uses actions that are not certified by GitHub.
-# They are provided by a third-party and are governed by
-# separate terms of service, privacy policy, and support
-# documentation.
-
-# GitHub recommends pinning actions to a commit SHA.
-# To get a newer version, you will need to update the SHA.
-# You can also reference a tag or branch, but the action may change without warning
-name: Add comment on enhancement issue
+name: Contribution triage - proposal not pursuing
on:
issues:
types: [labeled, opened]
permissions:
- issues: write
+ issues: write
jobs:
add-comment:
- name: If new enhancement issue is submitted, then post the following comment.
+ name:
+ If proposal is marked as not pursuing, then post the following comment.
runs-on: ubuntu-latest
+ if:
+ ${{ contains(github.event.label.name, format('proposal{0} not pursuing',
+ ':')) }}
steps:
- name: Add comment
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 #v6.4.1
- if:
- github.event.label.name == 'proposal\uFE55 not pursuing'
with:
script: |
github.rest.issues.createComment({
@@ -30,4 +24,4 @@ jobs:
owner: context.repo.owner,
repo: context.repo.repo,
body: 'Thank you for submitting your proposal. Unfortunately, it doesn't align with our roadmap or philosophy at this time. We value your contribution and encourage you to keep engaging with the community. For more information about our triage workflow and philosophy, please visit [here](https://github.com/carbon-design-system/carbon/blob/main/docs/guides/support.md#type-enhancement-).'
- })
\ No newline at end of file
+ })
diff --git a/.github/workflows/contribution-proposal.yml b/.github/workflows/contribution-proposal.yml
index 4bc964325500..118166684339 100644
--- a/.github/workflows/contribution-proposal.yml
+++ b/.github/workflows/contribution-proposal.yml
@@ -1,26 +1,20 @@
-# This workflow uses actions that are not certified by GitHub.
-# They are provided by a third-party and are governed by
-# separate terms of service, privacy policy, and support
-# documentation.
-
-# GitHub recommends pinning actions to a commit SHA.
-# To get a newer version, you will need to update the SHA.
-# You can also reference a tag or branch, but the action may change without warning
-name: Add comment on enhancement issue
+name: Contribution triage - enhancement created
on:
issues:
types: [labeled, opened]
permissions:
- issues: write
+ issues: write
jobs:
add-comment:
- name: If new enhancement issue is submitted, then post the following comment.
+ name:
+ If new enhancement issue is submitted, then post the following comment.
runs-on: ubuntu-latest
+ if:
+ ${{ contains(github.event.label.name, format('type{0} enhancement 💡', ':')) }}
steps:
- name: Add comment
- if: github.event.label.name == format('type{0} enhancement', ':')
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 #v6.4.1
with:
script: |
@@ -32,4 +26,4 @@ jobs:
Your proposal is open and is now being [triaged](https://github.com/orgs/carbon-design-system/projects/51/views/1) by the Carbon team.
If your proposal is accepted and the Carbon team has bandwidth they will take on the issue, or else request volunteers from the community.
Read the full triaging workflow [here](https://github.com/carbon-design-system/carbon/blob/main/docs/guides/support.md#type-enhancement-).'
- })
\ No newline at end of file
+ })
diff --git a/.github/workflows/contribution-ready-to-be-worked.yml b/.github/workflows/contribution-ready-to-be-worked.yml
new file mode 100644
index 000000000000..bbd7ec0418eb
--- /dev/null
+++ b/.github/workflows/contribution-ready-to-be-worked.yml
@@ -0,0 +1,37 @@
+name: Contribution triage - Ready to be worked
+on:
+ issues:
+ types: [unlabeled]
+
+permissions:
+ issues: write
+
+jobs:
+ add-comment:
+ name: Add comment to with proposal accepted and community contribution
+ runs-on: ubuntu-latest
+ if:
+ ${{ (
+ contains(github.event.label.name, 'design contribution needed') &&
+ (!contains(github.event.issue.labels.*.name, 'development contribution
+ needed'))
+ ) || (
+ contains(github.event.label.name, 'development
+ contribution needed') && (!contains(github.event.issue.labels.*.name,
+ 'design contribution needed'))
+ )}}
+ steps:
+ - name: Log github event
+ env:
+ $GITHUB_CONTEXT_LABELS: ${{ toJson(github.event.label) }}
+ run: echo "$GITHUB_CONTEXT_LABELS"
+ - name: Add comment
+ uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 #v6.4.1
+ with:
+ script: |
+ github.rest.issues.createComment({
+ issue_number: context.issue.number,
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ body: 'This issue is ready to be worked on. Volunteers, please feel free to join any of the [relevant meetups](https://carbondesignsystem.com/whats-happening/meetups/) to get guidance and feedback. We encourage you to bring any work in progress, no matter its state of completion. You can also ask any questions on slack. For design questions, the best channels are: [#carbon-design-system](https://ibm-studios.slack.com/archives/C0M053VPT) and [#figma-guild](https://ibm-studios.slack.com/archives/C023WKW6J5U). For developer questions, the best channels are: [#carbon-react](https://ibm-studios.slack.com/archives/C2K6RFJ1G), [#carbon-web-components](https://ibm-studios.slack.com/archives/CL83LMKSA).'
+ })
diff --git a/README.md b/README.md
index aea97d5f992a..339729e927fd 100644
--- a/README.md
+++ b/README.md
@@ -251,6 +251,7 @@ check out our [Contributing Guide](/.github/CONTRIBUTING.md) and our
David Ragsdale 💻 |
Hao Cheng 💻 |
+ cordesmj 💻 |
diff --git a/e2e/components/Dropdown/Dropdown-test.avt.e2e.js b/e2e/components/Dropdown/Dropdown-test.avt.e2e.js
index 791b90ed21e8..d2654c2096c5 100644
--- a/e2e/components/Dropdown/Dropdown-test.avt.e2e.js
+++ b/e2e/components/Dropdown/Dropdown-test.avt.e2e.js
@@ -71,20 +71,19 @@ test.describe('Dropdown @avt', () => {
await expect(menu).not.toBeVisible();
await expect(toggleButton).toBeFocused();
await page.keyboard.press('Enter');
- // Navigation inside the menu
- await page.keyboard.press('ArrowDown');
+ // Should focus on selected item by default
await expect(
page.getByRole('option', {
- name: 'Lorem, ipsum dolor sit amet consectetur adipisicing elit.',
+ name: 'Option 1',
})
).toHaveClass(
- 'cds--list-box__menu-item cds--list-box__menu-item--highlighted'
+ 'cds--list-box__menu-item cds--list-box__menu-item--active cds--list-box__menu-item--highlighted'
);
- // move to second option
+ // Navigation inside the menu
await page.keyboard.press('ArrowDown');
await expect(
page.getByRole('option', {
- name: 'Option 1',
+ name: 'Option 2',
})
).toHaveClass(
'cds--list-box__menu-item cds--list-box__menu-item--highlighted'
diff --git a/e2e/components/MultiSelect/MultiSelect-test.avt.e2e.js b/e2e/components/MultiSelect/MultiSelect-test.avt.e2e.js
index a3c1c06b5563..891340406c05 100644
--- a/e2e/components/MultiSelect/MultiSelect-test.avt.e2e.js
+++ b/e2e/components/MultiSelect/MultiSelect-test.avt.e2e.js
@@ -172,7 +172,7 @@ test.describe('MultiSelect @avt', () => {
await expect(selection).not.toBeVisible();
});
- test('filterable multiselect - keyboard nav', async ({ page }) => {
+ test.slow('filterable multiselect - keyboard nav', async ({ page }) => {
await visitStory(page, {
component: 'FilterableMultiSelect',
id: 'components-multiselect--filterable',
diff --git a/e2e/components/TextInput/TextInput-test.avt.e2e.js b/e2e/components/TextInput/TextInput-test.avt.e2e.js
index c98c5be09277..3b351447aa2b 100644
--- a/e2e/components/TextInput/TextInput-test.avt.e2e.js
+++ b/e2e/components/TextInput/TextInput-test.avt.e2e.js
@@ -37,4 +37,47 @@ test.describe('TextInput @avt', () => {
await expect(page.getByRole('textbox')).toBeDisabled();
await expect(page).toHaveNoACViolations('TextInput-Disabled');
});
+
+ test('accessibility-checker keyboard nav', async ({ page }) => {
+ await visitStory(page, {
+ component: 'TextInput',
+ id: 'components-textinput--default',
+ globals: {
+ theme: 'white',
+ },
+ });
+ const input = page.getByRole('textbox');
+
+ // Check the focus
+ await expect(input).toBeVisible();
+ await page.keyboard.press('Tab');
+ await expect(input).toBeFocused();
+
+ // Check input interaction
+ await input.fill('Text');
+ await expect(input).toHaveValue('Text');
+ await page.keyboard.press('Backspace');
+ await expect(input).toHaveValue('Tex');
+ });
+
+ test('accessibility-checker keyboard nav for password', async ({ page }) => {
+ await visitStory(page, {
+ component: 'TextInput',
+ id: 'components-textinput--toggle-password-visibility',
+ globals: {
+ theme: 'white',
+ },
+ });
+ const input = page.getByRole('textbox');
+ const span = page.locator('span.cds--assistive-text');
+
+ await page.keyboard.press('Tab');
+ await input.fill('Text');
+
+ // Checking toggle interaction
+ await page.keyboard.press('Tab');
+ await expect(span).toHaveText('Show password');
+ await page.keyboard.press('Enter');
+ await expect(span).toHaveText('Hide password');
+ });
});
diff --git a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
index 74451c45c18b..dd66d464dae1 100644
--- a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
+++ b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
@@ -3866,6 +3866,7 @@ Map {
},
},
"GlobalTheme" => Object {
+ "$$typeof": Symbol(react.forward_ref),
"propTypes": Object {
"children": Object {
"type": "node",
@@ -3882,6 +3883,7 @@ Map {
"type": "oneOf",
},
},
+ "render": [Function],
},
"Grid" => Object {
"propTypes": Object {
@@ -4104,6 +4106,7 @@ Map {
},
},
"Heading" => Object {
+ "$$typeof": Symbol(react.forward_ref),
"propTypes": Object {
"children": Object {
"type": "node",
@@ -4112,6 +4115,7 @@ Map {
"type": "string",
},
},
+ "render": [Function],
},
"IconButton" => Object {
"$$typeof": Symbol(react.forward_ref),
@@ -4393,6 +4397,7 @@ Map {
},
},
"Layer" => Object {
+ "$$typeof": Symbol(react.forward_ref),
"propTypes": Object {
"as": Object {
"args": Array [
@@ -4427,6 +4432,7 @@ Map {
"type": "oneOf",
},
},
+ "render": [Function],
},
"Link" => Object {
"$$typeof": Symbol(react.forward_ref),
@@ -6640,6 +6646,7 @@ Map {
},
"SecondaryButton" => Object {},
"Section" => Object {
+ "$$typeof": Symbol(react.forward_ref),
"propTypes": Object {
"as": Object {
"type": "elementType",
@@ -6654,6 +6661,7 @@ Map {
"type": "number",
},
},
+ "render": [Function],
},
"Select" => Object {
"$$typeof": Symbol(react.forward_ref),
diff --git a/packages/react/src/components/DataTable/Table.tsx b/packages/react/src/components/DataTable/Table.tsx
index c6fed5ca0ea8..821253bdb069 100644
--- a/packages/react/src/components/DataTable/Table.tsx
+++ b/packages/react/src/components/DataTable/Table.tsx
@@ -11,6 +11,7 @@ import React, {
useRef,
useCallback,
useLayoutEffect,
+ useState,
} from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
@@ -111,6 +112,7 @@ export const Table = ({
}: PropsWithChildren) => {
const { titleId, descriptionId } = useContext(TableContext);
const prefix = usePrefix();
+ const [isScrollable, setIsScrollable] = useState(false);
const tableRef = useRef(null);
const componentClass = cx(`${prefix}--data-table`, className, {
[`${prefix}--data-table--${size}`]: size,
@@ -185,6 +187,26 @@ export const Table = ({
useWindowEvent('resize', debouncedSetTableAlignment);
+ // Used to set a tabIndex when the Table is horizontally scrollable
+ const setTabIndex = useCallback(() => {
+ const tableContainer = tableRef?.current?.parentNode as HTMLElement;
+ const tableHeader = tableRef?.current?.firstChild as HTMLElement;
+
+ if (tableHeader?.scrollWidth > tableContainer?.clientWidth) {
+ setIsScrollable(true);
+ } else {
+ setIsScrollable(false);
+ }
+ }, []);
+
+ const debouncedSetTabIndex = debounce(setTabIndex, 100);
+
+ useWindowEvent('resize', debouncedSetTabIndex);
+
+ useLayoutEffect(() => {
+ setTabIndex();
+ }, [setTabIndex]);
+
// recalculate table alignment once fonts have loaded
if (
typeof document !== 'undefined' &&
@@ -201,7 +223,10 @@ export const Table = ({
}, [setTableAlignment, size]);
const table = (
-
+
(
id="default"
titleText="Dropdown label"
helperText="This is some helper text"
- label="Dropdown menu options"
+ initialSelectedItem={items[1]}
+ label="Option 1"
items={items}
itemToString={(item) => (item ? item.text : '')}
{...args}
@@ -163,7 +164,8 @@ export const Default = () => (
id="default"
titleText="Dropdown label"
helperText="This is some helper text"
- label="Dropdown menu options"
+ initialSelectedItem={items[1]}
+ label="Option 1"
items={items}
itemToString={(item) => (item ? item.text : '')}
/>
@@ -175,7 +177,8 @@ export const Inline = () => (
(item ? item.text : '')}
@@ -191,7 +194,8 @@ export const _WithLayer = () => (
id={`default-${layer}`}
titleText="Dropdown label"
helperText="This is some helper text"
- label="Dropdown menu options"
+ initialSelectedItem={items[1]}
+ label="Option 1"
items={items}
itemToString={(item) => (item ? item.text : '')}
/>
@@ -207,7 +211,8 @@ export const InlineWithLayer = () => (
(item ? item.text : '')}
diff --git a/packages/react/src/components/Heading/index.tsx b/packages/react/src/components/Heading/index.tsx
index 138d8886546a..4c0dea11580a 100644
--- a/packages/react/src/components/Heading/index.tsx
+++ b/packages/react/src/components/Heading/index.tsx
@@ -22,21 +22,26 @@ type SectionProps = PolymorphicProps<
SectionBaseProps
>;
-export function Section({
- as: BaseComponent = 'section' as E,
- level: levelOverride,
- ...rest
-}: SectionProps) {
+export const Section = React.forwardRef(function Section<
+ E extends ElementType = 'section'
+>(
+ {
+ as: BaseComponent = 'section' as E,
+ level: levelOverride,
+ ...rest
+ }: SectionProps,
+ ref: React.Ref
+) {
const parentLevel = React.useContext(HeadingContext);
const level = levelOverride ?? parentLevel + 1;
const BaseComponentAsAny = BaseComponent as any;
return (
-
+
);
-}
+});
Section.propTypes = {
/**
@@ -63,10 +68,13 @@ Section.propTypes = {
type HeadingProps = JSX.IntrinsicElements[`h${HeadingLevel}`];
-export function Heading(props: HeadingProps) {
+export const Heading = React.forwardRef(function Heading(
+ props: HeadingProps,
+ ref: React.Ref
+) {
const HeadingIntrinsic = `h${React.useContext(HeadingContext)}` as const;
- return ;
-}
+ return ;
+});
Heading.propTypes = {
/**
diff --git a/packages/react/src/components/Icon/Icon.Skeleton.js b/packages/react/src/components/Icon/Icon.Skeleton.tsx
similarity index 67%
rename from packages/react/src/components/Icon/Icon.Skeleton.js
rename to packages/react/src/components/Icon/Icon.Skeleton.tsx
index 8c7947c52ec6..f2ce86af20fc 100644
--- a/packages/react/src/components/Icon/Icon.Skeleton.js
+++ b/packages/react/src/components/Icon/Icon.Skeleton.tsx
@@ -6,18 +6,22 @@
*/
import PropTypes from 'prop-types';
-import React from 'react';
+import React, { ComponentProps } from 'react';
import cx from 'classnames';
import { usePrefix } from '../../internal/usePrefix';
-const IconSkeleton = ({ className, ...rest }) => {
+export interface IconSkeletonProps extends ComponentProps<'div'> {
+ /**
+ * Specify an optional className to add.
+ */
+ className?: string;
+}
+
+const IconSkeleton = ({ className, ...rest }: IconSkeletonProps) => {
const prefix = usePrefix();
- const props = {
- ...rest,
- };
return (
-
+
);
};
diff --git a/packages/react/src/components/Icon/index.js b/packages/react/src/components/Icon/index.ts
similarity index 100%
rename from packages/react/src/components/Icon/index.js
rename to packages/react/src/components/Icon/index.ts
diff --git a/packages/react/src/components/Layer/index.js b/packages/react/src/components/Layer/index.js
index 003160c1c446..e3374a4e485f 100644
--- a/packages/react/src/components/Layer/index.js
+++ b/packages/react/src/components/Layer/index.js
@@ -26,13 +26,16 @@ export function useLayer() {
};
}
-export function Layer({
- as: BaseComponent = 'div',
- className: customClassName,
- children,
- level: overrideLevel,
- ...rest
-}) {
+export const Layer = React.forwardRef(function Layer(
+ {
+ as: BaseComponent = 'div',
+ className: customClassName,
+ children,
+ level: overrideLevel,
+ ...rest
+ },
+ ref
+) {
const contextLevel = React.useContext(LayerContext);
const level = overrideLevel ?? contextLevel;
const prefix = usePrefix();
@@ -42,12 +45,12 @@ export function Layer({
return (
-
+
{children}
);
-}
+});
Layer.propTypes = {
/**
diff --git a/packages/react/src/components/MultiSelect/FilterableMultiSelect.js b/packages/react/src/components/MultiSelect/FilterableMultiSelect.js
index bf1c9b2033dc..07f235035fa1 100644
--- a/packages/react/src/components/MultiSelect/FilterableMultiSelect.js
+++ b/packages/react/src/components/MultiSelect/FilterableMultiSelect.js
@@ -163,9 +163,7 @@ const FilterableMultiSelect = React.forwardRef(function FilterableMultiSelect(
case stateChangeTypes.keyDownHome:
case stateChangeTypes.keyDownEnd:
setHighlightedIndex(
- changes.highlightedIndex !== undefined
- ? changes.highlightedIndex
- : null
+ changes.highlightedIndex !== undefined ? changes.highlightedIndex : 0
);
if (stateChangeTypes.keyDownArrowDown === type && !isOpen) {
handleOnMenuChange(true);
diff --git a/packages/react/src/components/MultiSelect/MultiSelect.tsx b/packages/react/src/components/MultiSelect/MultiSelect.tsx
index 45df6ad4644f..127191bdff49 100644
--- a/packages/react/src/components/MultiSelect/MultiSelect.tsx
+++ b/packages/react/src/components/MultiSelect/MultiSelect.tsx
@@ -499,6 +499,12 @@ const MultiSelect = React.forwardRef(
);
props.scrollIntoView(itemArray[highlightedIndex]);
}
+ if (highlightedIndex === -1) {
+ return {
+ ...changes,
+ highlightedIndex: 0,
+ };
+ }
return changes;
case ItemMouseMove:
return { ...changes, highlightedIndex: state.highlightedIndex };
diff --git a/packages/react/src/components/Tag/Tag-test.js b/packages/react/src/components/Tag/Tag-test.js
index 74d75da7cd47..8de524d9efac 100644
--- a/packages/react/src/components/Tag/Tag-test.js
+++ b/packages/react/src/components/Tag/Tag-test.js
@@ -30,16 +30,16 @@ describe('Tag', () => {
it('should have an appropriate aria-label when (filterable)', () => {
const children = 'tag-3';
const { container } = render(
-
+
{children}
);
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
- const button = container.querySelector('[aria-labelledby]');
- const accessibilityLabel = button.getAttribute('aria-labelledby');
+ const button = container.querySelector('[aria-label]');
+ const accessibilityLabel = button.getAttribute('aria-label');
// This check would mirror our "Accessibility label must contain at least all of visible label"
// requirement
- expect(accessibilityLabel).toEqual(expect.stringContaining(children));
+ expect(accessibilityLabel).toEqual(expect.stringContaining('Close tag'));
});
it('should allow for a custom label', () => {
diff --git a/packages/react/src/components/Tag/Tag.Skeleton.tsx b/packages/react/src/components/Tag/Tag.Skeleton.tsx
index e9a2e21d7f7e..a9e2819351af 100644
--- a/packages/react/src/components/Tag/Tag.Skeleton.tsx
+++ b/packages/react/src/components/Tag/Tag.Skeleton.tsx
@@ -7,7 +7,7 @@
import PropTypes from 'prop-types';
import React from 'react';
-import cx from 'classnames';
+import classNames from 'classnames';
import { usePrefix } from '../../internal/usePrefix';
export interface TagSkeletonProps
@@ -26,14 +26,16 @@ export interface TagSkeletonProps
function TagSkeleton({ className, size, ...rest }: TagSkeletonProps) {
const prefix = usePrefix();
- return (
-
+ const tagClasses = classNames(
+ `${prefix}--tag`,
+ `${prefix}--skeleton`,
+ className,
+ {
+ [`${prefix}--tag--${size}`]: size, // TODO: V12 - Remove this class
+ [`${prefix}--layout--size-${size}`]: size,
+ }
);
+ return ;
}
TagSkeleton.propTypes = {
diff --git a/packages/react/src/components/Tag/Tag.stories.js b/packages/react/src/components/Tag/Tag.stories.js
index d1b2e44009f2..20af2d55aaf1 100644
--- a/packages/react/src/components/Tag/Tag.stories.js
+++ b/packages/react/src/components/Tag/Tag.stories.js
@@ -7,6 +7,7 @@
import React from 'react';
import { default as Tag } from '../Tag';
+import TagSkeleton from '../Tag/Tag.Skeleton';
export default {
title: 'Components/Tag',
@@ -121,3 +122,73 @@ Playground.argTypes = {
},
},
};
+
+export const Skeleton = (args) => (
+
+
+
+);
+
+Skeleton.args = {
+ size: 'md',
+};
+
+Skeleton.argTypes = {
+ children: {
+ table: {
+ disable: true,
+ },
+ },
+ as: {
+ table: {
+ disable: true,
+ },
+ },
+ className: {
+ table: {
+ disable: true,
+ },
+ },
+ disabled: {
+ table: {
+ disable: true,
+ },
+ },
+
+ filter: {
+ table: {
+ disable: true,
+ },
+ },
+ id: {
+ table: {
+ disable: true,
+ },
+ },
+ onClose: {
+ table: {
+ disable: true,
+ },
+ },
+ renderIcon: {
+ table: {
+ disable: true,
+ },
+ },
+ title: {
+ table: {
+ disable: true,
+ },
+ },
+ type: {
+ table: {
+ disable: true,
+ },
+ },
+ size: {
+ options: ['sm', 'md'],
+ control: {
+ type: 'select',
+ },
+ },
+};
diff --git a/packages/react/src/components/Tag/Tag.tsx b/packages/react/src/components/Tag/Tag.tsx
index fb2cee7777ad..2323dcbbae3f 100644
--- a/packages/react/src/components/Tag/Tag.tsx
+++ b/packages/react/src/components/Tag/Tag.tsx
@@ -95,7 +95,7 @@ const Tag = ({
type,
filter,
renderIcon: CustomIconElement,
- title,
+ title = 'Clear filter',
disabled,
onClose,
size,
@@ -137,7 +137,7 @@ const Tag = ({
className={`${prefix}--tag__close-icon`}
onClick={handleClose}
disabled={disabled}
- aria-labelledby={tagId}
+ aria-label={title}
title={title}>
diff --git a/packages/react/src/components/Theme/index.tsx b/packages/react/src/components/Theme/index.tsx
index 691363210020..f99e23c4118c 100644
--- a/packages/react/src/components/Theme/index.tsx
+++ b/packages/react/src/components/Theme/index.tsx
@@ -14,25 +14,34 @@ import { LayerContext } from '../Layer/LayerContext';
interface GlobalThemeProps {
theme?: 'white' | 'g10' | 'g90' | 'g100';
+ children?: React.ReactNode;
}
export const ThemeContext = React.createContext({
theme: 'white',
});
-export function GlobalTheme({
- children,
- theme,
-}: PropsWithChildren) {
+export const GlobalTheme = React.forwardRef(function GlobalTheme(
+ { children, theme }: PropsWithChildren,
+ ref: React.Ref
+) {
const value = useMemo(() => {
return {
theme,
};
}, [theme]);
+
+ const childrenWithProps = React.cloneElement(
+ children as React.ReactElement,
+ { ref: ref }
+ );
+
return (
- {children}
+
+ {childrenWithProps}
+
);
-}
+});
GlobalTheme.propTypes = {
/**
diff --git a/packages/react/src/components/UIShell/HeaderMenu.js b/packages/react/src/components/UIShell/HeaderMenu.js
index 37d551e86291..664daf7b697e 100644
--- a/packages/react/src/components/UIShell/HeaderMenu.js
+++ b/packages/react/src/components/UIShell/HeaderMenu.js
@@ -136,25 +136,22 @@ class HeaderMenu extends React.Component {
/**
* Handle our blur event from our underlying menuitems. Will mostly be used
- * for toggling the expansion status of our menu in response to a user
- * clicking off of the menu or menubar.
+ * for closing our menu in response to a user clicking off or tabbing out of
+ * the menu or menubar.
*/
handleOnBlur = (event) => {
- // Rough guess for a blur event that is triggered outside of our menu or
- // menubar context
- const itemTriggeredBlur = this.items.find(
+ // Close the menu on blur when the related target is not a sibling menu item
+ // or a child in a submenu
+ const siblingItemBlurredTo = this.items.find(
(element) => element === event.relatedTarget
);
- if (
- event.relatedTarget &&
- ((event.relatedTarget.getAttribute('href') &&
- event.relatedTarget.getAttribute('href') !== '#') ||
- itemTriggeredBlur)
- ) {
- return;
- }
+ const childItemBlurredTo = this._subMenus.current?.contains(
+ event.relatedTarget
+ );
- this.setState({ expanded: false, selectedIndex: null });
+ if (!siblingItemBlurredTo && !childItemBlurredTo) {
+ this.setState({ expanded: false, selectedIndex: null });
+ }
};
/**
diff --git a/packages/styles/scss/components/data-table/_data-table.scss b/packages/styles/scss/components/data-table/_data-table.scss
index 602bef2d0e0e..4e57d3876f16 100644
--- a/packages/styles/scss/components/data-table/_data-table.scss
+++ b/packages/styles/scss/components/data-table/_data-table.scss
@@ -39,6 +39,10 @@
overflow-x: auto;
}
+ .#{$prefix}--data-table-content:focus {
+ @include focus-outline('outline');
+ }
+
//----------------------------------------------------------------------------
// Table title text
//----------------------------------------------------------------------------
diff --git a/packages/styles/scss/components/tag/_tag.scss b/packages/styles/scss/components/tag/_tag.scss
index 71e6be20111f..d6b161a39111 100644
--- a/packages/styles/scss/components/tag/_tag.scss
+++ b/packages/styles/scss/components/tag/_tag.scss
@@ -17,7 +17,7 @@
@use '../../spacing' as *;
@use './tokens' as *;
@use './mixins' as *;
-@use '../../utilities/skeleton';
+@use '../../utilities/skeleton' as *;
/// Tag styles
/// @access public
@@ -250,6 +250,8 @@
// Skeleton state
.#{$prefix}--tag.#{$prefix}--skeleton {
+ @include skeleton;
+
@include tag-theme(
$bg-color: $skeleton-background,
$text-color: $text-primary
diff --git a/packages/themes/src/component-tokens/tag/tokens.js b/packages/themes/src/component-tokens/tag/tokens.js
index 57ca80293fcf..da62a0758f04 100644
--- a/packages/themes/src/component-tokens/tag/tokens.js
+++ b/packages/themes/src/component-tokens/tag/tokens.js
@@ -2,52 +2,52 @@ import {
red20,
red70,
red80,
- red30,
+ red20Hover,
red70Hover,
magenta20,
magenta70,
magenta80,
- magenta30,
+ magenta20Hover,
magenta70Hover,
purple20,
purple70,
purple80,
- purple30,
+ purple20Hover,
purple70Hover,
blue20,
blue70,
blue80,
- blue30,
+ blue20Hover,
blue70Hover,
cyan20,
cyan70,
cyan80,
- cyan30,
+ cyan20Hover,
cyan70Hover,
teal20,
teal70,
teal80,
- teal30,
+ teal20Hover,
teal70Hover,
green20,
green70,
green80,
- green30,
+ green20Hover,
green70Hover,
gray20,
gray70,
gray80,
- gray30,
+ gray20Hover,
gray70Hover,
warmGray20,
warmGray70,
warmGray80,
- warmGray30,
+ warmGray20Hover,
warmGray70Hover,
coolGray20,
coolGray70,
coolGray80,
- coolGray30,
+ coolGray20Hover,
coolGray70Hover,
} from '@carbon/colors';
@@ -66,8 +66,8 @@ export const tagColorRed = {
};
export const tagHoverRed = {
- whiteTheme: red30,
- g10: red30,
+ whiteTheme: red20Hover,
+ g10: red20Hover,
g90: red70Hover,
g100: red70Hover,
};
@@ -87,8 +87,8 @@ export const tagColorMagenta = {
};
export const tagHoverMagenta = {
- whiteTheme: magenta30,
- g10: magenta30,
+ whiteTheme: magenta20Hover,
+ g10: magenta20Hover,
g90: magenta70Hover,
g100: magenta70Hover,
};
@@ -108,8 +108,8 @@ export const tagColorPurple = {
};
export const tagHoverPurple = {
- whiteTheme: purple30,
- g10: purple30,
+ whiteTheme: purple20Hover,
+ g10: purple20Hover,
g90: purple70Hover,
g100: purple70Hover,
};
@@ -129,8 +129,8 @@ export const tagColorBlue = {
};
export const tagHoverBlue = {
- whiteTheme: blue30,
- g10: blue30,
+ whiteTheme: blue20Hover,
+ g10: blue20Hover,
g90: blue70Hover,
g100: blue70Hover,
};
@@ -150,8 +150,8 @@ export const tagColorCyan = {
};
export const tagHoverCyan = {
- whiteTheme: cyan30,
- g10: cyan30,
+ whiteTheme: cyan20Hover,
+ g10: cyan20Hover,
g90: cyan70Hover,
g100: cyan70Hover,
};
@@ -169,8 +169,8 @@ export const tagColorTeal = {
};
export const tagHoverTeal = {
- whiteTheme: teal30,
- g10: teal30,
+ whiteTheme: teal20Hover,
+ g10: teal20Hover,
g90: teal70Hover,
g100: teal70Hover,
};
@@ -190,8 +190,8 @@ export const tagColorGreen = {
};
export const tagHoverGreen = {
- whiteTheme: green30,
- g10: green30,
+ whiteTheme: green20Hover,
+ g10: green20Hover,
g90: green70Hover,
g100: green70Hover,
};
@@ -211,8 +211,8 @@ export const tagColorGray = {
};
export const tagHoverGray = {
- whiteTheme: gray30,
- g10: gray30,
+ whiteTheme: gray20Hover,
+ g10: gray20Hover,
g90: gray70Hover,
g100: gray70Hover,
};
@@ -232,8 +232,8 @@ export const tagColorCoolGray = {
};
export const tagHoverCoolGray = {
- whiteTheme: coolGray30,
- g10: coolGray30,
+ whiteTheme: coolGray20Hover,
+ g10: coolGray20Hover,
g90: coolGray70Hover,
g100: coolGray70Hover,
};
@@ -253,8 +253,8 @@ export const tagColorWarmGray = {
};
export const tagHoverWarmGray = {
- whiteTheme: warmGray30,
- g10: warmGray30,
+ whiteTheme: warmGray20Hover,
+ g10: warmGray20Hover,
g90: warmGray70Hover,
g100: warmGray70Hover,
};