diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index baa772c0b6..c920c2fbc6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -293,11 +293,11 @@ export class Component { @Prop() status?: string; - @Event() icComponentThemeChange!: EventEmitter<{ mode: string }>; + @Event() icComponentBrandChange!: EventEmitter<{ mode: string }>; - @Listen("themeChange", { target: "document" }) - themeChangeHandler(ev: CustomEvent): void { - this.theme = ev.detail.mode; + @Listen("brandChange", { target: "document" }) + brandChangeHandler(ev: CustomEvent): void { + this.brand = ev.detail.mode; } @Method() @@ -305,14 +305,14 @@ export class Component { this.label = label; } - private theme: IcTheme; + private brand: IcBrand; private updateStatus(status: string) { this.status = status; } private clickHandler() { - this.icComponentThemeChange.emit({ + this.icComponentBrandChange.emit({ mode: "dark" }); } @@ -370,9 +370,9 @@ it('tests receiving custom events', async () => { html: `` }); - await page.rootInstance.themeChangeHandler({ detail: { mode: "bar" } }); + await page.rootInstance.brandChangeHandler({ detail: { mode: "bar" } }); await page.waitForChanges(); - expect(page.rootInstance.theme).toBe("bar"); + expect(page.rootInstance.brand).toBe("bar"); }); // Testing events emitted from the component @@ -383,9 +383,9 @@ it('tests emitted events', async () => { }); const callbackFn = jest.fn(); - page.win.addEventListener('icComponentThemeChange', callbackFn); + page.win.addEventListener('icComponentBrandChange', callbackFn); - // clickHandler emits the icComponentThemeChange event + // clickHandler emits the icComponentBrandChange event await page.rootInstance.clickHandler(); await page.waitForChanges(); expect(callbackFn).toHaveBeenCalled(); diff --git a/package-lock.json b/package-lock.json index 2f7861b622..3544168d06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,7 +4,6 @@ "requires": true, "packages": { "": { - "name": "ic-ui-kit", "devDependencies": { "@commitlint/cli": "^17.3.0", "@commitlint/config-conventional": "^17.3.0", @@ -10690,9 +10689,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { @@ -24078,9 +24077,9 @@ "dev": true }, "nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true }, "natural-compare": { diff --git a/package.json b/package.json index d183eb3afc..02ba087c2a 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "build:all": "npm run build && npm run build:canary", "release-check": "lerna version --no-push --no-git-tag-version", "storybook": "lerna run storybook --stream --parallel", + "storybook:core": "lerna run storybook --scope @ukic/web-components --scope @ukic/react --stream --parallel", "storybook:canary": "lerna run storybook --scope=@ukic/canary-* --stream --parallel", "build-storybook:web-components": "lerna run build-storybook --scope @ukic/web-components", "build-storybook:react": "lerna run build-storybook --scope @ukic/react", diff --git a/packages/canary-docs/docs.json b/packages/canary-docs/docs.json index 57310ead6f..e622244dfd 100644 --- a/packages/canary-docs/docs.json +++ b/packages/canary-docs/docs.json @@ -376,7 +376,7 @@ "passive": false }, { - "event": "themeChange", + "event": "brandChange", "target": "document", "capture": false, "passive": false @@ -3882,6 +3882,27 @@ ], "optional": true, "required": false + }, + { + "name": "truncateTreeItem", + "type": "boolean", + "complexType": { + "original": "boolean", + "resolved": "boolean", + "references": {} + }, + "mutable": false, + "attr": "truncate-tree-item", + "reflectToAttr": false, + "docs": "If `true`, the tree item label will be truncated instead of text wrapping.", + "docsTags": [], + "values": [ + { + "type": "boolean" + } + ], + "optional": true, + "required": false } ], "methods": [ @@ -4097,6 +4118,50 @@ ], "optional": true, "required": false + }, + { + "name": "truncateHeading", + "type": "boolean", + "complexType": { + "original": "boolean", + "resolved": "boolean", + "references": {} + }, + "mutable": false, + "attr": "truncate-heading", + "reflectToAttr": false, + "docs": "If `true`, the tree view heading will be truncated instead of text wrapping.", + "docsTags": [], + "default": "false", + "values": [ + { + "type": "boolean" + } + ], + "optional": true, + "required": false + }, + { + "name": "truncateTreeItems", + "type": "boolean", + "complexType": { + "original": "boolean", + "resolved": "boolean", + "references": {} + }, + "mutable": false, + "attr": "truncate-tree-items", + "reflectToAttr": false, + "docs": "If `true`, tree items will be truncated, unless they are individually overridden.", + "docsTags": [], + "default": "false", + "values": [ + { + "type": "boolean" + } + ], + "optional": true, + "required": false } ], "methods": [], diff --git a/packages/canary-react/cypress-image-diff-screenshots/baseline/IcDataTable.cy.tsx-action-elements.png b/packages/canary-react/cypress-image-diff-screenshots/baseline/IcDataTable.cy.tsx-action-elements.png new file mode 100644 index 0000000000..20184e36d6 Binary files /dev/null and b/packages/canary-react/cypress-image-diff-screenshots/baseline/IcDataTable.cy.tsx-action-elements.png differ diff --git a/packages/canary-react/cypress-image-diff-screenshots/baseline/IcTreeView.cy.tsx-text-wrapping.png b/packages/canary-react/cypress-image-diff-screenshots/baseline/IcTreeView.cy.tsx-text-wrapping.png new file mode 100755 index 0000000000..e010dcddc9 Binary files /dev/null and b/packages/canary-react/cypress-image-diff-screenshots/baseline/IcTreeView.cy.tsx-text-wrapping.png differ diff --git a/packages/canary-react/package-lock.json b/packages/canary-react/package-lock.json index 69b4e6947d..f165e32a66 100644 --- a/packages/canary-react/package-lock.json +++ b/packages/canary-react/package-lock.json @@ -17670,7 +17670,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.7", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { @@ -17678,7 +17680,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, diff --git a/packages/canary-react/src/component-tests/IcDataTable/IcDataTable.cy.tsx b/packages/canary-react/src/component-tests/IcDataTable/IcDataTable.cy.tsx index 6a1435fc0a..3ded0529fc 100644 --- a/packages/canary-react/src/component-tests/IcDataTable/IcDataTable.cy.tsx +++ b/packages/canary-react/src/component-tests/IcDataTable/IcDataTable.cy.tsx @@ -42,6 +42,7 @@ import { textWrapColumns, textWrapRow, DATA_EMPHASIS, + ACTION_DATA_ELEMENTS, } from "@ukic/canary-web-components/src/components/ic-data-table/story-data"; import { @@ -579,7 +580,7 @@ describe("IcDataTables", () => { cy.checkHydrated(DATA_TABLE_SELECTOR); cy.wait(350).compareSnapshot({ name: "loading-options", - testThreshold: setThresholdBasedOnEnv(0.026), + testThreshold: setThresholdBasedOnEnv(0.029), cypressScreenshotOptions: { capture: "viewport", }, @@ -1084,6 +1085,75 @@ describe("IcDataTables", () => { sorted: "descending", }); }); + + it("should render an element in the table cell if the data prop contains the actionElement key", () => { + mount( + + ); + + cy.checkHydrated(DATA_TABLE_SELECTOR); + + cy.findShadowEl(DATA_TABLE_SELECTOR, "td") + .eq(0) + .find("span") + .should(HAVE_CLASS, "action-element") + .find("ic-button") + .should("be.visible"); + + cy.checkA11yWithWait(undefined, 300); + + cy.compareSnapshot({ + name: "action-elements", + testThreshold: setThresholdBasedOnEnv(DEFAULT_THRESHOLD + 0.038), + cypressScreenshotOptions: { + capture: "viewport", + }, + }); + }); + + it("should not render an element in the table cell if the data prop does not contain the actionElement key", () => { + mount( + + ); + + cy.checkHydrated(DATA_TABLE_SELECTOR); + + cy.findShadowEl(DATA_TABLE_SELECTOR, "td") + .eq(1) + .find("span") + .should("not.exist"); + }); + + it("should apply styling to the cell container if an action element is present in the cell", () => { + mount( + + ); + + cy.checkHydrated(DATA_TABLE_SELECTOR); + + cy.findShadowEl(DATA_TABLE_SELECTOR, "td") + .eq(0) + .find("div") + .eq(0) + .should(HAVE_CLASS, "cell-grid-wrapper") + .should(HAVE_CSS, "grid-template-columns", "156.797px 32px"); + + cy.findShadowEl(DATA_TABLE_SELECTOR, "span") + .should(HAVE_CLASS, "action-element") + .should(HAVE_CSS, "justify-content", "right"); + }); }); describe("IcDataTables with IcPaginationBar", () => { diff --git a/packages/canary-react/src/component-tests/IcTreeView/IcTreeView.cy.tsx b/packages/canary-react/src/component-tests/IcTreeView/IcTreeView.cy.tsx index 30b206569f..3eb480fe2c 100644 --- a/packages/canary-react/src/component-tests/IcTreeView/IcTreeView.cy.tsx +++ b/packages/canary-react/src/component-tests/IcTreeView/IcTreeView.cy.tsx @@ -199,7 +199,7 @@ describe("IcTreeView", () => { }); }); - it("should render with truncated text", () => { + it("should render with text wrapping", () => { mount(
{
); + cy.findShadowEl(TREE_ITEM, TREE_ITEM_CONTENT).eq(1).click(); + + cy.findShadowEl(TREE_ITEM, TREE_ITEM_CONTENT).eq(2).click(); + + cy.checkA11yWithWait(); + cy.compareSnapshot({ + name: "text-wrapping", + testThreshold: setThresholdBasedOnEnv(DEFAULT_TEST_THRESHOLD + 0.01), + }); + }); + + it("should render with truncated text", () => { + mount( +
+ + + + + + + + +
+ ); + const TEXT_OVERFLOW = "text-overflow"; const ELLIPSIS = "ellipsis"; diff --git a/packages/canary-react/src/stories/ic-data-table.stories.mdx b/packages/canary-react/src/stories/ic-data-table.stories.mdx index 3396136f8a..b0fe64bec4 100644 --- a/packages/canary-react/src/stories/ic-data-table.stories.mdx +++ b/packages/canary-react/src/stories/ic-data-table.stories.mdx @@ -27,7 +27,8 @@ import { ICON_COLS, COLUMNS_TEXT_WRAP, TEXT_WRAP_LONG_DATA, - COLS_WIDTH + COLS_WIDTH, + ACTION_DATA_ELEMENTS } from "../../../canary-web-components/src/components/ic-data-table/story-data"; import { mdiAccountGroup, mdiImage, mdiDelete } from "@mdi/js"; @@ -2074,6 +2075,58 @@ const DataTable = () => { }; ``` +### Action Element + +An example with action elements passed in via the data + + + + + + + +#### Action Element code example + +```jsx +import * as React from "react"; +import { IcDataTable } from "@ukic/canary-react"; +import { IcDataTableColumnObject } from "@ukic/canary-web-components"; + +const COLS: IcDataTableColumnObject[] = [ + { key: "firstName", title: "First name", dataType: "string", columnWidth: "15%" }, + { key: "lastName", title: "Last name", dataType: "string", columnWidth: "300px" }, + { + key: "age", + title: "Age", + dataType: "number", + columnWidth: { + maxWidth: "100px", + }, + }, + ... +]; + +const DATA = [ + { + firstName: { + data: "Joe", + actionElement: ` `, + }, + lastName: "Bloggs", + age: 30, + jobTitle: "Developer", + address: "1 Main Street, Town, County, Postcode", + }, + ... +]; + +const DataTable = () => ( + +); + +export default DataTable; +``` + ### Playground example Go to the separate page for the playground example to view the prop controls. diff --git a/packages/canary-react/src/stories/ic-tree-view.stories.mdx b/packages/canary-react/src/stories/ic-tree-view.stories.mdx index 597000be27..8d74726f1f 100644 --- a/packages/canary-react/src/stories/ic-tree-view.stories.mdx +++ b/packages/canary-react/src/stories/ic-tree-view.stories.mdx @@ -565,7 +565,7 @@ export default TreeView; ### Max content -An example with max content. When the heading/label exceeds the width of the container, it will be truncated with an ellipsis. +An example with max content. When the heading/label exceeds the width of the container, it will text wrap unless truncate-tree-item is set. @@ -639,6 +639,83 @@ const TreeView = () => ( export default TreeView; ``` +### Truncation behaviour + +An example with truncated tree items. When the heading/label exceeds the width of the container, it will be truncated with an ellipsis. + + + +
+ + + + + + + + + + + + + + + + + + + +
+
+
+ +#### Truncation behaviour code example + +```jsx +import * as React from "react"; +import { IcTreeView, IcTreeItem } from "@ukic/canary-react"; +import { SlottedSVG } from "@ukic/react"; + +const TreeView = () => ( + + + + + + + + + + + + + + + + + + +) + +export default TreeView; +``` + ### Selected tree item An example where the tree item with `selected="true"` is selected on initial render. @@ -856,6 +933,8 @@ export const defaultArgs = { heading: "Menu", size: "medium", showIcon: false, + truncateHeading: false, + truncateTreeItems: false, treeItemDisabled: false, treeItemHref: "", treeItemLabel: "Coffee", @@ -886,6 +965,8 @@ export const defaultArgs = { size={args.size} focusInset={args.focusInset} theme={args.theme} + truncateHeading={args.truncateHeading} + truncateTreeItems={args.truncateTreeItems} > {args.showIcon && ( diff --git a/packages/canary-web-components/package-lock.json b/packages/canary-web-components/package-lock.json index 488bee8196..283e6e2372 100644 --- a/packages/canary-web-components/package-lock.json +++ b/packages/canary-web-components/package-lock.json @@ -18657,9 +18657,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { diff --git a/packages/canary-web-components/src/components.d.ts b/packages/canary-web-components/src/components.d.ts index 1486a4663b..0068e5b590 100644 --- a/packages/canary-web-components/src/components.d.ts +++ b/packages/canary-web-components/src/components.d.ts @@ -550,6 +550,10 @@ export namespace Components { * Sets the theme color to the dark or light theme color. "inherit" will set the color based on the system settings or ic-theme component. */ "theme"?: IcThemeMode; + /** + * If `true`, the tree item label will be truncated instead of text wrapping. + */ + "truncateTreeItem"?: boolean; "updateAriaLabel": () => Promise; } interface IcTreeView { @@ -569,6 +573,14 @@ export namespace Components { * Sets the theme color to the dark or light theme color. "inherit" will set the color based on the system settings or ic-theme component. */ "theme"?: IcThemeMode; + /** + * If `true`, the tree view heading will be truncated instead of text wrapping. + */ + "truncateHeading"?: boolean; + /** + * If `true`, tree items will be truncated, unless they are individually overridden. + */ + "truncateTreeItems"?: boolean; } } export interface IcDataTableCustomEvent extends CustomEvent { @@ -1282,6 +1294,10 @@ declare namespace LocalJSX { * Sets the theme color to the dark or light theme color. "inherit" will set the color based on the system settings or ic-theme component. */ "theme"?: IcThemeMode; + /** + * If `true`, the tree item label will be truncated instead of text wrapping. + */ + "truncateTreeItem"?: boolean; } interface IcTreeView { /** @@ -1300,6 +1316,14 @@ declare namespace LocalJSX { * Sets the theme color to the dark or light theme color. "inherit" will set the color based on the system settings or ic-theme component. */ "theme"?: IcThemeMode; + /** + * If `true`, the tree view heading will be truncated instead of text wrapping. + */ + "truncateHeading"?: boolean; + /** + * If `true`, tree items will be truncated, unless they are individually overridden. + */ + "truncateTreeItems"?: boolean; } interface IntrinsicElements { "ic-card-horizontal": IcCardHorizontal; diff --git a/packages/canary-web-components/src/components/ic-card-horizontal/ic-card-horizontal.css b/packages/canary-web-components/src/components/ic-card-horizontal/ic-card-horizontal.css index 686e349f75..5a5ce3e6d6 100644 --- a/packages/canary-web-components/src/components/ic-card-horizontal/ic-card-horizontal.css +++ b/packages/canary-web-components/src/components/ic-card-horizontal/ic-card-horizontal.css @@ -145,9 +145,12 @@ button { .image { display: flex; + border-radius: var(--ic-border-radius); +} + +.image ::slotted([slot="image"]) { height: var(--image-size); width: var(--image-size); - border-radius: var(--ic-border-radius); } .card-content { diff --git a/packages/canary-web-components/src/components/ic-card-horizontal/ic-card-horizontal.tsx b/packages/canary-web-components/src/components/ic-card-horizontal/ic-card-horizontal.tsx index de025a0341..87650ff74b 100644 --- a/packages/canary-web-components/src/components/ic-card-horizontal/ic-card-horizontal.tsx +++ b/packages/canary-web-components/src/components/ic-card-horizontal/ic-card-horizontal.tsx @@ -12,14 +12,14 @@ import { import { onComponentRequiredPropUndefined, isSlotUsed, - getThemeFromContext, + getBrandFromContext, removeDisabledFalse, checkSlotInChildMutations, } from "../../utils/helpers"; import { - IcTheme, - IcThemeForeground, - IcThemeForegroundEnum, + IcBrand, + IcBrandForeground, + IcBrandForegroundEnum, IcThemeMode, } from "../../utils/types"; import { IcCardSizes } from "./ic-card-horizontal.types"; @@ -41,7 +41,7 @@ export class Card { @Element() el: HTMLIcCardHorizontalElement; - @State() appearance?: IcThemeForeground = "default"; + @State() appearance?: IcBrandForeground = "default"; @State() isFocussed: boolean = false; @State() parentEl: HTMLElement | null = null; @State() parentIsAnchorTag: boolean = false; @@ -143,8 +143,8 @@ export class Card { } } - @Listen("themeChange", { target: "document" }) - themeChangeHandler(ev: CustomEvent): void { + @Listen("brandChange", { target: "document" }) + brandChangeHandler(ev: CustomEvent): void { this.updateTheme(ev.detail.mode); } @@ -180,11 +180,14 @@ export class Card { this.isFocussed = false; }; - private updateTheme(newTheme: IcThemeForeground = null): void { - const foregroundColor = getThemeFromContext(this.el, newTheme); + private updateTheme(mode: IcBrandForeground = null): void { + const foregroundColor = getBrandFromContext(this.el, mode); - if (foregroundColor !== IcThemeForegroundEnum.Default) { - this.theme = foregroundColor; + if (foregroundColor !== IcBrandForegroundEnum.Default) { + this.theme = + foregroundColor === IcBrandForegroundEnum.Light + ? IcBrandForegroundEnum.Dark + : IcBrandForegroundEnum.Light; } } @@ -229,7 +232,7 @@ export class Card { clickable: clickable && !disabled, disabled, focussed: isFocussed, - dark: appearance === IcThemeForegroundEnum.Dark, + dark: appearance === IcBrandForegroundEnum.Dark, [`${size}`]: true, "with-icon": isSlotUsed(this.el, "icon"), "with-image": isSlotUsed(this.el, "image"), diff --git a/packages/canary-web-components/src/components/ic-data-table/ic-data-table.css b/packages/canary-web-components/src/components/ic-data-table/ic-data-table.css index 5c947daf53..708181db12 100644 --- a/packages/canary-web-components/src/components/ic-data-table/ic-data-table.css +++ b/packages/canary-web-components/src/components/ic-data-table/ic-data-table.css @@ -414,6 +414,16 @@ td.table-density-spacious { width: 100%; } +.action-element { + display: flex; + justify-content: right; +} + +.cell-grid-wrapper { + display: grid; + grid-template-columns: auto auto; +} + @media screen and (min-width: 577px) { .column-header-inner-container { display: flex; diff --git a/packages/canary-web-components/src/components/ic-data-table/ic-data-table.stories.mdx b/packages/canary-web-components/src/components/ic-data-table/ic-data-table.stories.mdx index e3ab501195..1ee343bf89 100644 --- a/packages/canary-web-components/src/components/ic-data-table/ic-data-table.stories.mdx +++ b/packages/canary-web-components/src/components/ic-data-table/ic-data-table.stories.mdx @@ -36,6 +36,7 @@ import { UpdatingData, SlottedPagination, LargeDataSet, + ActionElement, DevArea, } from "./story-data"; import readme from "./readme.md"; @@ -1643,6 +1644,16 @@ To set the min and max width of a table cell, set the `table-layout` attribute t ``` +### Action Element + +The example below demonstrates action elements appearing in table cells after being passed in via the data. + + + + {ActionElement()} + + + #### Development Area diff --git a/packages/canary-web-components/src/components/ic-data-table/ic-data-table.tsx b/packages/canary-web-components/src/components/ic-data-table/ic-data-table.tsx index cdb8693487..f1421cee81 100644 --- a/packages/canary-web-components/src/components/ic-data-table/ic-data-table.tsx +++ b/packages/canary-web-components/src/components/ic-data-table/ic-data-table.tsx @@ -398,6 +398,7 @@ export class DataTable { componentDidRender(): void { this.fixCellTooltips(); + this.adjustWidthForActionElement(); } private runHeaderResizeObserver = () => { @@ -1234,6 +1235,126 @@ export class DataTable { return {}; }; + private adjustWidthForActionElement = () => { + const elements = this.el.shadowRoot.querySelectorAll(".action-element"); + elements.forEach((element) => { + const width = (element.firstChild as HTMLElement).getBoundingClientRect() + .width; + const gridWrapper: HTMLElement = element.closest(".cell-grid-wrapper"); + gridWrapper.style.gridTemplateColumns = `auto calc(${width}px + var(--ic-space-xs))`; + }); + }; + + private createCellContent = ( + columnProps: IcDataTableColumnObject, + cell: any, + cellSlotName: string, + rowOptions: any, + rowAlignment: string, + hasIcon: boolean, + currentRowHeight: number, + cellValue: (key: string) => any, + rowEmphasis: string + ) => ( +
+ {isSlotUsed(this.el, cellSlotName) ? ( + + ) : ( + + {isSlotUsed(this.el, `${cellSlotName}-icon`) ? ( + + ) : ( + (hasIcon || columnProps?.icon?.onAllCells) && + (cellValue("icon") || columnProps?.icon?.icon) && ( + + ) + )} + {columnProps?.dataType !== "element" && + !isSlotUsed(this.el, cellSlotName) && ( + + {this.isObject(cell) && columnProps?.dataType !== "date" ? ( + Object.keys(cell).includes("href") ? ( + + {cellValue("data")} + + ) : ( + cellValue("data") + ) + ) : ( + this.getCellContent(cell, columnProps?.dataType) + )} + + )} + + )} +
+ ); + private createCells = (row: IcDataTableDataType, rowIndex: number) => { const rowValues = Object.values(row); const rowKeys = Object.keys(row); @@ -1292,107 +1413,37 @@ export class DataTable { }} style={{ ...this.getColumnWidth(columnProps.columnWidth) }} > -
+ {this.createCellContent( columnProps, - rowOptions?.textWrap, - cell - ), - ...this.getColumnWidth(columnProps?.columnWidth), - }} - data-row-height={ - this.truncationPattern || currentRowHeight - ? this.setRowHeight(currentRowHeight) - : null - } - > - {isSlotUsed(this.el, cellSlotName) ? ( - - ) : ( - - {isSlotUsed(this.el, `${cellSlotName}-icon`) ? ( - - ) : ( - (hasIcon || columnProps?.icon?.onAllCells) && - (cellValue("icon") || columnProps?.icon?.icon) && ( - - ) - )} - {columnProps?.dataType !== "element" && - !isSlotUsed(this.el, cellSlotName) && ( - - {this.isObject(cell) && - columnProps?.dataType !== "date" ? ( - Object.keys(cell).includes("href") ? ( - - {cellValue("data")} - - ) : ( - cellValue("data") - ) - ) : ( - this.getCellContent(cell, columnProps?.dataType) - )} - - )} - - )} -
+ cell, + cellSlotName, + rowOptions, + rowAlignment, + hasIcon, + currentRowHeight, + cellValue, + rowEmphasis + )} + + + ) : ( + this.createCellContent( + columnProps, + cell, + cellSlotName, + rowOptions, + rowAlignment, + hasIcon, + currentRowHeight, + cellValue, + rowEmphasis + ) + )} ); } diff --git a/packages/canary-web-components/src/components/ic-data-table/story-data.ts b/packages/canary-web-components/src/components/ic-data-table/story-data.ts index 771c811a29..d33d737b32 100644 --- a/packages/canary-web-components/src/components/ic-data-table/story-data.ts +++ b/packages/canary-web-components/src/components/ic-data-table/story-data.ts @@ -1004,6 +1004,56 @@ export const DATA_REACT_ELEMENTS_WITH_ICONS = [ }, ]; +export const ACTION_DATA_ELEMENTS = [ + { + firstName: { + data: "Joe", + actionElement: ` `, + }, + lastName: "Bloggs", + age: 31, + jobTitle: "Developer", + address: "1 Main Street, Town, County, Postcode", + }, + { + firstName: "Sarah", + lastName: "Jane", + age: 28, + jobTitle: { + data: "Senior Software Developer, Site Reliability Engineering", + actionElement: ``, + }, + address: "2 Main Street, Town, Country, Postcode", + }, + { + firstName: "Mark", + lastName: "Smith", + age: { + data: 45, + actionElement: ` `, + }, + jobTitle: "Team Lead", + address: "12 Key Street, Town, Country, Postcode", + }, + { + firstName: "Naomi", + lastName: "Kens", + age: 32, + jobTitle: "Analyst", + address: "8 Side Street, Town, Country, Postcode", + }, + { + firstName: "Luke", + lastName: "Sky", + age: 18, + jobTitle: "Junior Developer", + address: { + data: "5 New Street, Town, Country, Postcode", + actionElement: ``, + }, + }, +]; + export const createDataTableElement = ( caption: string, columns: IcDataTableColumnObject[] = COLS, @@ -1461,6 +1511,9 @@ export const SlottedPagination = (): HTMLIcDataTableElement => { return dataTable; }; +export const ActionElement = (): HTMLElement => + createDataTableElement("Action Element", COLS, ACTION_DATA_ELEMENTS); + export const DevArea = (): HTMLElement => { const dataTable = createDataTableElement( "Basic Table", diff --git a/packages/canary-web-components/src/components/ic-data-table/test/basic/__snapshots__/ic-data-table.spec.tsx.snap b/packages/canary-web-components/src/components/ic-data-table/test/basic/__snapshots__/ic-data-table.spec.tsx.snap index 21e546fea5..349853a83f 100644 --- a/packages/canary-web-components/src/components/ic-data-table/test/basic/__snapshots__/ic-data-table.spec.tsx.snap +++ b/packages/canary-web-components/src/components/ic-data-table/test/basic/__snapshots__/ic-data-table.spec.tsx.snap @@ -966,7 +966,7 @@ exports[`ic-data-table should be able to slot a custom empty state into the data - +
@@ -987,7 +987,7 @@ exports[`ic-data-table should be able to slot a custom empty state into the data
- + - -
- + `} ### Playground export const defaultArgs = { - color: "rgba(27, 60, 121, 1)", + brandColor: "rgba(27, 60, 121, 1)", theme: "light", }; @@ -199,7 +200,7 @@ export const defaultArgs = { }} > {(args) => - html` + html`
; + @Event() brandChange: EventEmitter; componentWillLoad(): void { this.darkModeChangeHandler(); - this.setThemeColor(); + this.setBrandColor(); window.matchMedia && window @@ -70,32 +73,32 @@ export class Theme { } }; - private checkThemeColorContrast = (): void => { + private checkBrandColorContrast = (): void => { if ( - getThemeColorBrightness() < BLACK_MIN_COLOR_BRIGHTNESS && - getThemeColorBrightness() > WHITE_MAX_COLOR_BRIGHTNESS + getBrandColorBrightness() < BLACK_MIN_COLOR_BRIGHTNESS && + getBrandColorBrightness() > WHITE_MAX_COLOR_BRIGHTNESS ) { console.warn( - `The theme colour does not provide enough contrast with either of the ICDS black or white foreground colours. Consider choosing a colour with a different brightness to achieve sufficient colour contrast for good visibility. See https://www.w3.org/TR/AERT/#color-contrast for more information about colour contrast.` + `The brand colour does not provide enough contrast with either of the ICDS black or white foreground colours. Consider choosing a colour with a different brightness to achieve sufficient colour contrast for good visibility. See https://www.w3.org/TR/AERT/#color-contrast for more information about colour contrast.` ); } }; - private setThemeColor = () => { - const colorRGBA = convertToRGBA(this.color); + private setBrandColor = () => { + const colorRGBA = convertToRGBA(this.brandColor); if (colorRGBA) { const { r, g, b, a } = colorRGBA; const { style } = document.documentElement; - style.setProperty("--ic-theme-primary-r", `${r}`); - style.setProperty("--ic-theme-primary-g", `${g}`); - style.setProperty("--ic-theme-primary-b", `${b}`); - style.setProperty("--ic-theme-primary-a", `${a}`); + style.setProperty("--ic-brand-color-primary-r", `${r}`); + style.setProperty("--ic-brand-color-primary-g", `${g}`); + style.setProperty("--ic-brand-color-primary-b", `${b}`); + style.setProperty("--ic-brand-color-primary-a", `${a}`); - this.checkThemeColorContrast(); + this.checkBrandColorContrast(); - this.themeChange.emit({ - mode: getThemeForegroundColor(), + this.brandChange.emit({ + mode: getBrandForegroundAppearance(), color: colorRGBA, }); } diff --git a/packages/web-components/src/components/ic-theme/readme.md b/packages/web-components/src/components/ic-theme/readme.md index c20bb6e099..7e6de2b4ce 100644 --- a/packages/web-components/src/components/ic-theme/readme.md +++ b/packages/web-components/src/components/ic-theme/readme.md @@ -7,10 +7,10 @@ ## Properties -| Property | Attribute | Description | Type | Default | -| -------- | --------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------- | --------- | -| `color` | `color` | The theme colour. Can be a hex value e.g. "#ff0000", RGB e.g. "rgb(255, 0, 0)", or RGBA e.g. "rgba(255, 0, 0, 1)". | ``#${string}` \| `rgb(${string})` \| `rgba(${string})`` | `null` | -| `theme` | `theme` | The theme mode. Can be "dark", "light", or "system". "system" will use the device or browser settings. | `"dark" \| "light" \| "system"` | `"light"` | +| Property | Attribute | Description | Type | Default | +| ------------ | ------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------- | --------- | +| `brandColor` | `brand-color` | The brand colour. Can be a hex value e.g. "#ff0000", RGB e.g. "rgb(255, 0, 0)", or RGBA e.g. "rgba(255, 0, 0, 1)". | ``#${string}` \| `rgb(${string})` \| `rgba(${string})`` | `null` | +| `theme` | `theme` | The theme mode. Can be "dark", "light", or "system". "system" will use the device or browser settings. | `"dark" \| "light" \| "system"` | `"light"` | ---------------------------------------------- diff --git a/packages/web-components/src/components/ic-theme/test/basic/ic-theme.e2e.ts b/packages/web-components/src/components/ic-theme/test/basic/ic-theme.e2e.ts index 47e8c4e13a..8decc0e675 100644 --- a/packages/web-components/src/components/ic-theme/test/basic/ic-theme.e2e.ts +++ b/packages/web-components/src/components/ic-theme/test/basic/ic-theme.e2e.ts @@ -9,15 +9,15 @@ describe("ic-theme", () => { expect(element).toHaveClass("hydrated"); }); - it("should emit themeChange when setThemeRGB method called", async () => { + it("should emit brandChange event when brandColor is changed", async () => { const page = await newE2EPage(); await page.setContent(``); - const spy = await page.spyOnEvent("themeChange"); + const spy = await page.spyOnEvent("brandChange"); await page.$eval("ic-theme", (elm: HTMLIcThemeElement) => { - elm.color = "rgb(255, 201, 60)"; + elm.brandColor = "rgb(255, 201, 60)"; }); await page.waitForChanges(); @@ -33,7 +33,7 @@ describe("ic-theme", () => { }); await page.$eval("ic-theme", (elm: HTMLIcThemeElement) => { - elm.color = "rgb(35, 81, 142)"; + elm.brandColor = "rgb(35, 81, 142)"; }); await page.waitForChanges(); @@ -53,10 +53,10 @@ describe("ic-theme", () => { const page = await newE2EPage(); await page.setContent(""); - const spy = await page.spyOnEvent("themeChange"); + const spy = await page.spyOnEvent("brandChange"); await page.$eval("ic-theme", (elm: HTMLIcThemeElement) => { - elm.color = "rgb(1000, 1000, 1000)"; + elm.brandColor = "rgb(1000, 1000, 1000)"; }); await page.waitForChanges(); @@ -72,7 +72,7 @@ describe("ic-theme", () => { }); await page.$eval("ic-theme", (elm: HTMLIcThemeElement) => { - elm.color = "rgb(-1, -1, -1)"; + elm.brandColor = "rgb(-1, -1, -1)"; }); await page.waitForChanges(); diff --git a/packages/web-components/src/components/ic-theme/test/basic/ic-theme.spec.tsx b/packages/web-components/src/components/ic-theme/test/basic/ic-theme.spec.tsx index 3bf3417857..7433984bdb 100644 --- a/packages/web-components/src/components/ic-theme/test/basic/ic-theme.spec.tsx +++ b/packages/web-components/src/components/ic-theme/test/basic/ic-theme.spec.tsx @@ -6,14 +6,14 @@ import { WHITE_MAX_COLOR_BRIGHTNESS, } from "../../../../utils/constants"; -//mocked as getThemeColorBrightness is NaN when run in test context +//mocked as getBrandColorBrightness is NaN when run in test context //instead we return a value which will trigger the console warning about theme color contrast const mockThemeColorContrastFail = () => { const func = jest.fn(() => { return (BLACK_MIN_COLOR_BRIGHTNESS + WHITE_MAX_COLOR_BRIGHTNESS) / 2; }); - Object.defineProperty(helpers, "getThemeColorBrightness", { + Object.defineProperty(helpers, "getBrandColorBrightness", { value: func, }); }; @@ -25,13 +25,13 @@ const expectRGBToBe = ( expectB: string ) => { const r = page.doc.documentElement.style.getPropertyValue( - "--ic-theme-primary-r" + "--ic-brand-color-primary-r" ); const g = page.doc.documentElement.style.getPropertyValue( - "--ic-theme-primary-g" + "--ic-brand-color-primary-g" ); const b = page.doc.documentElement.style.getPropertyValue( - "--ic-theme-primary-b" + "--ic-brand-color-primary-b" ); expect(r).toBe(expectR); @@ -47,7 +47,7 @@ describe("ic-theme", () => { it("should set theme colour with hex", async () => { const page = await newSpecPage({ components: [Theme], - html: ``, + html: ``, }); expectRGBToBe(page, "255", "192", "203"); @@ -56,7 +56,7 @@ describe("ic-theme", () => { it("should set theme colour with 3 character hex", async () => { const page = await newSpecPage({ components: [Theme], - html: ``, + html: ``, }); expectRGBToBe(page, "255", "255", "255"); @@ -65,7 +65,7 @@ describe("ic-theme", () => { it("should set theme colour with rgb", async () => { const page = await newSpecPage({ components: [Theme], - html: ``, + html: ``, }); expectRGBToBe(page, "159", "43", "104"); @@ -74,7 +74,7 @@ describe("ic-theme", () => { it("should set theme colour with rgba", async () => { const page = await newSpecPage({ components: [Theme], - html: ``, + html: ``, }); expectRGBToBe(page, "159", "43", "104"); }); @@ -84,10 +84,10 @@ describe("ic-theme", () => { const page = await newSpecPage({ components: [Theme], - html: ``, + html: ``, }); - page.root.color = "rgba(133, 133, 133, 1)"; + page.root.brandColor = "rgba(133, 133, 133, 1)"; await page.waitForChanges(); expectRGBToBe(page, "133", "133", "133"); diff --git a/packages/web-components/src/components/ic-top-navigation/ic-top-navigation.css b/packages/web-components/src/components/ic-top-navigation/ic-top-navigation.css index 0b414db94b..0bbaf2e08d 100644 --- a/packages/web-components/src/components/ic-top-navigation/ic-top-navigation.css +++ b/packages/web-components/src/components/ic-top-navigation/ic-top-navigation.css @@ -8,8 +8,8 @@ --ic-typography-color: var(--ic-top-navigation-text); - --ic-button-secondary-text-monochrome: var(--ic-theme-text); - --ic-button-secondary-border-monochrome: var(--ic-theme-text); + --ic-button-secondary-text-monochrome: var(--ic-brand-text-color); + --ic-button-secondary-border-monochrome: var(--ic-brand-text-color); } :host(.dark) { diff --git a/packages/web-components/src/components/ic-top-navigation/ic-top-navigation.stories.mdx b/packages/web-components/src/components/ic-top-navigation/ic-top-navigation.stories.mdx index 30a5d69994..3e0abcca8b 100644 --- a/packages/web-components/src/components/ic-top-navigation/ic-top-navigation.stories.mdx +++ b/packages/web-components/src/components/ic-top-navigation/ic-top-navigation.stories.mdx @@ -701,15 +701,15 @@ import NavigationGroup from "../ic-navigation-group/readme.md"; -### Theming +### Brand {(args) => - html` + html` - Default theme + Default brand - Sunrise theme - ` + Sunrise brand + + ` } diff --git a/packages/web-components/src/components/ic-top-navigation/ic-top-navigation.tsx b/packages/web-components/src/components/ic-top-navigation/ic-top-navigation.tsx index dafaac91c9..a60b1a43d6 100644 --- a/packages/web-components/src/components/ic-top-navigation/ic-top-navigation.tsx +++ b/packages/web-components/src/components/ic-top-navigation/ic-top-navigation.tsx @@ -14,9 +14,9 @@ import { import { IcTypographyVariants, IcAlignment, - IcThemeForeground, - IcThemeForegroundEnum, - IcTheme, + IcBrandForeground, + IcBrandForegroundEnum, + IcBrand, IcDeviceSizes, IcValueEventDetail, IcThemeMode, @@ -25,7 +25,7 @@ import { checkResizeObserver, DEVICE_SIZES, getCurrentDeviceSize, - getThemeForegroundColor, + getBrandForegroundAppearance, getSlot, onComponentPropUndefinedChange, onComponentRequiredPropUndefined, @@ -61,7 +61,7 @@ export class TopNavigation { @Element() el: HTMLIcTopNavigationElement; @State() deviceSize: number = DEVICE_SIZES.XL; - @State() foregroundColor: IcThemeForeground = getThemeForegroundColor(); + @State() foregroundColor: IcBrandForeground = getBrandForegroundAppearance(); @State() hasFullWidthSearchBar: boolean = false; @State() mobileSearchBarVisible: boolean = false; @State() mobileSearchHiddenOnBlur: boolean = false; @@ -186,8 +186,8 @@ export class TopNavigation { this.searchValue = detail.value as string; } - @Listen("themeChange", { target: "document" }) - themeChangeHandler({ detail }: CustomEvent): void { + @Listen("brandChange", { target: "document" }) + brandChangeHandler({ detail }: CustomEvent): void { this.foregroundColor = detail.mode; } @@ -340,8 +340,8 @@ export class TopNavigation { class={{ "fullwidth-searchbar": hasFullWidthSearchBar, "mobile-mode": overMobileBreakpoint, - [IcThemeForegroundEnum.Dark]: - foregroundColor === IcThemeForegroundEnum.Dark, + [IcBrandForegroundEnum.Dark]: + foregroundColor === IcBrandForegroundEnum.Dark, [`ic-theme-${theme}`]: theme !== "inherit", }} > @@ -425,7 +425,11 @@ export class TopNavigation { monochrome size={searchButtonSize} aria-label={mobileSearchButtonTitle} - theme={foregroundColor == "light" ? "dark" : "light"} + theme={ + foregroundColor == IcBrandForegroundEnum.Light + ? IcBrandForegroundEnum.Dark + : IcBrandForegroundEnum.Light + } onClick={searchButtonClickHandler} > @@ -462,7 +466,9 @@ export class TopNavigation {