Skip to content

Commit

Permalink
refactor expander-section to use expander-item to be more flexible an…
Browse files Browse the repository at this point in the history
…d allow tooltips via content projection
  • Loading branch information
Pablo Lopez committed Aug 30, 2024
1 parent 0a0c8f4 commit 7188980
Show file tree
Hide file tree
Showing 9 changed files with 190 additions and 88 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<input
type="radio"
[value]="value"
[id]="'layer-' + value"
[name]="groupName"
[checked]="checked"
(click)="optionSelected.emit(value)" />
<label [for]="'layer-' + value">{{ label }}</label>
<ng-container *ngIf="showTooltip">
<button mat-icon-button class="info" [matMenuTriggerFor]="tooltipMenu">
<mat-icon class="material-symbols-outlined info" color="primary">
info_outline
</mat-icon>
</button>
<mat-menu #tooltipMenu="matMenu">
<div class="tooltip">
<ng-content></ng-content>
</div>
</mat-menu>
</ng-container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@import "mixins";

:host {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
text-transform: capitalize;
}

input {
margin: 0;
width: 20px;
cursor: pointer;
}

label {
@include small-paragraph();
cursor: pointer;
}

.info {
width: 22px;
height: 22px;
font-size: 22px;
line-height: 0;
}

button.info {
margin-left: auto;
}

.tooltip {
padding: 20px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { ExpanderItemComponent } from './expander-item.component';

describe('ExpanderItemComponent', () => {
let component: ExpanderItemComponent;
let fixture: ComponentFixture<ExpanderItemComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ExpanderItemComponent],
}).compileComponents();

fixture = TestBed.createComponent(ExpanderItemComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { MatIconModule } from '@angular/material/icon';
import { MatLegacyButtonModule } from '@angular/material/legacy-button';
import { MatLegacyMenuModule } from '@angular/material/legacy-menu';
import { NgIf, NgSwitchCase } from '@angular/common';

/**
* This component is used in combination with `<sg-expander-section>`.
* It shows a radio button with its label, and additionally a tooltip.
* The markup provided in content projection will be used for the tooltip.
*/
@Component({
selector: 'sg-expander-item',
standalone: true,
imports: [
MatIconModule,
MatLegacyButtonModule,
MatLegacyMenuModule,
NgSwitchCase,
NgIf,
],
templateUrl: './expander-item.component.html',
styleUrl: './expander-item.component.scss',
})
export class ExpanderItemComponent {
/**
* The label for the radio button
*/
@Input() label: string = '';
/**
* The value for the radio button
*/
@Input() value: string | number = '';
/**
* The name of the group (the input name) for the radio button
*/
@Input() groupName: string = '';
/**
* Whether the item is checked (selected)
*/
@Input() checked = false;
/**
* Whether it shows the tooltip icon
*/
@Input() showTooltip = false;
/**
* Emitted when the user changes the selected option
*/
@Output() optionSelected = new EventEmitter<string | number>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,5 @@
{{ title }}
</div>
<div class="base-options" *ngIf="isOpen">
<div *ngFor="let t of options" class="base-option">
<input
type="radio"
[value]="getValue(t)"
[id]="'layer-' + getValue(t)"
[name]="groupName"
[checked]="t === defaultOption"
(click)="optionSelected.emit(t)" />
<label [for]="'layer-' + getValue(t)">{{ getText(t) }}</label>
</div>
<ng-content></ng-content>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,3 @@
.base-options {
padding: 16px 14px;
}

.base-option {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
text-transform: capitalize;

input {
margin: 0;
width: 20px;
cursor: pointer;
}

label {
@include small-paragraph();
cursor: pointer;

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ExpanderSectionComponent } from './expander-section.component';

describe('ExpanderSectionComponent', () => {
let component: ExpanderSectionComponent<string>;
let fixture: ComponentFixture<ExpanderSectionComponent<string>>;
let component: ExpanderSectionComponent;
let fixture: ComponentFixture<ExpanderSectionComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ExpanderSectionComponent],
}).compileComponents();

fixture = TestBed.createComponent(ExpanderSectionComponent<string>);
fixture = TestBed.createComponent(ExpanderSectionComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { NgClass, NgForOf, NgIf } from '@angular/common';

export type OptionType = { value: string | number; text: string } | string;

/**
* The expander section component shows a title with a control to expand and collapse
* inner content.
* This component accepts html via content projection.
* Its content can be any arbitrary html, or you can provide `<sg-expander-item>` to display
* a list of radio buttons with optional tooltips.
*/
@Component({
selector: 'sg-expander-section',
standalone: true,
Expand All @@ -17,25 +16,7 @@ export type OptionType = { value: string | number; text: string } | string;
styleUrl: './expander-section.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ExpanderSectionComponent<T extends OptionType> {
export class ExpanderSectionComponent {
@Input() isOpen?: boolean;
@Input() title: string = '';
@Input() groupName = '';
@Input() options: T[] = [];
@Input() defaultOption?: T;
@Output() optionSelected = new EventEmitter<T>();

getValue(item: OptionType) {
if (typeof item === 'string') {
return item;
}
return item.value;
}

getText(item: OptionType) {
if (typeof item === 'string') {
return item;
}
return item.text;
}
}
Original file line number Diff line number Diff line change
@@ -1,56 +1,79 @@
import type { Meta, StoryObj } from '@storybook/angular';
import { argsToTemplate } from '@storybook/angular';
import { ExpanderSectionComponent, OptionType } from '@styleguide';
import {
applicationConfig,
argsToTemplate,
moduleMetadata,
} from '@storybook/angular';
import { ExpanderSectionComponent } from '@styleguide';
import { provideAnimations } from '@angular/platform-browser/animations';
import { ExpanderItemComponent } from '../expander-item/expander-item.component';

const meta: Meta<ExpanderSectionComponent<OptionType>> = {
const meta: Meta<ExpanderSectionComponent> = {
title: 'Components/Expander Section',
component: ExpanderSectionComponent,
decorators: [
applicationConfig({
providers: [provideAnimations()],
}),
moduleMetadata({ imports: [ExpanderItemComponent] }),
],
tags: ['autodocs'],
render: (args) => ({
props: args,
template: `<sg-expander-section ${argsToTemplate(args)}></sg-expander-section>`,
template: `<sg-expander-section ${argsToTemplate(args)}>
<sg-expander-item label='Red' value='1' groupName='options' [checked]='true'></sg-expander-item>
<sg-expander-item label='Green' value='2' groupName='options'></sg-expander-item>
<sg-expander-item label='Blue' value='3' groupName='options'></sg-expander-item>
</sg-expander-section>`,
}),
};

export default meta;
type Story = StoryObj<ExpanderSectionComponent<string>>;

type StoryKeyValue = StoryObj<
ExpanderSectionComponent<{ value: number; text: string }>
>;
type Story = StoryObj<ExpanderSectionComponent>;

export const Default: Story = {
args: {
title: 'Pick your color',
options: ['black', 'red', 'blue', 'green'],
defaultOption: 'red',
groupName: 'options-colors',
},
};

export const Open: Story = {
args: {
title: 'Pick your color',
isOpen: true,
title: 'Pick your size',
options: ['small', 'medium', 'large', 'x-large'],
defaultOption: 'small',
groupName: 'options-pick-size',
},
};

const options = [
{ value: 1, text: 'One' },
{ value: 2, text: 'Two' },
{ value: 3, text: 'Three' },
{ value: 4, text: 'Four' },
];

export const WithKeyValue: StoryKeyValue = {
export const WithTooltips: Story = {
args: {
title: 'Pick your size',
isOpen: true,
},
render: (args) => ({
props: args,
template: `<sg-expander-section ${argsToTemplate(args)}>
<sg-expander-item label='Small' value='1' groupName='sizes' [showTooltip]='true'>
Some tooltip content
</sg-expander-item>
<sg-expander-item label='Medium' value='2' groupName='sizes' [showTooltip]='true'>
Another tooltip content
</sg-expander-item>
<sg-expander-item label='Large' value='3' groupName='sizes' [showTooltip]='true'>
This can be <strong>html</strong>
</sg-expander-item>
</sg-expander-section>`,
}),
};

export const AnyContent: Story = {
args: {
title: 'Pick your size',
options: options,
defaultOption: options[1],
groupName: 'options-keyvalue',
isOpen: true,
},
render: (args) => ({
props: args,
template: `<sg-expander-section ${argsToTemplate(args)}>
This can actually be whatever content we want.
</sg-expander-section>`,
}),
};

0 comments on commit 7188980

Please sign in to comment.