Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions 2nd-gen/packages/core/components/button-group/ButtonGroup.base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/**
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import { PropertyValues } from 'lit';
import { property } from 'lit/decorators.js';

import { SpectrumElement } from '@spectrum-web-components/core/element/index.js';
import { SizedMixin } from '@spectrum-web-components/core/mixins/index.js';

import {
BUTTON_GROUP_ALIGNMENTS,
BUTTON_GROUP_ORIENTATIONS,
BUTTON_GROUP_SIZES,
type ButtonGroupAlignment,
type ButtonGroupOrientation,
type ButtonGroupSize,
} from './ButtonGroup.types.js';

/**
* A button group clusters related actions together, providing consistent
* spacing, sizing, and orientation. The host exposes `role="group"` and
* propagates `size` and `disabled` to slotted button children.
*
* This base class owns shared logic and accessibility semantics. Rendering
* and styling live in the concrete SWC subclass.
*
* @slot - One or more `swc-button` elements.
*
* @attribute {ElementSize} size - The size of the button group and its children.
*/
export abstract class ButtonGroupBase extends SizedMixin(SpectrumElement, {
validSizes: BUTTON_GROUP_SIZES,
}) {
/**
* The size of the button group. Propagated to all slotted button children.
*
* @default m
*/
declare public size: ButtonGroupSize;

// ──────────────────
// SHARED API
// ──────────────────

/**
* @internal
*
* Valid orientation values for validation.
*/
static readonly ORIENTATIONS: readonly string[] = BUTTON_GROUP_ORIENTATIONS;

/**
* @internal
*
* Valid alignment values for validation.
*/
static readonly ALIGNMENTS: readonly string[] = BUTTON_GROUP_ALIGNMENTS;

/**
* The layout direction of the button group.
*/
@property({ type: String, reflect: true })
public orientation: ButtonGroupOrientation = 'horizontal';

/**
* Whether all buttons in the group are disabled. When set, propagates
* the disabled state to each slotted button child.
*/
@property({ type: Boolean, reflect: true })
public disabled = false;

/**
* The alignment of buttons within the group along the main axis.
*/
@property({ type: String, reflect: true })
public align: ButtonGroupAlignment = 'start';

// ──────────────────────
// IMPLEMENTATION
// ──────────────────────

protected override firstUpdated(changed: PropertyValues<this>): void {
super.firstUpdated(changed);
this.setAttribute('role', 'group');
}

protected override update(changedProperties: PropertyValues): void {
if (window.__swc?.DEBUG) {
const constructor = this.constructor as typeof ButtonGroupBase;
if (!constructor.ORIENTATIONS.includes(this.orientation)) {
window.__swc.warn(
this,
`<${this.localName}> element expects the "orientation" attribute to be one of the following:`,
'https://opensource.adobe.com/spectrum-web-components/components/button-group/',
{ issues: [...constructor.ORIENTATIONS] }
);
}
if (!constructor.ALIGNMENTS.includes(this.align)) {
window.__swc.warn(
this,
`<${this.localName}> element expects the "align" attribute to be one of the following:`,
'https://opensource.adobe.com/spectrum-web-components/components/button-group/',
{ issues: [...constructor.ALIGNMENTS] }
);
}
}
super.update(changedProperties);
}

protected override updated(changed: PropertyValues<this>): void {
super.updated(changed);

if (changed.has('size') || changed.has('disabled')) {
this.propagateToChildren();
}
}

/**
* Handles slotchange events from the default slot. Ensures newly slotted
* children receive the current size and disabled state.
*/
protected handleSlotchange(): void {
this.propagateToChildren();
}

private propagateToChildren(): void {
const slot = this.renderRoot?.querySelector('slot');
if (!slot) {return;}

const buttons = slot.assignedElements() as HTMLElement[];
for (const button of buttons) {
if ('size' in button) {
(button as HTMLElement & { size: string }).size = this.size;
}
if ('disabled' in button) {
(button as HTMLElement & { disabled: boolean }).disabled = this.disabled;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import type { ElementSize } from '@spectrum-web-components/core/mixins/index.js';

// ──────────────────
// SHARED
// ──────────────────

export const BUTTON_GROUP_SIZES = [
's',
'm',
'l',
'xl',
] as const satisfies readonly ElementSize[];

export const BUTTON_GROUP_ORIENTATIONS = [
'horizontal',
'vertical',
] as const;

export const BUTTON_GROUP_ALIGNMENTS = [
'start',
'center',
'end',
] as const;

// ──────────────────
// TYPES
// ──────────────────

export type ButtonGroupSize = (typeof BUTTON_GROUP_SIZES)[number];
export type ButtonGroupOrientation = (typeof BUTTON_GROUP_ORIENTATIONS)[number];
export type ButtonGroupAlignment = (typeof BUTTON_GROUP_ALIGNMENTS)[number];
13 changes: 13 additions & 0 deletions 2nd-gen/packages/core/components/button-group/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
export * from './ButtonGroup.base.js';
export * from './ButtonGroup.types.js';
1 change: 1 addition & 0 deletions 2nd-gen/packages/core/mixins/sized-mixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export function SizedMixin<T extends Constructor<ReactiveElement>>(
this.requestUpdate('size', oldSize);
}

/** @internal */
private _size: ElementSize | null = defaultSize;

protected override update(changes: PropertyValues): void {
Expand Down
7 changes: 7 additions & 0 deletions 2nd-gen/packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
"types": "./dist/components/button/index.d.ts",
"import": "./dist/components/button/index.js"
},
"./components/button-group": {
"types": "./dist/components/button-group/index.d.ts",
"import": "./dist/components/button-group/index.js"
},
"./components/divider": {
"types": "./dist/components/divider/index.d.ts",
"import": "./dist/components/divider/index.js"
Expand Down Expand Up @@ -160,6 +164,9 @@
"components/button": [
"dist/components/button/index.d.ts"
],
"components/button-group": [
"dist/components/button-group/index.d.ts"
],
"components/divider": [
"dist/components/divider/index.d.ts"
],
Expand Down
58 changes: 58 additions & 0 deletions 2nd-gen/packages/swc/components/button-group/ButtonGroup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import { CSSResultArray, html, TemplateResult } from 'lit';
import { classMap } from 'lit/directives/class-map.js';

import { ButtonGroupBase } from '@spectrum-web-components/core/components/button-group';
import { capitalize } from '@spectrum-web-components/core/utils/index.js';

import styles from './button-group.css';

/**
* A button group clusters related actions together, providing consistent
* spacing, sizing, and orientation.
*
* @element swc-button-group
* @since 2.0.0
*
* @slot - One or more `swc-button` elements.
*
* @cssprop --swc-button-group-gap - Space between buttons in the group.
* @cssprop --swc-button-group-justify-content - Alignment of buttons within the group along the main axis.
*/
export class ButtonGroup extends ButtonGroupBase {
// ──────────────────────────────
// RENDERING & STYLING
// ──────────────────────────────

public static override get styles(): CSSResultArray {
return [styles];
}

protected override render(): TemplateResult {
return html`
<div
class=${classMap({
['swc-ButtonGroup']: true,
['swc-ButtonGroup--vertical']: this.orientation === 'vertical',
[`swc-ButtonGroup--size${this.size?.toUpperCase()}`]:
this.size != null,
[`swc-ButtonGroup--align${capitalize(this.align)}`]:
this.align !== 'start',
})}
>
<slot @slotchange=${this.handleSlotchange}></slot>
</div>
`;
}
}
50 changes: 50 additions & 0 deletions 2nd-gen/packages/swc/components/button-group/button-group.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

:host {
display: flex;
}

* {
box-sizing: border-box;
}

.swc-ButtonGroup {
--_swc-button-group-gap: var(--swc-button-group-gap, token("spacing-300"));

display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: var(--_swc-button-group-gap);
justify-content: var(--swc-button-group-justify-content, normal);
}

:host([size="s"]) {
--swc-button-group-gap: token("spacing-200");
}

.swc-ButtonGroup--vertical {
display: inline-flex;
flex-direction: column;
}

.swc-ButtonGroup--alignCenter {
justify-content: center;
}

.swc-ButtonGroup--alignEnd {
justify-content: flex-end;
}

::slotted(*) {
flex-shrink: 0;
}
12 changes: 12 additions & 0 deletions 2nd-gen/packages/swc/components/button-group/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
export * from './ButtonGroup.js';
Loading
Loading