Add split button component with comprehensive functionality

- Create new SplitButtonComponent with primary action and dropdown menu
- Support for three variants: filled, tonal, outlined
- Three size options: small, medium, large
- Icon support with configurable positioning
- Configurable dropdown menu items with icons and labels
- Full accessibility support with ARIA attributes and keyboard navigation
- Interactive states: hover, active, disabled, loading
- Responsive design using semantic design tokens
- Complete demo application with multiple examples
- Integration with dashboard navigation menu

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
skyai_dev
2025-09-03 14:41:16 +10:00
parent bf67b7f955
commit 1aaef36763
8 changed files with 888 additions and 5 deletions

View File

@@ -18,6 +18,7 @@ import { ProgressDemoComponent } from './progress-demo/progress-demo.component';
import { CardDemoComponent } from './card-demo/card-demo.component';
import { ChipDemoComponent } from './chip-demo/chip-demo.component';
import { AppbarDemoComponent } from './appbar-demo/appbar-demo.component';
import { BottomNavigationDemoComponent } from './bottom-navigation-demo/bottom-navigation-demo.component';
import { FontAwesomeDemoComponent } from './fontawesome-demo/fontawesome-demo.component';
import { ImageContainerDemoComponent } from './image-container-demo/image-container-demo.component';
import { CarouselDemoComponent } from './carousel-demo/carousel-demo.component';
@@ -46,9 +47,14 @@ import { TooltipDemoComponent } from './tooltip-demo/tooltip-demo.component';
import { AccordionDemoComponent } from './accordion-demo/accordion-demo.component';
import { PopoverDemoComponent } from './popover-demo/popover-demo.component';
import { AlertDemoComponent } from './alert-demo/alert-demo.component';
import { SnackbarDemoComponent } from './snackbar-demo/snackbar-demo.component';
import { ToastDemoComponent } from './toast-demo/toast-demo.component';
import { TreeViewDemoComponent } from './tree-view-demo/tree-view-demo.component';
import { TimelineDemoComponent } from './timeline-demo/timeline-demo.component';
import { StepperDemoComponent } from './stepper-demo/stepper-demo.component';
import { FabMenuDemoComponent } from './fab-menu-demo/fab-menu-demo.component';
import { EnhancedTableDemoComponent } from './enhanced-table-demo/enhanced-table-demo.component';
import { SplitButtonDemoComponent } from './split-button-demo/split-button-demo.component';
@Component({
@@ -65,6 +71,10 @@ import { TimelineDemoComponent } from './timeline-demo/timeline-demo.component';
<ui-appbar-demo></ui-appbar-demo>
}
@case ("bottom-navigation") {
<ui-bottom-navigation-demo></ui-bottom-navigation-demo>
}
@case ("avatar") {
<ui-avatar-demo></ui-avatar-demo>
}
@@ -231,6 +241,10 @@ import { TimelineDemoComponent } from './timeline-demo/timeline-demo.component';
<ui-alert-demo></ui-alert-demo>
}
@case ("snackbar") {
<ui-snackbar-demo></ui-snackbar-demo>
}
@case ("toast") {
<ui-toast-demo></ui-toast-demo>
}
@@ -243,6 +257,22 @@ import { TimelineDemoComponent } from './timeline-demo/timeline-demo.component';
<ui-timeline-demo></ui-timeline-demo>
}
@case ("stepper") {
<ui-stepper-demo></ui-stepper-demo>
}
@case ("fab-menu") {
<ui-fab-menu-demo></ui-fab-menu-demo>
}
@case ("enhanced-table") {
<ui-enhanced-table-demo></ui-enhanced-table-demo>
}
@case ("split-button") {
<ui-split-button-demo></ui-split-button-demo>
}
}
`,
@@ -251,14 +281,14 @@ import { TimelineDemoComponent } from './timeline-demo/timeline-demo.component';
MenuDemoComponent, InputDemoComponent,
LayoutDemoComponent, RadioDemoComponent, CheckboxDemoComponent,
SearchDemoComponent, SwitchDemoComponent, ProgressDemoComponent,
AppbarDemoComponent, FontAwesomeDemoComponent, ImageContainerDemoComponent,
AppbarDemoComponent, BottomNavigationDemoComponent, FontAwesomeDemoComponent, ImageContainerDemoComponent,
CarouselDemoComponent, VideoPlayerDemoComponent, ListDemoComponent,
ModalDemoComponent, DrawerDemoComponent, DatePickerDemoComponent, TimePickerDemoComponent,
GridSystemDemoComponent, SpacerDemoComponent, ContainerDemoComponent, PaginationDemoComponent,
SkeletonLoaderDemoComponent, EmptyStateDemoComponent, FileUploadDemoComponent, FormFieldDemoComponent,
AutocompleteDemoComponent, BackdropDemoComponent, OverlayContainerDemoComponent, LoadingSpinnerDemoComponent,
ProgressCircleDemoComponent, RangeSliderDemoComponent, DividerDemoComponent, TooltipDemoComponent, AccordionDemoComponent,
PopoverDemoComponent, AlertDemoComponent, ToastDemoComponent, TreeViewDemoComponent, TimelineDemoComponent]
PopoverDemoComponent, AlertDemoComponent, SnackbarDemoComponent, ToastDemoComponent, TreeViewDemoComponent, TimelineDemoComponent, StepperDemoComponent, FabMenuDemoComponent, EnhancedTableDemoComponent, SplitButtonDemoComponent]
})

View File

@@ -0,0 +1,63 @@
@use '../../../../../shared-ui/src/styles/semantic/index' as *;
.demo-container {
padding: $semantic-spacing-component-lg;
max-width: 1200px;
margin: 0 auto;
}
.demo-section {
margin-bottom: $semantic-spacing-layout-section-lg;
h3 {
font-family: map-get($semantic-typography-heading-h3, font-family);
font-size: map-get($semantic-typography-heading-h3, font-size);
font-weight: map-get($semantic-typography-heading-h3, font-weight);
line-height: map-get($semantic-typography-heading-h3, line-height);
color: $semantic-color-text-primary;
margin-bottom: $semantic-spacing-component-md;
border-bottom: $semantic-border-width-1 solid $semantic-color-border-secondary;
padding-bottom: $semantic-spacing-component-sm;
}
}
.demo-row {
display: flex;
flex-wrap: wrap;
gap: $semantic-spacing-component-md;
align-items: flex-start;
margin-bottom: $semantic-spacing-component-lg;
}
.demo-results {
padding: $semantic-spacing-component-md;
background: $semantic-color-surface-secondary;
border: $semantic-border-width-1 solid $semantic-color-border-secondary;
border-radius: $semantic-border-radius-md;
p {
font-family: map-get($semantic-typography-body-medium, font-family);
font-size: map-get($semantic-typography-body-medium, font-size);
font-weight: map-get($semantic-typography-body-medium, font-weight);
line-height: map-get($semantic-typography-body-medium, line-height);
color: $semantic-color-text-primary;
margin: $semantic-spacing-component-xs 0;
strong {
font-weight: $semantic-typography-font-weight-semibold;
color: $semantic-color-text-primary;
}
}
}
// Responsive adjustments
@media (max-width: 768px) {
.demo-row {
flex-direction: column;
gap: $semantic-spacing-component-sm;
}
.demo-container {
padding: $semantic-spacing-component-md;
}
}

View File

@@ -0,0 +1,211 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { faDownload, faSave, faShare, faEdit, faTrash, faFile, faFileText, faPrint } from '@fortawesome/free-solid-svg-icons';
import { SplitButtonComponent, SplitButtonMenuItem } from '../../../../../ui-essentials/src/lib/components/buttons/split-button';
@Component({
selector: 'ui-split-button-demo',
standalone: true,
imports: [CommonModule, SplitButtonComponent],
template: `
<div class="demo-container">
<h2>Split Button Demo</h2>
<!-- Size Variants -->
<section class="demo-section">
<h3>Sizes</h3>
<div class="demo-row">
@for (size of sizes; track size) {
<ui-split-button
[size]="size"
[menuItems]="basicMenuItems"
(primaryClicked)="handlePrimaryClick('Primary clicked - ' + size)"
(menuItemClicked)="handleMenuItemClick($event)">
{{ size | titlecase }} Size
</ui-split-button>
}
</div>
</section>
<!-- Variant Styles -->
<section class="demo-section">
<h3>Variants</h3>
<div class="demo-row">
@for (variant of variants; track variant) {
<ui-split-button
[variant]="variant"
[menuItems]="basicMenuItems"
(primaryClicked)="handlePrimaryClick('Primary clicked - ' + variant)"
(menuItemClicked)="handleMenuItemClick($event)">
{{ variant | titlecase }}
</ui-split-button>
}
</div>
</section>
<!-- With Icons -->
<section class="demo-section">
<h3>With Icons</h3>
<div class="demo-row">
<ui-split-button
[icon]="faDownload"
iconPosition="left"
[menuItems]="exportMenuItems"
(primaryClicked)="handlePrimaryClick('Download clicked')"
(menuItemClicked)="handleMenuItemClick($event)">
Download
</ui-split-button>
<ui-split-button
variant="tonal"
[icon]="faSave"
iconPosition="right"
[menuItems]="saveMenuItems"
(primaryClicked)="handlePrimaryClick('Save clicked')"
(menuItemClicked)="handleMenuItemClick($event)">
Save
</ui-split-button>
<ui-split-button
variant="outlined"
[icon]="faShare"
[menuItems]="shareMenuItems"
(primaryClicked)="handlePrimaryClick('Share clicked')"
(menuItemClicked)="handleMenuItemClick($event)">
Share
</ui-split-button>
</div>
</section>
<!-- States -->
<section class="demo-section">
<h3>States</h3>
<div class="demo-row">
<ui-split-button
[disabled]="true"
[menuItems]="basicMenuItems">
Disabled
</ui-split-button>
<ui-split-button
[loading]="true"
[menuItems]="basicMenuItems">
Loading
</ui-split-button>
<ui-split-button
[fullWidth]=true
variant="tonal"
[menuItems]="basicMenuItems"
(primaryClicked)="handlePrimaryClick('Full width clicked')"
(menuItemClicked)="handleMenuItemClick($event)">
Full Width
</ui-split-button>
</div>
</section>
<!-- Complex Examples -->
<section class="demo-section">
<h3>Complex Examples</h3>
<div class="demo-row">
<ui-split-button
size="large"
variant="filled"
[icon]="faEdit"
[menuItems]="editMenuItems"
(primaryClicked)="handlePrimaryClick('Edit Document clicked')"
(menuItemClicked)="handleMenuItemClick($event)">
Edit Document
</ui-split-button>
<ui-split-button
size="medium"
variant="outlined"
[menuItems]="fileMenuItems"
(primaryClicked)="handlePrimaryClick('New File clicked')"
(menuItemClicked)="handleMenuItemClick($event)">
New File
</ui-split-button>
</div>
</section>
<!-- Interactive Results -->
<section class="demo-section">
<h3>Interactive Results</h3>
<div class="demo-results">
<p><strong>Last Action:</strong> {{ lastAction || 'None' }}</p>
<p><strong>Click Count:</strong> {{ clickCount }}</p>
</div>
</section>
</div>
`,
styleUrl: './split-button-demo.component.scss'
})
export class SplitButtonDemoComponent {
sizes = ['small', 'medium', 'large'] as const;
variants = ['filled', 'tonal', 'outlined'] as const;
clickCount = 0;
lastAction = '';
// Icons
faDownload = faDownload;
faSave = faSave;
faShare = faShare;
faEdit = faEdit;
faTrash = faTrash;
faFile = faFile;
faFileText = faFileText;
faPrint = faPrint;
// Menu configurations
basicMenuItems: SplitButtonMenuItem[] = [
{ label: 'Option 1', value: 'opt1' },
{ label: 'Option 2', value: 'opt2' },
{ label: 'Option 3', value: 'opt3' },
{ label: 'Disabled Option', value: 'disabled', disabled: true }
];
exportMenuItems: SplitButtonMenuItem[] = [
{ label: 'Download PDF', value: 'pdf', icon: this.faFile },
{ label: 'Download CSV', value: 'csv', icon: this.faFileText },
{ label: 'Download Excel', value: 'excel', icon: this.faFile },
{ label: 'Print', value: 'print', icon: this.faPrint }
];
saveMenuItems: SplitButtonMenuItem[] = [
{ label: 'Save As...', value: 'save-as' },
{ label: 'Save Copy', value: 'save-copy' },
{ label: 'Save Template', value: 'save-template' }
];
shareMenuItems: SplitButtonMenuItem[] = [
{ label: 'Share Link', value: 'share-link' },
{ label: 'Share via Email', value: 'share-email' },
{ label: 'Export & Share', value: 'export-share' }
];
editMenuItems: SplitButtonMenuItem[] = [
{ label: 'Quick Edit', value: 'quick-edit', icon: this.faEdit },
{ label: 'Advanced Edit', value: 'advanced-edit', icon: this.faEdit },
{ label: 'Delete', value: 'delete', icon: this.faTrash }
];
fileMenuItems: SplitButtonMenuItem[] = [
{ label: 'New Text File', value: 'new-text', icon: this.faFileText },
{ label: 'New Document', value: 'new-doc', icon: this.faFile },
{ label: 'New from Template', value: 'new-template', icon: this.faFile }
];
handlePrimaryClick(action: string): void {
this.clickCount++;
this.lastAction = action;
console.log('Primary button clicked:', action);
}
handleMenuItemClick(data: { event: Event; item: SplitButtonMenuItem }): void {
this.clickCount++;
this.lastAction = `Menu item: ${data.item.label} (value: ${data.item.value})`;
console.log('Menu item clicked:', data);
}
}

View File

@@ -10,7 +10,7 @@ import {
faGripVertical, faArrowsAlt, faBoxOpen, faChevronLeft, faSpinner, faExclamationTriangle,
faCloudUploadAlt, faFileText, faListAlt, faCircle, faExpandArrowsAlt, faCircleNotch, faSliders,
faMinus, faInfoCircle, faChevronDown, faCaretUp, faExclamationCircle, faSitemap, faStream,
faBell
faBell, faRoute, faChevronUp, faEllipsisV, faCut
} from '@fortawesome/free-solid-svg-icons';
import { DemoRoutes } from '../../demos';
import { ScrollContainerComponent } from '../../../../../ui-essentials/src/lib/layouts/scroll-container.component';
@@ -100,6 +100,10 @@ export class DashboardComponent {
faSitemap = faSitemap;
faStream = faStream;
faBell = faBell;
faRoute = faRoute;
faChevronUp = faChevronUp;
faEllipsisV = faEllipsisV;
faCut = faCut;
menuItems: any = []
@@ -153,6 +157,7 @@ export class DashboardComponent {
const dataDisplayChildren = [
this.createChildItem("accordion", "Accordion", this.faChevronDown),
this.createChildItem("table", "Tables", this.faTable),
this.createChildItem("enhanced-table", "Enhanced Table", this.faTable),
this.createChildItem("lists", "Lists", this.faList),
this.createChildItem("progress", "Progress Bars", this.faCheckSquare),
this.createChildItem("badge", "Badges", this.faCertificate),
@@ -168,8 +173,10 @@ export class DashboardComponent {
// Navigation category
const navigationChildren = [
this.createChildItem("appbar", "App Bars", this.faWindowMaximize),
this.createChildItem("bottom-navigation", "Bottom Navigation", this.faChevronUp),
this.createChildItem("menu", "Menus", this.faBars),
this.createChildItem("pagination", "Pagination", this.faChevronLeft)
this.createChildItem("pagination", "Pagination", this.faChevronLeft),
this.createChildItem("stepper", "Stepper", this.faRoute)
];
this.addMenuItem("navigation", "Navigation", this.faCompass, navigationChildren);
@@ -183,13 +190,16 @@ export class DashboardComponent {
// Actions category
const actionsChildren = [
this.createChildItem("buttons", "Buttons", this.faMousePointer)
this.createChildItem("buttons", "Buttons", this.faMousePointer),
this.createChildItem("fab-menu", "FAB Menu", this.faEllipsisV),
this.createChildItem("split-button", "Split Button", this.faCut)
];
this.addMenuItem("actions", "Actions", this.faMousePointer, actionsChildren);
// Feedback category
const feedbackChildren = [
this.createChildItem("alert", "Alert", faExclamationCircle),
this.createChildItem("snackbar", "Snackbar", this.faBell),
this.createChildItem("toast", "Toast", this.faBell),
this.createChildItem("chips", "Chips", this.faTags),
this.createChildItem("loading-spinner", "Loading Spinner", this.faSpinner),