diff --git a/packages/react-components/components/stencil-generated/index.ts b/packages/react-components/components/stencil-generated/index.ts index 7d6a47de..c55e32f7 100644 --- a/packages/react-components/components/stencil-generated/index.ts +++ b/packages/react-components/components/stencil-generated/index.ts @@ -30,6 +30,8 @@ export const CbpPanel = /*@__PURE__*/createReactComponent('cbp-section'); export const CbpSegmentedButtonGroup = /*@__PURE__*/createReactComponent('cbp-segmented-button-group'); export const CbpSkipNav = /*@__PURE__*/createReactComponent('cbp-skip-nav'); +export const CbpStructuredList = /*@__PURE__*/createReactComponent('cbp-structured-list'); +export const CbpStructuredListItem = /*@__PURE__*/createReactComponent('cbp-structured-list-item'); export const CbpTab = /*@__PURE__*/createReactComponent('cbp-tab'); export const CbpTabPanel = /*@__PURE__*/createReactComponent('cbp-tab-panel'); export const CbpTabs = /*@__PURE__*/createReactComponent('cbp-tabs'); diff --git a/packages/web-components/CHANGELOG.md b/packages/web-components/CHANGELOG.md index 4ac37571..407b2b4d 100644 --- a/packages/web-components/CHANGELOG.md +++ b/packages/web-components/CHANGELOG.md @@ -7,6 +7,8 @@ The React components are wrappers generated from this package and will share the ## [unreleased] TBD * First cut of `cbp-expand`. +* First cut of `cbp-structured-list` and `cbp-structured-list-item`. +* Updates to `cbp-icon` and `cbp-button` in support of Expand and Accordion components. * Renamed `data-container-theme` to `data-cbp-container-context` to convey that a specific container/DOM node is being displayed light or dark. ## [0.0.1-develop.8] 04-29-2024 diff --git a/packages/web-components/src/components/cbp-expand/cbp-expand.scss b/packages/web-components/src/components/cbp-expand/cbp-expand.scss index ab3b54ea..f7feb099 100644 --- a/packages/web-components/src/components/cbp-expand/cbp-expand.scss +++ b/packages/web-components/src/components/cbp-expand/cbp-expand.scss @@ -4,10 +4,12 @@ --cbp-expand-control-gap: var(--cbp-space-1x); --cbp-expand-control-padding: var(--cbp-space-3x); --cbp-expand-content-padding: var(--cbp-space-3x); + --cbp-expand-margin-bottom: var(--cbp-space-2x); } cbp-expand { display: block; + margin-bottom: var(--cbp-expand-margin-bottom); &:not([open]) { --cbp-expand-rotate-icon: rotate(-90deg); diff --git a/packages/web-components/src/components/cbp-link/cbp-link.specs.mdx b/packages/web-components/src/components/cbp-link/cbp-link.specs.mdx index f513defe..488c02d5 100644 --- a/packages/web-components/src/components/cbp-link/cbp-link.specs.mdx +++ b/packages/web-components/src/components/cbp-link/cbp-link.specs.mdx @@ -39,7 +39,6 @@ The Link component renders (or wraps) an anchor for navigation styled in accorda ### Additional Notes and Considerations * An anchor may be supplied as slotted content rather than rendered by the web component. This requirement supports frameworks that use component-based routers, such as React or Vue. -* TODO: need clarification on "definition" variant and expected functionality. * TODO: investigate side effects and edge cases pertaining to "disabled" links. * Are any interactive styles still inherited? * Does a mouse click still emit a click event? diff --git a/packages/web-components/src/components/cbp-section/cbp-section.specs.mdx b/packages/web-components/src/components/cbp-section/cbp-section.specs.mdx index f5f74fd6..051bd9cd 100644 --- a/packages/web-components/src/components/cbp-section/cbp-section.specs.mdx +++ b/packages/web-components/src/components/cbp-section/cbp-section.specs.mdx @@ -11,7 +11,7 @@ The Section component represents a generic block that may be used for semantic m ## Functional Requirements * The Section component may render either a `section` tag, a `div` tag, or no tag inside of the host element. -* The Section component may be used apply a background color or horizontal line similar to the Typography component, but for a larger blocks of content. +* The Section component may be used to apply a background color or horizontal line similar to the Typography component, but for a larger blocks of content. ## Technical Specifications diff --git a/packages/web-components/src/components/cbp-structured-list-item/cbp-structured-list-item.scss b/packages/web-components/src/components/cbp-structured-list-item/cbp-structured-list-item.scss new file mode 100644 index 00000000..daebbbd7 --- /dev/null +++ b/packages/web-components/src/components/cbp-structured-list-item/cbp-structured-list-item.scss @@ -0,0 +1,11 @@ +cbp-structured-list-item { + display: block; + + &[color=danger]:not([selected]) { + background-color: var(--cbp-color-danger-lighter) !important; + } + + &[selected] { + background-color: var(--cbp-color-interactive-selected-light) !important; + } +} diff --git a/packages/web-components/src/components/cbp-structured-list-item/cbp-structured-list-item.tsx b/packages/web-components/src/components/cbp-structured-list-item/cbp-structured-list-item.tsx new file mode 100644 index 00000000..da093f95 --- /dev/null +++ b/packages/web-components/src/components/cbp-structured-list-item/cbp-structured-list-item.tsx @@ -0,0 +1,38 @@ +import { Component, Prop, Element, Host, h } from '@stencil/core'; +import { setCSSProps } from '../../utils/utils'; + +@Component({ + tag: 'cbp-structured-list-item', + styleUrl: 'cbp-structured-list-item.scss', +}) + +export class CbpStructuredListItem { + + @Element() host: HTMLElement; + + /** Optionally specifies a color variant based on design tokens. */ + @Prop({ reflect: true }) color: 'danger'; + + /** Specifies whether the item is selected. */ + @Prop({ reflect: true }) selected: boolean; + + /** Supports adding inline styles as an object */ + @Prop() sx: any = {}; + + componentWillLoad() { + if (typeof this.sx == 'string') { + this.sx = JSON.parse(this.sx) || {}; + } + setCSSProps(this.host, { + ...this.sx, + }); + } + + render() { + return ( + + + + ); + } +} diff --git a/packages/web-components/src/components/cbp-structured-list/cbp-structured-list.scss b/packages/web-components/src/components/cbp-structured-list/cbp-structured-list.scss new file mode 100644 index 00000000..3bb047a1 --- /dev/null +++ b/packages/web-components/src/components/cbp-structured-list/cbp-structured-list.scss @@ -0,0 +1,49 @@ +:root { + --cbp-structured-list-item-padding: var(--cbp-space-4x); +} +cbp-structured-list { + display: block; + background-color: var(--cbp-color-white); + + [slot] { + display: flex; + align-items: center; + padding: var(--cbp-space-3x); + min-height: var(--cbp-space-13x); + font-weight: var(--cbp-font-weight-bold); + + // Nesting 2 flex contexts will not take up 100% of the width + *:only-child { + flex-basis: 100%; + } + } + + [slot=cbp-structured-list-header] { + color: var(--cbp-color-text-darkest); + background-color: var(--cbp-color-gray-cool-30); + font-style: italic; + } + + [slot=cbp-structured-list-footer] { + color: var(--cbp-color-text-lightest); + background-color: var(--cbp-color-gray-cool-70); + } + + &[striped] [role=list] > *:nth-child(even) { + background-color: var(--cbp-color-gray-cool-4); + } + + // Style all list items regardless of the element used to wrap/define them (don't leave this to cbp-structured-list-item) + div[role=list] > * { + display: block; + padding: var(--cbp-structured-list-item-padding); + border-bottom-style: solid; + border-bottom-width: var(--cbp-border-size-md); + border-bottom-color: var(--cbp-color-gray-cool-30); + + &:hover { + background-color: var(--cbp-color-gray-cool-10) !important; + } + } + +} diff --git a/packages/web-components/src/components/cbp-structured-list/cbp-structured-list.specs.mdx b/packages/web-components/src/components/cbp-structured-list/cbp-structured-list.specs.mdx new file mode 100644 index 00000000..b30bd5f1 --- /dev/null +++ b/packages/web-components/src/components/cbp-structured-list/cbp-structured-list.specs.mdx @@ -0,0 +1,53 @@ +import { Meta } from '@storybook/addon-docs'; + + + +# cbp-structured-list and cbp-structured-list-item + +## Purpose + +Structured lists are a way of displaying long lists of data where the user is not directly comparing raw data one row at a time. + +## Functional Requirements + +* The parent Structured List component renders a `div` with `role="list"`, with optional slots for header and footer information before and after it respectively. +* The Structured List may contain plain `li` elements slotted within the default slot. +* Optionally, the Structured List Item component (`cbp-structured-list-item`) may be slotted within the default slot if additional functionality will be used, such as: + * Selecting of items + * Shading of rows in a `danger` color. + +## Technical Specifications + +### User Interactions + +* The Structured List supports styling for selected list items. +* The actual user interactions and implementation of checkboxes and buttons via slotted content is provided by the application code. + +### Responsiveness + +* The Structured List is semantically an unordered list. However, its contents may be complex and arranged in a grid with faux columns. + * Because each list item is separate, an overarching grid context that takes queues from content length cannot be established to lay out the list. + * Creating equal width columns can only be achieved using CSS Grid or the `cbp-grid` component within each list item. + * These grid columns should be explicitly defined and not determined by content length, since each row/item is independent and columns may not line up across rows/items. + * A responsive grid that wraps, containing equal sized (but flexible) columns may be achieved using some variant of `grid-template-columns` with `autofit`, + e.g., `grid-template-columns="repeat(auto-fit, minmax(5rem, 1fr))"`. + * For grids that require more granular definition of each column, the `cbp-grid` component's `breakpoint` property may be used to linearize the grid at a specified viewport size. + +### Accessibility + +* The Structured List should be recognizable as a semantic list, regardless of how it is marked up. + * The parent `cbp-structured-list` component renders a `div` with `role="list"`. + * The immediate children slotted within the default slot of the parent should be one of the following: + * `li` elements + * `cbp-structured-list-item` components + * any other container with a `role="listitem"`. +* The `cbp-structured-list-item` component applies a `role="listitem"` to its host tag, acting as a proper list item. +* The Structured List component should have a value specified for its `accessibilityText` property, which describes the list and is applied via `aria-label`. +* If the Structured List has a header (content in the `cbp-structured-list-header` named slot), this element should be given an `id`, which is also specified as the `headerId` property + and used to link the header to the list via the `aria-describedby` attribute, providing additional context for the list to screen readers. +* The Structured List is not a table and has no column headings. If you feel the need for column headings, you should use a table instead. + +### Additional Notes and Considerations + +* The named slot `cbp-structured-list-footer` is intended for an action bar when items are selected and should not be used otherwise. +* TODO: integrating the `cbp-structured-list-item` `selected` property with user interaction with a checkbox may take additional work and testing. \ No newline at end of file diff --git a/packages/web-components/src/components/cbp-structured-list/cbp-structured-list.stories.tsx b/packages/web-components/src/components/cbp-structured-list/cbp-structured-list.stories.tsx new file mode 100644 index 00000000..2cfd31af --- /dev/null +++ b/packages/web-components/src/components/cbp-structured-list/cbp-structured-list.stories.tsx @@ -0,0 +1,256 @@ +export default { + title: 'Components/Structured List', + tags: ['autodocs'], + argTypes: { + showHeader: { + control: 'boolean', + }, + headerId: { + control: 'text', + }, + showFooter: { + control: 'boolean', + }, + striped: { + control: 'boolean', + }, + /* + selectable: { + control: 'boolean', + }, + */ + sx: { + description: 'Supports adding inline styles as an object of key-value pairs comprised of CSS properties and values. Values should reference design tokens when possible.', + control: 'object', + }, + }, + args: { + showHeader: true, + headerId: 'list-header', + } +}; + + +function generateLIs(items) { + const html = items.map(({ content }) => { + return `
  • ${content}
  • `; + }); + return html.join(''); +} + +function generateItems(items) { + const html = items.map(({ content, color, selected }) => { + return `${content}`; + }); + return html.join(''); +} + + + + +const StructuredListTemplate = ({ listItems, striped, selectable, showHeader, headerId, showFooter, sx }) => { + return ` + + ${showHeader ? `
    ${listItems.length} results, filters applied, etc. This acts as the "aria-description" for the list.
    ` : ''} + + ${generateLIs(listItems)} + + ${showFooter + ? ` +
    + +
    0 items selected.
    +
    + Delete + Compare +
    +
    + ` + : ''} +
    + `; +}; +export const StructuredList = StructuredListTemplate.bind({}); +StructuredList.argTypes = { + listItems: { + description: 'Configure various aspects of the list items within the structured list.', + control: 'object', + }, +} +StructuredList.args = { + listItems: [ + { + content: 'Structured list item 1' + }, + { + content: 'Structured list item 2' + }, + { + content: 'Structured list item 3' + }, + { + content: 'Structured list item 4' + }, + { + content: 'Structured list item 5' + }, + ] +} + + +const StructuredListItemsTemplate = ({ listItems, striped, selectable, showHeader, headerId, showFooter, sx }) => { + return ` + + ${showHeader ? `
    ${listItems.length} results, filters applied, etc. This acts as the "aria-description" for the list.
    ` : ''} + + ${generateItems(listItems)} + + ${showFooter + ? ` +
    + +
    0 items selected.
    +
    + Delete + Compare +
    +
    + ` + : ''} +
    + `; +}; +export const StructuredListItems = StructuredListItemsTemplate.bind({}); +StructuredListItems.argTypes = { + listItems: { + description: 'Configure various aspects of the list items within the structured list.', + control: 'object', + }, +} +StructuredListItems.args = { + listItems: [ + { + content: 'Structured list item 1', + color: 'default', + selected: false + }, + { + content: 'Structured list item 2', + color: 'default', + selected: false + }, + { + content: 'Structured list item 3', + color: 'default', + selected: false + }, + { + content: 'Structured list item 4', + color: 'danger', + selected: false + }, + { + content: 'Structured list item 5', + color: 'default', + selected: false + }, + ] +} + + +const StructuredListWithGridTemplate = ({ striped, selectable, showHeader, headerId, showFooter, sx }) => { + return ` + + ${showHeader ? `
    3 Results, filters applied, etc. This acts as the "aria-description" for the list.
    ` : ''} + + + + + Grid Item 1 + + + Grid Item 2 + + + Grid Item 3 + + + Grid Item 4 + + + + + + + + Grid Item 1 is a bit longer + + + Grid Item 2 + + + Grid Item 3 is a whole lot longer. Lorem ipsum dolor sit amet, consectetur adipiscing elit. + + + Grid Item 4 + + + + + + + + Grid Item 1 + + + Grid Item 2 has a little more. + + + Grid Item 3 + + + Grid Item 4 does too. + + + + + ${showFooter + ? ` +
    + +
    0 items selected.
    +
    + Delete + Compare +
    +
    + ` + : ''} +
    + `; +}; +export const StructuredListWithGrid = StructuredListWithGridTemplate.bind({}); \ No newline at end of file diff --git a/packages/web-components/src/components/cbp-structured-list/cbp-structured-list.tsx b/packages/web-components/src/components/cbp-structured-list/cbp-structured-list.tsx new file mode 100644 index 00000000..bd5f7ba1 --- /dev/null +++ b/packages/web-components/src/components/cbp-structured-list/cbp-structured-list.tsx @@ -0,0 +1,63 @@ +import { Component, Prop, Element, Host, h } from '@stencil/core'; +import { setCSSProps } from '../../utils/utils'; + +@Component({ + tag: 'cbp-structured-list', + styleUrl: 'cbp-structured-list.scss' +}) + +/** + * @slot - Only list items may be slotted within the default content. + * @slot cbp-structured-list-header - Optional information such as number of results, filters, etc. are provided by the application and slotted into this named slot. + * @slot cbp-structured-list-footer - Optional information and/or interactive elements are provided by the application and slotted into this named slot. + */ +export class CbpStructuredList { + + @Element() host: HTMLElement; + + /** + * Specifies an accessible label for the list as an `aria-label`, similar to a table `caption`. + * Since the structured list contains significant amount of data, it is advised to always specify a label describing the list. + */ + @Prop() accessibilityText: string; + + /** References an `id` placed on the element slotted into the `cbp-structured-list-header` named slot to provide additional accessible context to the list label. */ + @Prop() headerId: string; + + /** Specifies whether the list is "striped,"" with even rows shaded. */ + @Prop({ reflect: true }) striped: boolean; + + /** Specifies whether the list items are selectable (via checkbox - provided by the application). */ + @Prop({ reflect: true }) selectable: boolean; + + /** Supports adding inline styles as an object */ + @Prop() sx: any = {}; + + + componentWillLoad() { + if (typeof this.sx == 'string') { + this.sx = JSON.parse(this.sx) || {}; + } + setCSSProps(this.host, { + ...this.sx, + }); + } + + render() { + return ( + + + +
    + +
    + + +
    + ); + } + +}