-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: addition of progressbar widget (#109)
- Loading branch information
1 parent
909f58a
commit f6600cb
Showing
44 changed files
with
1,521 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import {ProgressbarComponent, provideWidgetsConfig} from '@agnos-ui/angular'; | ||
import {Component} from '@angular/core'; | ||
|
||
@Component({ | ||
standalone: true, | ||
imports: [ProgressbarComponent], | ||
providers: [ | ||
provideWidgetsConfig((config) => { | ||
config.progressbar = {...config.progressbar, slotDefault: (widget) => `${widget.state.percentage}%`}; | ||
return config; | ||
}), | ||
], | ||
template: ` | ||
<div class="d-flex flex-column gap-2"> | ||
<div auProgressbar [auValue]="20"></div> | ||
<div auProgressbar [auValue]="40" auClassName="text-bg-success"></div> | ||
<div auProgressbar [auValue]="60" auClassName="text-bg-info"></div> | ||
<div auProgressbar [auValue]="80" auClassName="text-bg-warning"></div> | ||
<div auProgressbar [auValue]="100" auClassName="text-bg-danger"></div> | ||
</div> | ||
`, | ||
}) | ||
export default class DefaultProgressBarComponent {} |
80 changes: 80 additions & 0 deletions
80
angular/demo/src/app/samples/progressbar/fullCustom.route.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import {ProgressbarComponent, ProgressbarContentDirective} from '@agnos-ui/angular'; | ||
import {NgIf} from '@angular/common'; | ||
import type {OnDestroy} from '@angular/core'; | ||
import {Component} from '@angular/core'; | ||
import type {Subscription} from 'rxjs'; | ||
import {interval, takeWhile} from 'rxjs'; | ||
|
||
@Component({ | ||
standalone: true, | ||
imports: [ProgressbarComponent, ProgressbarContentDirective, NgIf], | ||
template: ` | ||
<div class="d-flex align-items-center flex-wrap"> | ||
<div style="width: 350px"> | ||
<div auProgressbar #progressbar [auValue]="value"> | ||
<ng-template auProgressbarContent let-state="state"> | ||
<div class="position-relative" style="height: 300px"> | ||
<div class="cup"> | ||
<div class="cup-fill-parent"> | ||
<div class="cup-fill" [style.height.px]="value * 1.7"> | ||
<div class="bubble bubble-1" *ngIf="value >= 50"></div> | ||
<div class="bubble bubble-2" *ngIf="value >= 50"></div> | ||
<div class="bubble bubble-3" *ngIf="value >= 50"></div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</ng-template> | ||
</div> | ||
</div> | ||
<div class="d-flex flex-column justify-content-evenly h-100 ms-5"> | ||
<div class="btn-group" role="group"> | ||
<button class="btn btn-outline-primary" (click)="start()" [disabled]="progressbar.state().started">Start</button> | ||
<button | ||
class="btn btn-outline-primary" | ||
[disabled]="!progressbar.state().started || progressbar.state().finished" | ||
(click)="toggleProgress()" | ||
> | ||
{{ subscription ? 'Pause' : 'Resume' }} | ||
</button> | ||
<button class="btn btn-outline-primary" [disabled]="!progressbar.state().started" (click)="stop(true)">Reset</button> | ||
</div> | ||
<p class="mt-3"> | ||
<span>{{ value === 0 ? 'Need to wake up.' : value < 100 ? 'Retrieving coffee... ' + value + '%' : 'Ready to work !' }}</span> | ||
</p> | ||
</div> | ||
</div> | ||
`, | ||
styles: ["@import '@agnos-ui/common/samples/progressbar/custom.scss';"], | ||
}) | ||
export default class FullCustomProgressBarComponent implements OnDestroy { | ||
value = 0; | ||
subscription: Subscription | undefined; | ||
|
||
start() { | ||
if (!this.subscription) { | ||
this.subscription = interval(500) | ||
.pipe(takeWhile(() => this.value < 100)) | ||
.subscribe(() => { | ||
this.value += 10; | ||
}); | ||
} | ||
} | ||
stop(reset = false) { | ||
this.subscription?.unsubscribe(); | ||
this.subscription = undefined; | ||
if (reset) { | ||
this.value = 0; | ||
} | ||
} | ||
toggleProgress() { | ||
if (this.subscription) { | ||
this.stop(); | ||
} else { | ||
this.start(); | ||
} | ||
} | ||
ngOnDestroy() { | ||
this.stop(); | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
angular/demo/src/app/samples/progressbar/playground.route.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import {ProgressbarComponent} from '@agnos-ui/angular'; | ||
import {getProgressbarDefaultConfig} from '@agnos-ui/core'; | ||
import {Component, ViewChild} from '@angular/core'; | ||
import {getUndefinedValues, hashChangeHook, provideHashConfig} from '../../utils'; | ||
|
||
const undefinedConfig = getUndefinedValues(getProgressbarDefaultConfig()); | ||
|
||
@Component({ | ||
standalone: true, | ||
imports: [ProgressbarComponent], | ||
providers: provideHashConfig('progressbar'), | ||
template: `<div auProgressbar #widget></div>`, | ||
}) | ||
export default class PlaygroundComponent { | ||
@ViewChild('widget') widget: ProgressbarComponent; | ||
|
||
constructor() { | ||
hashChangeHook((props) => { | ||
this.widget?._widget.patch({...undefinedConfig, ...props}); | ||
}); | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
angular/demo/src/app/samples/progressbar/simpleCustom.route.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import {ProgressbarComponent} from '@agnos-ui/angular'; | ||
import {Component} from '@angular/core'; | ||
|
||
@Component({ | ||
standalone: true, | ||
imports: [ProgressbarComponent], | ||
template: ` | ||
<div class="d-flex flex-column gap-2"> | ||
<div> | ||
A progressbar using custom values for minimum and maximum: | ||
<div auProgressbar [auMin]="1" [auMax]="5" [auValue]="4" [auAriaValueTextFn]="valueText">Step 4 out of 5</div> | ||
</div> | ||
<div> | ||
A striped animated progress bar: | ||
<div auProgressbar auClassName="text-bg-info" [auValue]="63" [auStriped]="true" [auAnimated]="true"></div> | ||
</div> | ||
<div> | ||
Changing the height: | ||
<div auProgressbar [auHeight]="'1.5rem'" [auValue]="47"></div> | ||
</div> | ||
</div> | ||
`, | ||
}) | ||
export default class SimpleCustomProgressBarComponent { | ||
readonly valueText = (val: number, _min: number, max: number) => `Step ${val} out of ${max}`; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
140 changes: 140 additions & 0 deletions
140
angular/lib/src/lib/progressbar/progressbar.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import type {ProgressbarContext, ProgressbarProps, ProgressbarState, SlotContent} from '@agnos-ui/angular-headless'; | ||
import { | ||
ComponentTemplate, | ||
SlotDirective, | ||
callWidgetFactory, | ||
createProgressbar, | ||
patchSimpleChanges, | ||
toSlotContextWidget, | ||
SlotDefaultDirective, | ||
} from '@agnos-ui/angular-headless'; | ||
import type {AfterContentChecked, OnChanges, Signal, SimpleChanges} from '@angular/core'; | ||
import {NgClass, NgIf} from '@angular/common'; | ||
import {ChangeDetectionStrategy, Component, ContentChild, Directive, Input, TemplateRef, ViewChild, inject} from '@angular/core'; | ||
import {writable} from '@amadeus-it-group/tansu'; | ||
import {toSignal} from '@angular/core/rxjs-interop'; | ||
|
||
@Directive({selector: 'ng-template[auProgressbarContent]', standalone: true}) | ||
export class ProgressbarContentDirective { | ||
public templateRef = inject(TemplateRef<ProgressbarContext>); | ||
static ngTemplateContextGuard(_dir: ProgressbarContentDirective, context: unknown): context is ProgressbarContext { | ||
return true; | ||
} | ||
} | ||
|
||
@Component({ | ||
standalone: true, | ||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
imports: [NgClass, NgIf, SlotDirective, ProgressbarContentDirective], | ||
template: ` | ||
<ng-template auProgressbarContent #content let-state="state" let-widget="widget"> | ||
<div class="progress" [style.height]="state.height"> | ||
<div | ||
class="progress-bar" | ||
[class.progress-bar-striped]="state.striped" | ||
[class.progress-bar-animated]="state.animated" | ||
[ngClass]="state.className" | ||
[style.width.%]="state.percentage" | ||
> | ||
<ng-template [auSlot]="state.slotDefault" [auSlotProps]="{state, widget}"></ng-template> | ||
</div> | ||
</div> | ||
</ng-template> | ||
`, | ||
}) | ||
export class ProgressbarDefaultSlotsComponent { | ||
@ViewChild('content', {static: true}) content: TemplateRef<ProgressbarContext>; | ||
} | ||
|
||
export const progressbarDefaultSlotContent = new ComponentTemplate(ProgressbarDefaultSlotsComponent, 'content'); | ||
|
||
const defaultConfig: Partial<ProgressbarProps> = { | ||
slotContent: progressbarDefaultSlotContent, | ||
}; | ||
|
||
@Component({ | ||
selector: '[auProgressbar]', | ||
standalone: true, | ||
imports: [SlotDirective, SlotDefaultDirective], | ||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
host: { | ||
role: 'progressbar', | ||
'[attr.aria-label]': 'state().ariaLabel || undefined', | ||
'[attr.aria-valuenow]': 'state().value', | ||
'[attr.aria-valuemin]': 'state().min', | ||
'[attr.aria-valuemax]': 'state().max', | ||
'[attr.aria-valuetext]': 'state().ariaValueText', | ||
}, | ||
template: ` | ||
<ng-template [auSlotDefault]="defaultSlots"><ng-content></ng-content></ng-template> | ||
<ng-template [auSlot]="state().slotContent" [auSlotProps]="{state: state(), widget}"></ng-template> | ||
`, | ||
}) | ||
export class ProgressbarComponent implements AfterContentChecked, OnChanges { | ||
readonly defaultSlots = writable(defaultConfig); | ||
|
||
/** | ||
* The aria label. | ||
*/ | ||
@Input('auAriaLabel') ariaLabel: string | undefined; | ||
|
||
/** | ||
* The minimum value. | ||
*/ | ||
@Input('auMin') min: number | undefined; | ||
|
||
/** | ||
* The maximum value. | ||
*/ | ||
@Input('auMax') max: number | undefined; | ||
|
||
/** | ||
* The current value. | ||
*/ | ||
@Input('auValue') value: number | undefined; | ||
|
||
/** | ||
* CSS classes to be applied on the widget main container | ||
*/ | ||
@Input('auClassName') className: string | undefined; | ||
|
||
@Input('auSlotDefault') slotDefault: SlotContent<ProgressbarContext>; | ||
@Input('auSlotContent') slotContent: SlotContent<ProgressbarContext>; | ||
@ContentChild(ProgressbarContentDirective, {static: false}) slotContentFromContent: ProgressbarContentDirective | undefined; | ||
|
||
/** | ||
* Height of the progressbar, can be any valid css height value. | ||
*/ | ||
@Input('auHeight') height: string | undefined; | ||
|
||
/** | ||
* If `true`, animates a striped progressbar. | ||
* Takes effect only for browsers supporting CSS3 animations, and if `striped` is `true`. | ||
*/ | ||
@Input('auAnimated') animated: boolean | undefined; | ||
|
||
/** | ||
* If `true`, shows a striped progressbar. | ||
*/ | ||
@Input('auStriped') striped: boolean | undefined; | ||
|
||
/** | ||
* Return the value for the 'aria-valuetext' attribute. | ||
*/ | ||
@Input('auAriaValueTextFn') ariaValueTextFn: ((value: number, minimum: number, maximum: number) => string | undefined) | undefined; | ||
|
||
readonly _widget = callWidgetFactory(createProgressbar, 'progressbar', this.defaultSlots); | ||
readonly widget = toSlotContextWidget(this._widget); | ||
readonly api = this._widget.api; | ||
readonly state: Signal<ProgressbarState> = toSignal(this._widget.state$, {requireSync: true}); | ||
|
||
ngAfterContentChecked(): void { | ||
this._widget.patch({ | ||
slotContent: this.slotContentFromContent?.templateRef, | ||
}); | ||
} | ||
|
||
ngOnChanges(changes: SimpleChanges): void { | ||
patchSimpleChanges(this._widget.patch, changes); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.