diff --git a/change/@ni-ok-angular-09482ac4-a62c-47fc-be43-c52b823e870e.json b/change/@ni-ok-angular-09482ac4-a62c-47fc-be43-c52b823e870e.json new file mode 100644 index 0000000000..360a832613 --- /dev/null +++ b/change/@ni-ok-angular-09482ac4-a62c-47fc-be43-c52b823e870e.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: add OK accordion-item", + "packageName": "@ni/ok-angular", + "email": "1458528+fredvisser@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/change/@ni-ok-components-6037eb00-1211-4817-b88c-f456f2305a49.json b/change/@ni-ok-components-6037eb00-1211-4817-b88c-f456f2305a49.json new file mode 100644 index 0000000000..86adb9a6c6 --- /dev/null +++ b/change/@ni-ok-components-6037eb00-1211-4817-b88c-f456f2305a49.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: add OK accordion-item", + "packageName": "@ni/ok-components", + "email": "1458528+fredvisser@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/packages/angular-workspace/example-client-app/src/app/app.module.ts b/packages/angular-workspace/example-client-app/src/app/app.module.ts index 397d5a7efe..9be8dd00e3 100644 --- a/packages/angular-workspace/example-client-app/src/app/app.module.ts +++ b/packages/angular-workspace/example-client-app/src/app/app.module.ts @@ -34,6 +34,7 @@ import { NimbleTableColumnMenuButtonModule } from '@ni/nimble-angular/table-colu import { NimbleRichTextViewerModule } from '@ni/nimble-angular/rich-text/viewer'; import { NimbleRichTextEditorModule } from '@ni/nimble-angular/rich-text/editor'; import { NimbleRichTextMentionUsersModule } from '@ni/nimble-angular/rich-text-mention/users'; +import { OkAccordionItemModule } from 'ok-angular/accordion-item/ok-accordion-item.module'; import { OkButtonModule } from 'ok-angular/button/ok-button.module'; import { SprightChatConversationModule } from '@ni/spright-angular/chat/conversation'; import { SprightChatInputModule } from '@ni/spright-angular/chat/input'; @@ -120,6 +121,7 @@ import { CustomAppComponent } from './customapp/customapp.component'; NimbleMappingEmptyModule, NimbleIconPencilToRectangleModule, NimbleIconMessagesSparkleModule, + OkAccordionItemModule, OkButtonModule, SprightChatConversationModule, SprightChatInputModule, diff --git a/packages/angular-workspace/example-client-app/src/app/customapp/customapp.component.html b/packages/angular-workspace/example-client-app/src/app/customapp/customapp.component.html index 4458204fde..71d783a2fe 100644 --- a/packages/angular-workspace/example-client-app/src/app/customapp/customapp.component.html +++ b/packages/angular-workspace/example-client-app/src/app/customapp/customapp.component.html @@ -519,5 +519,14 @@
Button (Ok)
Ok +
+
Accordion Item (Ok)
+ + Calibration assets can expose operator-facing status, location, and ownership details. + + + This section starts collapsed to show the default interaction state. + +
diff --git a/packages/angular-workspace/ok-angular/accordion-item/ng-package.json b/packages/angular-workspace/ok-angular/accordion-item/ng-package.json new file mode 100644 index 0000000000..3ccce86b1c --- /dev/null +++ b/packages/angular-workspace/ok-angular/accordion-item/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public-api.ts" + } +} diff --git a/packages/angular-workspace/ok-angular/accordion-item/ok-accordion-item.directive.ts b/packages/angular-workspace/ok-angular/accordion-item/ok-accordion-item.directive.ts new file mode 100644 index 0000000000..42df590b07 --- /dev/null +++ b/packages/angular-workspace/ok-angular/accordion-item/ok-accordion-item.directive.ts @@ -0,0 +1,48 @@ +import { booleanAttribute, Directive, ElementRef, Input, Renderer2 } from '@angular/core'; +import type { AccordionItem } from '@ni/ok-components/dist/esm/accordion-item'; +import { accordionItemTag } from '@ni/ok-components/dist/esm/accordion-item'; +import type { AccordionItemAppearance } from '@ni/ok-components/dist/esm/accordion-item/types'; + +export type { AccordionItem }; +export { accordionItemTag }; + +/** + * Directive to provide Angular integration for the accordion item. + */ +@Directive({ + selector: 'ok-accordion-item', + standalone: false +}) +export class OkAccordionItemDirective { + public get header(): string { + return this.elementRef.nativeElement.header; + } + + @Input() + public set header(value: string) { + this.renderer.setProperty(this.elementRef.nativeElement, 'header', value); + } + + public get expanded(): boolean { + return this.elementRef.nativeElement.expanded; + } + + @Input({ transform: booleanAttribute }) + public set expanded(value: boolean) { + this.renderer.setProperty(this.elementRef.nativeElement, 'expanded', value); + } + + public get appearance(): AccordionItemAppearance { + return this.elementRef.nativeElement.appearance; + } + + @Input() + public set appearance(value: AccordionItemAppearance) { + this.renderer.setProperty(this.elementRef.nativeElement, 'appearance', value); + } + + public constructor( + private readonly elementRef: ElementRef, + private readonly renderer: Renderer2 + ) {} +} diff --git a/packages/angular-workspace/ok-angular/accordion-item/ok-accordion-item.module.ts b/packages/angular-workspace/ok-angular/accordion-item/ok-accordion-item.module.ts new file mode 100644 index 0000000000..22d5eb0461 --- /dev/null +++ b/packages/angular-workspace/ok-angular/accordion-item/ok-accordion-item.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { OkAccordionItemDirective } from './ok-accordion-item.directive'; + +import '@ni/ok-components/dist/esm/accordion-item'; + +@NgModule({ + declarations: [OkAccordionItemDirective], + imports: [CommonModule], + exports: [OkAccordionItemDirective] +}) +export class OkAccordionItemModule { } diff --git a/packages/angular-workspace/ok-angular/accordion-item/public-api.ts b/packages/angular-workspace/ok-angular/accordion-item/public-api.ts new file mode 100644 index 0000000000..c02a3cc11d --- /dev/null +++ b/packages/angular-workspace/ok-angular/accordion-item/public-api.ts @@ -0,0 +1,2 @@ +export * from './ok-accordion-item.directive'; +export * from './ok-accordion-item.module'; diff --git a/packages/angular-workspace/ok-angular/accordion-item/tests/accordion-item.directive.spec.ts b/packages/angular-workspace/ok-angular/accordion-item/tests/accordion-item.directive.spec.ts new file mode 100644 index 0000000000..70e07e763c --- /dev/null +++ b/packages/angular-workspace/ok-angular/accordion-item/tests/accordion-item.directive.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; +import { OkAccordionItemModule } from '../ok-accordion-item.module'; + +describe('Ok accordion item', () => { + describe('module', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [OkAccordionItemModule] + }); + }); + + it('custom element is defined', () => { + expect(customElements.get('ok-accordion-item')).not.toBeUndefined(); + }); + }); +}); diff --git a/packages/ok-components/src/accordion-item/index.ts b/packages/ok-components/src/accordion-item/index.ts new file mode 100644 index 0000000000..874f7156e7 --- /dev/null +++ b/packages/ok-components/src/accordion-item/index.ts @@ -0,0 +1,38 @@ +import { attr } from '@ni/fast-element'; +import { DesignSystem, FoundationElement } from '@ni/fast-foundation'; +import '@ni/nimble-components/dist/esm/icons/arrow-expander-right'; +import { styles } from './styles'; +import { template } from './template'; +import { AccordionItemAppearance } from './types'; + +declare global { + interface HTMLElementTagNameMap { + 'ok-accordion-item': AccordionItem; + } +} + +/** + * An accordion item component that can be expanded or collapsed to + * show or hide its content. + */ +export class AccordionItem extends FoundationElement { + @attr + public header = ''; + + @attr({ mode: 'boolean' }) + public expanded = false; + + @attr() + public appearance: AccordionItemAppearance = AccordionItemAppearance.outline; +} + +const okAccordionItem = AccordionItem.compose({ + baseName: 'accordion-item', + template, + styles +}); + +DesignSystem.getOrCreate() + .withPrefix('ok') + .register(okAccordionItem()); +export const accordionItemTag = 'ok-accordion-item'; diff --git a/packages/ok-components/src/accordion-item/styles.ts b/packages/ok-components/src/accordion-item/styles.ts new file mode 100644 index 0000000000..3cf033a381 --- /dev/null +++ b/packages/ok-components/src/accordion-item/styles.ts @@ -0,0 +1,118 @@ +import { css } from '@ni/fast-element'; +import { + bodyPlus1EmphasizedFont, + bodyPlus1EmphasizedFontColor, + borderHoverColor, + borderRgbPartialColor, + dividerWidth, + iconSize, + mediumDelay, + mediumPadding, + smallDelay, + standardPadding +} from '@ni/nimble-components/dist/esm/theme-provider/design-tokens'; +import { appearanceBehavior } from '@ni/nimble-components/dist/esm/utilities/style/appearance'; +import { display } from '../utilities/style/display'; +import { AccordionItemAppearance } from './types'; + +export const styles = css` + ${display('block')} + + .accordion-item-details > .accordion-item-summary { + display: flex; + box-sizing: border-box; + height: 36px; + align-items: center; + margin-left: calc(-1 * ${mediumPadding}); + border: ${dividerWidth} solid transparent; + list-style: none; + cursor: pointer; + user-select: none; + } + + .accordion-item-details > .accordion-item-summary::-webkit-details-marker { + display: none; + } + + .accordion-item-details > .accordion-item-summary::marker { + content: ""; + } + + .accordion-item-details > .accordion-item-summary:hover { + border: ${dividerWidth} solid ${borderHoverColor}; + transition: ${smallDelay} ease-in; + } + + .accordion-item-details > .accordion-item-summary:focus-visible { + outline: 2px solid ${borderHoverColor}; + outline-offset: -2px; + } + + .accordion-item-icon { + transition: transform ${mediumDelay} ease-in; + margin: ${mediumPadding}; + min-width: ${iconSize}; + } + + .accordion-item-details[open] > .accordion-item-summary > .accordion-item-icon { + transform: rotate(90deg); + } + + .accordion-item-title { + flex: 1; + width: 100%; + position: relative; + font: ${bodyPlus1EmphasizedFont}; + line-height: 20px; + color: ${bodyPlus1EmphasizedFontColor}; + text-align: left; + display: block; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + overflow-wrap: normal; + } + + .accordion-item-content { + display: flex; + flex-direction: column; + gap: ${mediumPadding}; + margin-left: calc(${iconSize} + ${mediumPadding}); + margin-top: ${mediumPadding}; + padding-bottom: ${standardPadding}; + } +`.withBehaviors( + appearanceBehavior( + AccordionItemAppearance.outline, + css` + :host { + border-bottom: ${dividerWidth} solid rgba(${borderRgbPartialColor}, 0.2); + } + ` + ), + appearanceBehavior( + AccordionItemAppearance.block, + css` + :host { + margin-bottom: 2px; + } + + .accordion-item-details > .accordion-item-summary { + background-color: rgba(${borderRgbPartialColor}, 0.1); + margin-left: 0; + padding-left: 0; + border: none; + } + + .accordion-item-details > .accordion-item-summary:hover { + border: none; + outline: ${dividerWidth} solid ${borderHoverColor}; + outline-offset: -1px; + } + + .accordion-item-content { + margin-left: calc(${iconSize} + (2 * ${mediumPadding})); + } + ` + ) +); diff --git a/packages/ok-components/src/accordion-item/template.ts b/packages/ok-components/src/accordion-item/template.ts new file mode 100644 index 0000000000..410436d24f --- /dev/null +++ b/packages/ok-components/src/accordion-item/template.ts @@ -0,0 +1,31 @@ +import { html } from '@ni/fast-element'; +import { iconArrowExpanderRightTag } from '@ni/nimble-components/dist/esm/icons/arrow-expander-right'; +import type { AccordionItem } from '.'; + +const arrowExpanderRightTag = iconArrowExpanderRightTag; + +export const template = html` +
+ + <${arrowExpanderRightTag} + class="accordion-item-icon" + > + + ${x => x.header} + + +
+ +
+
+`; diff --git a/packages/ok-components/src/accordion-item/tests/accordion-item.spec.ts b/packages/ok-components/src/accordion-item/tests/accordion-item.spec.ts new file mode 100644 index 0000000000..f45220d4be --- /dev/null +++ b/packages/ok-components/src/accordion-item/tests/accordion-item.spec.ts @@ -0,0 +1,114 @@ +import { html } from '@ni/fast-element'; +import { waitForUpdatesAsync } from '@ni/nimble-components/dist/esm/testing/async-helpers'; +import { AccordionItem, accordionItemTag } from '..'; +import { AccordionItemAppearance } from '../types'; +import { fixture, type Fixture } from '../../utilities/tests/fixture'; + +async function setup( + expanded = false +): Promise> { + return await fixture( + html`<${accordionItemTag} + header="Test Header" + ?expanded="${() => expanded}" + > + Test content + ` + ); +} + +describe('AccordionItem', () => { + let element: AccordionItem; + let connect: () => Promise; + let disconnect: (() => Promise) | undefined; + + afterEach(async () => { + await disconnect?.(); + disconnect = undefined; + }); + + it('can construct an element instance', () => { + expect(document.createElement(accordionItemTag)).toBeInstanceOf( + AccordionItem + ); + }); + + it('should have a details element in the shadow DOM', async () => { + ({ element, connect, disconnect } = await setup()); + await connect(); + const details = element.shadowRoot?.querySelector('details'); + expect(details).not.toBeNull(); + }); + + it('should display the header text', async () => { + ({ element, connect, disconnect } = await setup()); + await connect(); + const titleSpan = element.shadowRoot?.querySelector( + '.accordion-item-title' + ); + expect(titleSpan?.textContent?.trim()).toBe('Test Header'); + }); + + it('should be collapsed by default', async () => { + ({ element, connect, disconnect } = await setup(false)); + await connect(); + const details = element.shadowRoot?.querySelector('details'); + expect(details?.open).toBeFalse(); + }); + + it('should be expanded when expanded attribute is set', async () => { + ({ element, connect, disconnect } = await setup(true)); + await connect(); + const details = element.shadowRoot?.querySelector('details'); + expect(details?.open).toBeTrue(); + }); + + it('should have a slot for content', async () => { + ({ element, connect, disconnect } = await setup()); + await connect(); + const slot = element.shadowRoot?.querySelector('slot'); + expect(slot).not.toBeNull(); + }); + + it('should have an expander icon', async () => { + ({ element, connect, disconnect } = await setup()); + await connect(); + const icon = element.shadowRoot?.querySelector( + 'nimble-icon-arrow-expander-right' + ); + expect(icon).not.toBeNull(); + }); + + it('should update header when property changes', async () => { + ({ element, connect, disconnect } = await setup()); + await connect(); + element.header = 'New Header'; + await waitForUpdatesAsync(); + const titleSpan = element.shadowRoot?.querySelector( + '.accordion-item-title' + ); + expect(titleSpan?.textContent?.trim()).toBe('New Header'); + }); + + it('should default to outline appearance', async () => { + ({ element, connect, disconnect } = await setup()); + await connect(); + expect(element.appearance).toBe(AccordionItemAppearance.outline); + }); + + it('should reflect appearance attribute', async () => { + ({ element, connect, disconnect } = await setup()); + await connect(); + element.appearance = AccordionItemAppearance.ghost; + await waitForUpdatesAsync(); + expect(element.getAttribute('appearance')).toBe('ghost'); + }); + + it('should support block appearance', async () => { + ({ element, connect, disconnect } = await setup()); + await connect(); + element.appearance = AccordionItemAppearance.block; + await waitForUpdatesAsync(); + expect(element.getAttribute('appearance')).toBe('block'); + }); +}); diff --git a/packages/ok-components/src/accordion-item/types.ts b/packages/ok-components/src/accordion-item/types.ts new file mode 100644 index 0000000000..6737bce07a --- /dev/null +++ b/packages/ok-components/src/accordion-item/types.ts @@ -0,0 +1,6 @@ +export const AccordionItemAppearance = { + outline: 'outline', + ghost: 'ghost', + block: 'block' +} as const; +export type AccordionItemAppearance = (typeof AccordionItemAppearance)[keyof typeof AccordionItemAppearance]; diff --git a/packages/ok-components/src/all-components.ts b/packages/ok-components/src/all-components.ts index a07b59ba2d..19386f03b7 100644 --- a/packages/ok-components/src/all-components.ts +++ b/packages/ok-components/src/all-components.ts @@ -6,5 +6,6 @@ import '@ni/spright-components/dist/esm/all-components'; +import './accordion-item'; import './button'; import './icon-dynamic'; diff --git a/packages/storybook/src/ok/accordion-item/accordion-item-matrix.stories.ts b/packages/storybook/src/ok/accordion-item/accordion-item-matrix.stories.ts new file mode 100644 index 0000000000..17407a2e61 --- /dev/null +++ b/packages/storybook/src/ok/accordion-item/accordion-item-matrix.stories.ts @@ -0,0 +1,58 @@ +import type { StoryFn, Meta } from '@storybook/html-vite'; +import { html, ViewTemplate } from '@ni/fast-element'; +import { accordionItemTag } from '@ni/ok-components/dist/esm/accordion-item'; +import { AccordionItemAppearance } from '@ni/ok-components/dist/esm/accordion-item/types'; +import { textFieldTag } from '@ni/nimble-components/dist/esm/text-field'; +import { + createMatrix, + sharedMatrixParameters, + createMatrixThemeStory +} from '../../utilities/matrix'; +import { createStory } from '../../utilities/storybook'; +import { hiddenWrapper } from '../../utilities/hidden'; + +const expandedStates = [ + ['Expanded', true], + ['Collapsed', false] +] as const; +type ExpandedState = (typeof expandedStates)[number]; + +const appearanceStates = [ + ['Ghost', AccordionItemAppearance.ghost], + ['Outline', AccordionItemAppearance.outline], + ['Block', AccordionItemAppearance.block] +] as const; +type AppearanceState = (typeof appearanceStates)[number]; + +const metadata: Meta = { + title: 'Tests Ok/Accordion Item', + parameters: { + ...sharedMatrixParameters() + } +}; + +export default metadata; + +const component = ( + [expandedName, expanded]: ExpandedState, + [appearanceName, appearance]: AppearanceState +): ViewTemplate => html` + <${accordionItemTag} + header="${() => `${appearanceName} ${expandedName}`}" + ?expanded="${() => expanded}" + appearance="${() => appearance}" + style="margin-right: 8px; margin-bottom: 8px; width: 300px;"> + <${textFieldTag} placeholder="Enter name" appearance="underline"> + <${textFieldTag} placeholder="Enter category" appearance="underline"> + +`; + +export const themeMatrix: StoryFn = createMatrixThemeStory( + createMatrix(component, [expandedStates, appearanceStates]) +); + +export const hidden: StoryFn = createStory( + hiddenWrapper( + html`<${accordionItemTag} hidden header="Hidden">Content` + ) +); diff --git a/packages/storybook/src/ok/accordion-item/accordion-item.mdx b/packages/storybook/src/ok/accordion-item/accordion-item.mdx new file mode 100644 index 0000000000..d80e310535 --- /dev/null +++ b/packages/storybook/src/ok/accordion-item/accordion-item.mdx @@ -0,0 +1,65 @@ +import { Controls, Canvas, Meta, Title } from '@storybook/addon-docs/blocks'; +import * as accordionItemStories from './accordion-item.stories'; +import ComponentApisLink from '../../docs/component-apis-link.mdx'; + + + + +An accordion item is a collapsible section that can be expanded or collapsed +to show or hide its content. It uses a header with an expander icon to toggle +visibility. + +<Canvas of={accordionItemStories.outline} /> + +## API + +<Controls of={accordionItemStories.outline} /> +<ComponentApisLink /> + +## Appearance + +### Ghost + +No borders or background. Use for nested accordions or minimal visual chrome. + +<Canvas of={accordionItemStories.ghost} /> + +### Outline + +A bottom border divider between items. This is the default appearance. + +<Canvas of={accordionItemStories.outline} /> + +### Block + +A filled background on each header row. + +<Canvas of={accordionItemStories.block} /> + +## Examples + +### Outline Group + +Multiple outline accordion items used together as a form layout (e.g. checkout flow). + +<Canvas of={accordionItemStories.outlineGroup} /> + +### Block Group + +Multiple block accordion items for settings or configuration panels. + +<Canvas of={accordionItemStories.blockGroup} /> + +### Nested Accordion + +Accordion items nested inside other accordion items. Per the design spec, nested +accordions should always use the ghost appearance. + +<Canvas of={accordionItemStories.nestedAccordion} /> + +### Mixed Content + +Accordion items containing various types of interactive content — checkboxes, +selects, and number fields. + +<Canvas of={accordionItemStories.mixedContent} /> diff --git a/packages/storybook/src/ok/accordion-item/accordion-item.stories.ts b/packages/storybook/src/ok/accordion-item/accordion-item.stories.ts new file mode 100644 index 0000000000..3d43c10cdc --- /dev/null +++ b/packages/storybook/src/ok/accordion-item/accordion-item.stories.ts @@ -0,0 +1,213 @@ +import type { Meta, StoryObj } from '@storybook/html-vite'; +import { html } from '@ni/fast-element'; +import { accordionItemTag } from '@ni/ok-components/dist/esm/accordion-item'; +import { AccordionItemAppearance } from '@ni/ok-components/dist/esm/accordion-item/types'; +import { textFieldTag } from '@ni/nimble-components/dist/esm/text-field'; +import { numberFieldTag } from '@ni/nimble-components/dist/esm/number-field'; +import { checkboxTag } from '@ni/nimble-components/dist/esm/checkbox'; +import { selectTag } from '@ni/nimble-components/dist/esm/select'; +import { listOptionTag } from '@ni/nimble-components/dist/esm/list-option'; +import { + apiCategory, + createUserSelectedThemeStory +} from '../../utilities/storybook'; + +interface AccordionItemArgs { + header: string; + expanded: boolean; + appearance: AccordionItemAppearance; +} + +const metadata: Meta<AccordionItemArgs> = { + title: 'Ok/Accordion Item', + parameters: { + actions: {} + }, + render: createUserSelectedThemeStory(html` + <${accordionItemTag} + header="${x => x.header}" + ?expanded="${x => x.expanded}" + appearance="${x => x.appearance}" + > + <${textFieldTag} placeholder="Enter name" appearance="underline"></${textFieldTag}> + <${textFieldTag} placeholder="Enter category" appearance="underline"></${textFieldTag}> + </${accordionItemTag}> + `), + argTypes: { + header: { + description: + 'The text displayed in the accordion item header.', + table: { category: apiCategory.attributes } + }, + expanded: { + description: + 'Controls whether the accordion item content is visible.', + table: { category: apiCategory.attributes } + }, + appearance: { + description: + 'The visual appearance of the accordion item: ghost, outline, or block.', + options: Object.values(AccordionItemAppearance), + control: { type: 'radio' }, + table: { category: apiCategory.attributes } + } + }, + args: { + header: 'Expanded Accordion', + expanded: true, + appearance: AccordionItemAppearance.outline + } +}; + +export default metadata; + +export const ghost: StoryObj<AccordionItemArgs> = { + args: { + header: 'Ghost Accordion', + expanded: true, + appearance: AccordionItemAppearance.ghost + } +}; + +export const outline: StoryObj<AccordionItemArgs> = { + args: { + header: 'Outline Accordion', + expanded: true, + appearance: AccordionItemAppearance.outline + } +}; + +export const block: StoryObj<AccordionItemArgs> = { + args: { + header: 'Block Accordion', + expanded: true, + appearance: AccordionItemAppearance.block + } +}; + +export const outlineGroup: StoryObj<AccordionItemArgs> = { + render: createUserSelectedThemeStory(html` + <div style="width: 400px;"> + <${accordionItemTag} header="Shipping" appearance="${x => x.appearance}" expanded> + <${textFieldTag} appearance="underline">Address</${textFieldTag}> + <${textFieldTag} appearance="underline">City</${textFieldTag}> + <${selectTag}> + State + <${listOptionTag} value="CA">California</${listOptionTag}> + <${listOptionTag} value="NY">New York</${listOptionTag}> + <${listOptionTag} value="TX">Texas</${listOptionTag}> + </${selectTag}> + </${accordionItemTag}> + <${accordionItemTag} header="Payment" appearance="${x => x.appearance}"> + <${textFieldTag} appearance="underline">Card number</${textFieldTag}> + <${numberFieldTag} appearance="underline">CVV</${numberFieldTag}> + </${accordionItemTag}> + <${accordionItemTag} header="Delivery" appearance="${x => x.appearance}"> + <${checkboxTag}>Express shipping</${checkboxTag}> + <${checkboxTag}>Gift wrapping</${checkboxTag}> + </${accordionItemTag}> + </div> + `), + args: { + appearance: AccordionItemAppearance.outline + }, + argTypes: { + header: { control: false, table: { disable: true } }, + expanded: { control: false, table: { disable: true } } + } +}; + +export const blockGroup: StoryObj<AccordionItemArgs> = { + render: createUserSelectedThemeStory(html` + <div style="width: 400px;"> + <${accordionItemTag} header="General" appearance="${x => x.appearance}" expanded> + <${textFieldTag} appearance="underline">Project name</${textFieldTag}> + <${textFieldTag} appearance="underline">Description</${textFieldTag}> + </${accordionItemTag}> + <${accordionItemTag} header="Configuration" appearance="${x => x.appearance}"> + <${checkboxTag}>Enable notifications</${checkboxTag}> + <${checkboxTag}>Auto-save</${checkboxTag}> + </${accordionItemTag}> + <${accordionItemTag} header="Advanced" appearance="${x => x.appearance}"> + <${numberFieldTag} appearance="underline">Timeout (ms)</${numberFieldTag}> + <${numberFieldTag} appearance="underline">Max retries</${numberFieldTag}> + </${accordionItemTag}> + </div> + `), + args: { + appearance: AccordionItemAppearance.block + }, + argTypes: { + header: { control: false, table: { disable: true } }, + expanded: { control: false, table: { disable: true } } + } +}; + +export const nestedAccordion: StoryObj<AccordionItemArgs> = { + render: createUserSelectedThemeStory(html` + <div style="width: 400px;"> + <${accordionItemTag} header="Versioning" appearance="${x => x.appearance}" expanded> + <${accordionItemTag} header="GET / API information" appearance="ghost" expanded> + <p style="margin: 0;">Returns information about API versions and available operations.</p> + </${accordionItemTag}> + <${accordionItemTag} header="GET / v2 API version information" appearance="ghost"> + <p style="margin: 0;">Returns details for the v2 API endpoint.</p> + </${accordionItemTag}> + </${accordionItemTag}> + <${accordionItemTag} header="Subscriptions" appearance="${x => x.appearance}"> + <${accordionItemTag} header="List subscriptions" appearance="ghost"> + <p style="margin: 0;">Returns all active subscriptions.</p> + </${accordionItemTag}> + <${accordionItemTag} header="Create subscription" appearance="ghost"> + <${textFieldTag} appearance="underline">Name</${textFieldTag}> + </${accordionItemTag}> + </${accordionItemTag}> + <${accordionItemTag} header="Tags" appearance="${x => x.appearance}"> + <${checkboxTag}>Include archived</${checkboxTag}> + </${accordionItemTag}> + </div> + `), + args: { + appearance: AccordionItemAppearance.outline + }, + argTypes: { + header: { control: false, table: { disable: true } }, + expanded: { control: false, table: { disable: true } } + } +}; + +export const mixedContent: StoryObj<AccordionItemArgs> = { + render: createUserSelectedThemeStory(html` + <div style="width: 400px;"> + <${accordionItemTag} header="Size" appearance="${x => x.appearance}" expanded> + <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 4px;"> + <${checkboxTag}>XS</${checkboxTag}> + <${checkboxTag}>S</${checkboxTag}> + <${checkboxTag}>M</${checkboxTag}> + <${checkboxTag}>L</${checkboxTag}> + <${checkboxTag}>XL</${checkboxTag}> + <${checkboxTag}>XXL</${checkboxTag}> + </div> + </${accordionItemTag}> + <${accordionItemTag} header="Color" appearance="${x => x.appearance}"> + <${selectTag}> + Color + <${listOptionTag} value="red">Red</${listOptionTag}> + <${listOptionTag} value="blue">Blue</${listOptionTag}> + <${listOptionTag} value="green">Green</${listOptionTag}> + </${selectTag}> + </${accordionItemTag}> + <${accordionItemTag} header="Price" appearance="${x => x.appearance}"> + <${numberFieldTag} appearance="underline">Min</${numberFieldTag}> + <${numberFieldTag} appearance="underline">Max</${numberFieldTag}> + </${accordionItemTag}> + </div> + `), + args: { + appearance: AccordionItemAppearance.outline + }, + argTypes: { + header: { control: false, table: { disable: true } }, + expanded: { control: false, table: { disable: true } } + } +};