@joanroucoux/cairn-ui 0.7.2 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +72 -0
- package/README.md +67 -0
- package/fesm2022/joanroucoux-cairn-ui-action-bar.mjs +54 -0
- package/fesm2022/joanroucoux-cairn-ui-action-bar.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-alert.mjs +129 -0
- package/fesm2022/joanroucoux-cairn-ui-alert.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-amount.mjs +114 -0
- package/fesm2022/joanroucoux-cairn-ui-amount.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-async.mjs +282 -0
- package/fesm2022/joanroucoux-cairn-ui-async.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-avatar.mjs +71 -0
- package/fesm2022/joanroucoux-cairn-ui-avatar.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-back-link.mjs +79 -0
- package/fesm2022/joanroucoux-cairn-ui-back-link.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-badge.mjs +48 -0
- package/fesm2022/joanroucoux-cairn-ui-badge.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-button.mjs +128 -0
- package/fesm2022/joanroucoux-cairn-ui-button.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-card.mjs +146 -0
- package/fesm2022/joanroucoux-cairn-ui-card.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-choice-chips.mjs +172 -0
- package/fesm2022/joanroucoux-cairn-ui-choice-chips.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-control.mjs +18 -0
- package/fesm2022/joanroucoux-cairn-ui-control.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-delta.mjs +103 -0
- package/fesm2022/joanroucoux-cairn-ui-delta.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-dialog.mjs +626 -0
- package/fesm2022/joanroucoux-cairn-ui-dialog.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-donut.mjs +347 -0
- package/fesm2022/joanroucoux-cairn-ui-donut.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-empty.mjs +47 -0
- package/fesm2022/joanroucoux-cairn-ui-empty.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-external-link.mjs +65 -0
- package/fesm2022/joanroucoux-cairn-ui-external-link.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-fact.mjs +99 -0
- package/fesm2022/joanroucoux-cairn-ui-fact.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-field.mjs +232 -0
- package/fesm2022/joanroucoux-cairn-ui-field.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-filter-chips.mjs +89 -0
- package/fesm2022/joanroucoux-cairn-ui-filter-chips.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-input.mjs +90 -0
- package/fesm2022/joanroucoux-cairn-ui-input.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-line-chart.mjs +844 -0
- package/fesm2022/joanroucoux-cairn-ui-line-chart.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-menu.mjs +241 -0
- package/fesm2022/joanroucoux-cairn-ui-menu.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-meter.mjs +86 -0
- package/fesm2022/joanroucoux-cairn-ui-meter.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-motion.mjs +415 -0
- package/fesm2022/joanroucoux-cairn-ui-motion.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-nav-item.mjs +45 -0
- package/fesm2022/joanroucoux-cairn-ui-nav-item.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-row.mjs +164 -0
- package/fesm2022/joanroucoux-cairn-ui-row.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-segmented.mjs +165 -0
- package/fesm2022/joanroucoux-cairn-ui-segmented.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-select.mjs +69 -0
- package/fesm2022/joanroucoux-cairn-ui-select.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-skeleton.mjs +91 -0
- package/fesm2022/joanroucoux-cairn-ui-skeleton.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-stale-link.mjs +43 -0
- package/fesm2022/joanroucoux-cairn-ui-stale-link.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-stat.mjs +96 -0
- package/fesm2022/joanroucoux-cairn-ui-stat.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-switch.mjs +37 -0
- package/fesm2022/joanroucoux-cairn-ui-switch.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-tab-bar.mjs +75 -0
- package/fesm2022/joanroucoux-cairn-ui-tab-bar.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-table.mjs +321 -0
- package/fesm2022/joanroucoux-cairn-ui-table.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-toast.mjs +153 -0
- package/fesm2022/joanroucoux-cairn-ui-toast.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui.mjs +0 -5462
- package/fesm2022/joanroucoux-cairn-ui.mjs.map +1 -1
- package/package.json +141 -1
- package/types/joanroucoux-cairn-ui-action-bar.d.ts +22 -0
- package/types/joanroucoux-cairn-ui-alert.d.ts +23 -0
- package/types/joanroucoux-cairn-ui-amount.d.ts +45 -0
- package/types/joanroucoux-cairn-ui-async.d.ts +58 -0
- package/types/joanroucoux-cairn-ui-avatar.d.ts +35 -0
- package/types/joanroucoux-cairn-ui-back-link.d.ts +22 -0
- package/types/joanroucoux-cairn-ui-badge.d.ts +23 -0
- package/types/joanroucoux-cairn-ui-button.d.ts +29 -0
- package/types/joanroucoux-cairn-ui-card.d.ts +36 -0
- package/types/joanroucoux-cairn-ui-choice-chips.d.ts +46 -0
- package/types/joanroucoux-cairn-ui-control.d.ts +27 -0
- package/types/joanroucoux-cairn-ui-delta.d.ts +38 -0
- package/types/joanroucoux-cairn-ui-dialog.d.ts +72 -0
- package/types/joanroucoux-cairn-ui-donut.d.ts +59 -0
- package/types/joanroucoux-cairn-ui-empty.d.ts +19 -0
- package/types/joanroucoux-cairn-ui-external-link.d.ts +15 -0
- package/types/joanroucoux-cairn-ui-fact.d.ts +44 -0
- package/types/joanroucoux-cairn-ui-field.d.ts +84 -0
- package/types/joanroucoux-cairn-ui-filter-chips.d.ts +28 -0
- package/types/joanroucoux-cairn-ui-input.d.ts +51 -0
- package/types/joanroucoux-cairn-ui-line-chart.d.ts +114 -0
- package/types/joanroucoux-cairn-ui-menu.d.ts +66 -0
- package/types/joanroucoux-cairn-ui-meter.d.ts +30 -0
- package/types/joanroucoux-cairn-ui-motion.d.ts +124 -0
- package/types/joanroucoux-cairn-ui-nav-item.d.ts +21 -0
- package/types/joanroucoux-cairn-ui-row.d.ts +78 -0
- package/types/joanroucoux-cairn-ui-segmented.d.ts +36 -0
- package/types/joanroucoux-cairn-ui-select.d.ts +23 -0
- package/types/joanroucoux-cairn-ui-skeleton.d.ts +26 -0
- package/types/joanroucoux-cairn-ui-stale-link.d.ts +24 -0
- package/types/joanroucoux-cairn-ui-stat.d.ts +32 -0
- package/types/joanroucoux-cairn-ui-switch.d.ts +17 -0
- package/types/joanroucoux-cairn-ui-tab-bar.d.ts +31 -0
- package/types/joanroucoux-cairn-ui-table.d.ts +139 -0
- package/types/joanroucoux-cairn-ui-toast.d.ts +51 -0
- package/types/joanroucoux-cairn-ui.d.ts +1 -1440
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, booleanAttribute, computed, inject, ElementRef, DestroyRef, Component } from '@angular/core';
|
|
3
|
+
import { holdTransitionsUntilRendered } from '@joanroucoux/cairn-ui/motion';
|
|
4
|
+
|
|
5
|
+
/** Available button variants. `ButtonVariant` is derived from this tuple. */
|
|
6
|
+
const BUTTON_VARIANTS = [
|
|
7
|
+
'primary',
|
|
8
|
+
'outline',
|
|
9
|
+
'ghost',
|
|
10
|
+
'destructive',
|
|
11
|
+
'quiet',
|
|
12
|
+
'quiet-destructive',
|
|
13
|
+
'outline-destructive',
|
|
14
|
+
'quiet-glow',
|
|
15
|
+
'tonal',
|
|
16
|
+
];
|
|
17
|
+
/** Available button sizes. */
|
|
18
|
+
const BUTTON_SIZES = [
|
|
19
|
+
'compact',
|
|
20
|
+
'slim',
|
|
21
|
+
'xs',
|
|
22
|
+
'sm',
|
|
23
|
+
'md',
|
|
24
|
+
'lg',
|
|
25
|
+
'xl',
|
|
26
|
+
'xxl',
|
|
27
|
+
'tall',
|
|
28
|
+
'block',
|
|
29
|
+
'icon',
|
|
30
|
+
'icon-sm',
|
|
31
|
+
];
|
|
32
|
+
const BASE_CLASSES = 'relative inline-flex items-center justify-center rounded-control font-medium whitespace-nowrap cursor-pointer select-none touch-manipulation transition-[scale,background-color] [transition-duration:var(--duration-press),var(--duration-fast)] ease-out active:scale-(--press-scale) focus-visible:outline-2 focus-visible:outline-(--ring) disabled:pointer-events-none disabled:opacity-40 aria-disabled:not-aria-busy:opacity-40';
|
|
33
|
+
const RING_OUTSIDE = 'focus-visible:outline-offset-2';
|
|
34
|
+
const RING_INSIDE = 'focus-visible:-outline-offset-2';
|
|
35
|
+
const OUTLINE = 'bg-(--card) shadow-[inset_0_0_0_1px_var(--border)] hover:bg-(--glow) active:bg-(--soft)';
|
|
36
|
+
const VARIANT_CLASSES = {
|
|
37
|
+
primary: `${RING_OUTSIDE} bg-(--primary) text-(--primary-foreground) hover:bg-(--primary-to)`,
|
|
38
|
+
outline: `${RING_OUTSIDE} ${OUTLINE} text-(--foreground)`,
|
|
39
|
+
ghost: `${RING_OUTSIDE} bg-transparent text-(--foreground) hover:bg-(--glow) active:bg-(--soft)`,
|
|
40
|
+
destructive: `${RING_OUTSIDE} bg-(--destructive) text-(--destructive-foreground)`,
|
|
41
|
+
tonal: `${RING_OUTSIDE} bg-(--muted) text-(--foreground) lg:bg-transparent lg:hover:bg-(--glow)`,
|
|
42
|
+
'quiet-glow': `${RING_INSIDE} bg-transparent text-(--muted-foreground) hover:bg-(--glow) hover:text-(--foreground)`,
|
|
43
|
+
quiet: `${RING_INSIDE} bg-transparent text-(--muted-foreground) hover:bg-(--soft) hover:text-(--foreground) active:bg-(--soft)`,
|
|
44
|
+
'quiet-destructive': `${RING_INSIDE} bg-transparent text-(--muted-foreground) hover:bg-(--glow) hover:text-(--negative) active:bg-(--soft)`,
|
|
45
|
+
'outline-destructive': `${RING_OUTSIDE} ${OUTLINE} text-(--negative)`,
|
|
46
|
+
};
|
|
47
|
+
const SIZE_CLASSES = {
|
|
48
|
+
compact: 'h-8 lg:h-7 gap-1.5 px-2.5 lg:px-2 text-label before:absolute before:-inset-x-1 before:-inset-y-1.5 lg:before:hidden',
|
|
49
|
+
slim: 'h-11 lg:h-8 gap-1.5 px-2.5 text-label',
|
|
50
|
+
xs: 'h-8 gap-1.5 px-2.5 text-label',
|
|
51
|
+
sm: 'h-9 gap-2 px-3 text-label',
|
|
52
|
+
md: 'min-h-(--row-min) gap-2 px-4 text-label has-[>span>svg:first-child]:pl-3',
|
|
53
|
+
lg: 'h-11 gap-2 px-[18px] text-label',
|
|
54
|
+
xl: 'h-[50px] gap-2 px-6 text-body',
|
|
55
|
+
xxl: 'h-[52px] lg:h-11 gap-2.5 px-6 lg:px-5 text-body lg:has-[>span>svg:first-child]:pl-4',
|
|
56
|
+
tall: 'h-12 lg:h-10 gap-2 px-4 text-body',
|
|
57
|
+
block: 'h-[50px] lg:h-11 gap-2 px-4 text-body',
|
|
58
|
+
icon: 'size-(--row-min) gap-2 p-0',
|
|
59
|
+
'icon-sm': 'size-11 pointer-fine:size-9 gap-2 p-0',
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Styled native button or anchor, applied as an attribute so the host keeps every native behavior.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* <button ui-button variant="destructive">Delete</button>
|
|
66
|
+
* <button ui-button size="icon" variant="ghost" aria-label="More actions">...</button>
|
|
67
|
+
*/
|
|
68
|
+
class UiButton {
|
|
69
|
+
variant = input('primary', /* @ts-ignore */
|
|
70
|
+
...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
71
|
+
size = input('md', /* @ts-ignore */
|
|
72
|
+
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
73
|
+
loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
74
|
+
busy = input(false, { ...(ngDevMode ? { debugName: "busy" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
75
|
+
inactive = computed(() => this.loading() || this.busy(), /* @ts-ignore */
|
|
76
|
+
...(ngDevMode ? [{ debugName: "inactive" }] : /* istanbul ignore next */ []));
|
|
77
|
+
classes = computed(() => `${BASE_CLASSES} ${VARIANT_CLASSES[this.variant()]} ${SIZE_CLASSES[this.size()]}${this.inactive() ? ' pointer-events-none opacity-70' : ''}`, /* @ts-ignore */
|
|
78
|
+
...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
79
|
+
constructor() {
|
|
80
|
+
holdTransitionsUntilRendered();
|
|
81
|
+
const host = inject(ElementRef).nativeElement;
|
|
82
|
+
const guard = (event) => {
|
|
83
|
+
if (this.inactive()) {
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
event.stopImmediatePropagation();
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
host.addEventListener('click', guard, { capture: true });
|
|
89
|
+
inject(DestroyRef).onDestroy(() => host.removeEventListener('click', guard, { capture: true }));
|
|
90
|
+
}
|
|
91
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiButton, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
92
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiButton, isStandalone: true, selector: "button[ui-button], a[ui-button]", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, busy: { classPropertyName: "busy", publicName: "busy", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()", "attr.aria-busy": "inactive() || null", "attr.aria-disabled": "inactive() || null" } }, ngImport: i0, template: `
|
|
93
|
+
@if (loading()) {
|
|
94
|
+
<span
|
|
95
|
+
aria-hidden="true"
|
|
96
|
+
class="rounded-pill animate-cairn-spin absolute inset-0 m-auto size-4 border-2 border-current border-r-transparent"
|
|
97
|
+
></span>
|
|
98
|
+
}
|
|
99
|
+
<span class="contents" [class.text-transparent]="loading()"><ng-content /></span>
|
|
100
|
+
`, isInline: true });
|
|
101
|
+
}
|
|
102
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiButton, decorators: [{
|
|
103
|
+
type: Component,
|
|
104
|
+
args: [{
|
|
105
|
+
selector: 'button[ui-button], a[ui-button]',
|
|
106
|
+
template: `
|
|
107
|
+
@if (loading()) {
|
|
108
|
+
<span
|
|
109
|
+
aria-hidden="true"
|
|
110
|
+
class="rounded-pill animate-cairn-spin absolute inset-0 m-auto size-4 border-2 border-current border-r-transparent"
|
|
111
|
+
></span>
|
|
112
|
+
}
|
|
113
|
+
<span class="contents" [class.text-transparent]="loading()"><ng-content /></span>
|
|
114
|
+
`,
|
|
115
|
+
host: {
|
|
116
|
+
'[class]': 'classes()',
|
|
117
|
+
'[attr.aria-busy]': 'inactive() || null',
|
|
118
|
+
'[attr.aria-disabled]': 'inactive() || null',
|
|
119
|
+
},
|
|
120
|
+
}]
|
|
121
|
+
}], ctorParameters: () => [], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], busy: [{ type: i0.Input, args: [{ isSignal: true, alias: "busy", required: false }] }] } });
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Generated bundle index. Do not edit.
|
|
125
|
+
*/
|
|
126
|
+
|
|
127
|
+
export { BUTTON_SIZES, BUTTON_VARIANTS, UiButton };
|
|
128
|
+
//# sourceMappingURL=joanroucoux-cairn-ui-button.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"joanroucoux-cairn-ui-button.mjs","sources":["../../../projects/ui/button/button.ts","../../../projects/ui/button/joanroucoux-cairn-ui-button.ts"],"sourcesContent":["import { Component, DestroyRef, ElementRef, booleanAttribute, computed, inject, input } from '@angular/core';\n\nimport { holdTransitionsUntilRendered } from '@joanroucoux/cairn-ui/motion';\n\n/** Available button variants. `ButtonVariant` is derived from this tuple. */\nexport const BUTTON_VARIANTS = [\n 'primary',\n 'outline',\n 'ghost',\n 'destructive',\n 'quiet',\n 'quiet-destructive',\n 'outline-destructive',\n 'quiet-glow',\n 'tonal',\n] as const;\nexport type ButtonVariant = (typeof BUTTON_VARIANTS)[number];\n\n/** Available button sizes. */\nexport const BUTTON_SIZES = [\n 'compact',\n 'slim',\n 'xs',\n 'sm',\n 'md',\n 'lg',\n 'xl',\n 'xxl',\n 'tall',\n 'block',\n 'icon',\n 'icon-sm',\n] as const;\nexport type ButtonSize = (typeof BUTTON_SIZES)[number];\n\nconst BASE_CLASSES =\n 'relative inline-flex items-center justify-center rounded-control font-medium whitespace-nowrap cursor-pointer select-none touch-manipulation transition-[scale,background-color] [transition-duration:var(--duration-press),var(--duration-fast)] ease-out active:scale-(--press-scale) focus-visible:outline-2 focus-visible:outline-(--ring) disabled:pointer-events-none disabled:opacity-40 aria-disabled:not-aria-busy:opacity-40';\n\nconst RING_OUTSIDE = 'focus-visible:outline-offset-2';\nconst RING_INSIDE = 'focus-visible:-outline-offset-2';\nconst OUTLINE = 'bg-(--card) shadow-[inset_0_0_0_1px_var(--border)] hover:bg-(--glow) active:bg-(--soft)';\n\nconst VARIANT_CLASSES: Record<ButtonVariant, string> = {\n primary: `${RING_OUTSIDE} bg-(--primary) text-(--primary-foreground) hover:bg-(--primary-to)`,\n outline: `${RING_OUTSIDE} ${OUTLINE} text-(--foreground)`,\n ghost: `${RING_OUTSIDE} bg-transparent text-(--foreground) hover:bg-(--glow) active:bg-(--soft)`,\n destructive: `${RING_OUTSIDE} bg-(--destructive) text-(--destructive-foreground)`,\n tonal: `${RING_OUTSIDE} bg-(--muted) text-(--foreground) lg:bg-transparent lg:hover:bg-(--glow)`,\n 'quiet-glow': `${RING_INSIDE} bg-transparent text-(--muted-foreground) hover:bg-(--glow) hover:text-(--foreground)`,\n quiet: `${RING_INSIDE} bg-transparent text-(--muted-foreground) hover:bg-(--soft) hover:text-(--foreground) active:bg-(--soft)`,\n 'quiet-destructive': `${RING_INSIDE} bg-transparent text-(--muted-foreground) hover:bg-(--glow) hover:text-(--negative) active:bg-(--soft)`,\n 'outline-destructive': `${RING_OUTSIDE} ${OUTLINE} text-(--negative)`,\n};\n\nconst SIZE_CLASSES: Record<ButtonSize, string> = {\n compact:\n 'h-8 lg:h-7 gap-1.5 px-2.5 lg:px-2 text-label before:absolute before:-inset-x-1 before:-inset-y-1.5 lg:before:hidden',\n slim: 'h-11 lg:h-8 gap-1.5 px-2.5 text-label',\n xs: 'h-8 gap-1.5 px-2.5 text-label',\n sm: 'h-9 gap-2 px-3 text-label',\n md: 'min-h-(--row-min) gap-2 px-4 text-label has-[>span>svg:first-child]:pl-3',\n lg: 'h-11 gap-2 px-[18px] text-label',\n xl: 'h-[50px] gap-2 px-6 text-body',\n xxl: 'h-[52px] lg:h-11 gap-2.5 px-6 lg:px-5 text-body lg:has-[>span>svg:first-child]:pl-4',\n tall: 'h-12 lg:h-10 gap-2 px-4 text-body',\n block: 'h-[50px] lg:h-11 gap-2 px-4 text-body',\n icon: 'size-(--row-min) gap-2 p-0',\n 'icon-sm': 'size-11 pointer-fine:size-9 gap-2 p-0',\n};\n\n/**\n * Styled native button or anchor, applied as an attribute so the host keeps every native behavior.\n *\n * @example\n * <button ui-button variant=\"destructive\">Delete</button>\n * <button ui-button size=\"icon\" variant=\"ghost\" aria-label=\"More actions\">...</button>\n */\n@Component({\n selector: 'button[ui-button], a[ui-button]',\n template: `\n @if (loading()) {\n <span\n aria-hidden=\"true\"\n class=\"rounded-pill animate-cairn-spin absolute inset-0 m-auto size-4 border-2 border-current border-r-transparent\"\n ></span>\n }\n <span class=\"contents\" [class.text-transparent]=\"loading()\"><ng-content /></span>\n `,\n host: {\n '[class]': 'classes()',\n '[attr.aria-busy]': 'inactive() || null',\n '[attr.aria-disabled]': 'inactive() || null',\n },\n})\nexport class UiButton {\n readonly variant = input<ButtonVariant>('primary');\n readonly size = input<ButtonSize>('md');\n readonly loading = input(false, { transform: booleanAttribute });\n readonly busy = input(false, { transform: booleanAttribute });\n\n protected readonly inactive = computed(() => this.loading() || this.busy());\n\n protected readonly classes = computed(\n () =>\n `${BASE_CLASSES} ${VARIANT_CLASSES[this.variant()]} ${SIZE_CLASSES[this.size()]}${this.inactive() ? ' pointer-events-none opacity-70' : ''}`,\n );\n\n constructor() {\n holdTransitionsUntilRendered();\n const host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n const guard = (event: Event): void => {\n if (this.inactive()) {\n event.preventDefault();\n event.stopImmediatePropagation();\n }\n };\n\n host.addEventListener('click', guard, { capture: true });\n inject(DestroyRef).onDestroy(() => host.removeEventListener('click', guard, { capture: true }));\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, booleanAttribute, computed, Component } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
/** Available card variants. `CardVariant` is derived from this tuple. */
|
|
5
|
+
const CARD_VARIANTS = ['default', 'elevated', 'inset'];
|
|
6
|
+
/** Available card paddings. `CardPadding` is derived from this tuple. */
|
|
7
|
+
const CARD_PADDINGS = ['none', 'xs', 'sm', 'md', 'list', 'rows', 'recap', 'panel'];
|
|
8
|
+
/** Where the card surface (fill, border, radius) and its padding apply. `CardSurface` is derived from this tuple. */
|
|
9
|
+
const CARD_SURFACES = ['always', 'lg', 'max-lg'];
|
|
10
|
+
/** Border of the card surface; `auto` follows the variant. `CardBorder` is derived from this tuple. */
|
|
11
|
+
const CARD_BORDERS = ['auto', 'none', 'border', 'hairline'];
|
|
12
|
+
const BASE_CLASSES = 'block';
|
|
13
|
+
const VARIANT_CLASSES = {
|
|
14
|
+
default: {
|
|
15
|
+
always: 'rounded-container bg-(--card) text-(--card-foreground)',
|
|
16
|
+
lg: 'lg:rounded-container lg:bg-(--card) lg:text-(--card-foreground)',
|
|
17
|
+
'max-lg': 'max-lg:rounded-container max-lg:bg-(--card) max-lg:text-(--card-foreground)',
|
|
18
|
+
},
|
|
19
|
+
elevated: {
|
|
20
|
+
always: 'rounded-container bg-(--elevated) text-(--foreground)',
|
|
21
|
+
lg: 'lg:rounded-container lg:bg-(--elevated) lg:text-(--foreground)',
|
|
22
|
+
'max-lg': 'max-lg:rounded-container max-lg:bg-(--elevated) max-lg:text-(--foreground)',
|
|
23
|
+
},
|
|
24
|
+
inset: {
|
|
25
|
+
always: 'rounded-control bg-(--background) text-(--foreground)',
|
|
26
|
+
lg: 'lg:rounded-control lg:bg-(--background) lg:text-(--foreground)',
|
|
27
|
+
'max-lg': 'max-lg:rounded-control max-lg:bg-(--background) max-lg:text-(--foreground)',
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
const AUTO_BORDERS = {
|
|
31
|
+
default: 'border',
|
|
32
|
+
elevated: 'hairline',
|
|
33
|
+
inset: 'none',
|
|
34
|
+
};
|
|
35
|
+
const BORDER_CLASSES = {
|
|
36
|
+
none: { always: '', lg: '', 'max-lg': '' },
|
|
37
|
+
border: {
|
|
38
|
+
always: 'shadow-[inset_0_0_0_1px_var(--border)]',
|
|
39
|
+
lg: 'lg:shadow-[inset_0_0_0_1px_var(--border)]',
|
|
40
|
+
'max-lg': 'max-lg:shadow-[inset_0_0_0_1px_var(--border)]',
|
|
41
|
+
},
|
|
42
|
+
hairline: {
|
|
43
|
+
always: 'shadow-[inset_0_0_0_1px_var(--hairline)]',
|
|
44
|
+
lg: 'lg:shadow-[inset_0_0_0_1px_var(--hairline)]',
|
|
45
|
+
'max-lg': 'max-lg:shadow-[inset_0_0_0_1px_var(--hairline)]',
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
const PADDING_CLASSES = {
|
|
49
|
+
none: {
|
|
50
|
+
always: 'p-0',
|
|
51
|
+
lg: 'lg:p-0',
|
|
52
|
+
'max-lg': 'max-lg:p-0',
|
|
53
|
+
},
|
|
54
|
+
xs: {
|
|
55
|
+
always: 'p-1',
|
|
56
|
+
lg: 'lg:p-1',
|
|
57
|
+
'max-lg': 'max-lg:p-1',
|
|
58
|
+
},
|
|
59
|
+
sm: {
|
|
60
|
+
always: 'p-3',
|
|
61
|
+
lg: 'lg:p-3',
|
|
62
|
+
'max-lg': 'max-lg:p-3',
|
|
63
|
+
},
|
|
64
|
+
md: {
|
|
65
|
+
always: 'p-(--inset-card)',
|
|
66
|
+
lg: 'lg:p-(--inset-card)',
|
|
67
|
+
'max-lg': 'max-lg:p-(--inset-card)',
|
|
68
|
+
},
|
|
69
|
+
list: {
|
|
70
|
+
always: 'px-2 py-3',
|
|
71
|
+
lg: 'lg:px-2 lg:py-3',
|
|
72
|
+
'max-lg': 'max-lg:px-2 max-lg:py-3',
|
|
73
|
+
},
|
|
74
|
+
rows: {
|
|
75
|
+
always: 'px-(--inset-card) py-1',
|
|
76
|
+
lg: 'lg:px-(--inset-card) lg:py-1',
|
|
77
|
+
'max-lg': 'max-lg:px-(--inset-card) max-lg:py-1',
|
|
78
|
+
},
|
|
79
|
+
recap: {
|
|
80
|
+
always: 'px-3 py-1',
|
|
81
|
+
lg: 'lg:px-3 lg:py-1',
|
|
82
|
+
'max-lg': 'max-lg:px-3 max-lg:py-1',
|
|
83
|
+
},
|
|
84
|
+
panel: {
|
|
85
|
+
always: 'px-3 py-2.5 lg:px-4 lg:py-3',
|
|
86
|
+
lg: 'lg:px-4 lg:py-3',
|
|
87
|
+
'max-lg': 'max-lg:px-3 max-lg:py-2.5',
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Surface that groups related content: a summary block, a table, a side panel. `surface` limits the
|
|
92
|
+
* fill, border, radius and padding to one side of `64rem`, for a block that is a card on a desktop and
|
|
93
|
+
* plain on an iPhone, or the reverse. `variant="inset"` is the `--background` box that sits inside a
|
|
94
|
+
* card or a dialog: a picked title, a results list, a recap, an empty-state panel.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* <ui-card variant="elevated" padding="sm">Total</ui-card>
|
|
98
|
+
* <ui-card variant="inset" border="hairline" padding="xs">Results</ui-card>
|
|
99
|
+
* <ui-card padding="none" clip>Table that touches the edges, clipped to the corners</ui-card>
|
|
100
|
+
* <ui-card surface="max-lg">Card on an iPhone, plain on a desktop</ui-card>
|
|
101
|
+
*/
|
|
102
|
+
class UiCard {
|
|
103
|
+
variant = input('default', /* @ts-ignore */
|
|
104
|
+
...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
105
|
+
padding = input('md', /* @ts-ignore */
|
|
106
|
+
...(ngDevMode ? [{ debugName: "padding" }] : /* istanbul ignore next */ []));
|
|
107
|
+
surface = input('always', /* @ts-ignore */
|
|
108
|
+
...(ngDevMode ? [{ debugName: "surface" }] : /* istanbul ignore next */ []));
|
|
109
|
+
border = input('auto', /* @ts-ignore */
|
|
110
|
+
...(ngDevMode ? [{ debugName: "border" }] : /* istanbul ignore next */ []));
|
|
111
|
+
clip = input(false, { ...(ngDevMode ? { debugName: "clip" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
112
|
+
classes = computed(() => {
|
|
113
|
+
const variant = this.variant();
|
|
114
|
+
const surface = this.surface();
|
|
115
|
+
const border = this.border();
|
|
116
|
+
return [
|
|
117
|
+
BASE_CLASSES,
|
|
118
|
+
VARIANT_CLASSES[variant][surface],
|
|
119
|
+
BORDER_CLASSES[border === 'auto' ? AUTO_BORDERS[variant] : border][surface],
|
|
120
|
+
PADDING_CLASSES[this.padding()][surface],
|
|
121
|
+
this.clip() ? 'overflow-hidden' : '',
|
|
122
|
+
]
|
|
123
|
+
.filter(Boolean)
|
|
124
|
+
.join(' ');
|
|
125
|
+
}, /* @ts-ignore */
|
|
126
|
+
...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
127
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiCard, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
128
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.7", type: UiCard, isStandalone: true, selector: "ui-card", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null }, surface: { classPropertyName: "surface", publicName: "surface", isSignal: true, isRequired: false, transformFunction: null }, border: { classPropertyName: "border", publicName: "border", isSignal: true, isRequired: false, transformFunction: null }, clip: { classPropertyName: "clip", publicName: "clip", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()" } }, ngImport: i0, template: '<ng-content />', isInline: true });
|
|
129
|
+
}
|
|
130
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiCard, decorators: [{
|
|
131
|
+
type: Component,
|
|
132
|
+
args: [{
|
|
133
|
+
selector: 'ui-card',
|
|
134
|
+
template: '<ng-content />',
|
|
135
|
+
host: {
|
|
136
|
+
'[class]': 'classes()',
|
|
137
|
+
},
|
|
138
|
+
}]
|
|
139
|
+
}], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], padding: [{ type: i0.Input, args: [{ isSignal: true, alias: "padding", required: false }] }], surface: [{ type: i0.Input, args: [{ isSignal: true, alias: "surface", required: false }] }], border: [{ type: i0.Input, args: [{ isSignal: true, alias: "border", required: false }] }], clip: [{ type: i0.Input, args: [{ isSignal: true, alias: "clip", required: false }] }] } });
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Generated bundle index. Do not edit.
|
|
143
|
+
*/
|
|
144
|
+
|
|
145
|
+
export { CARD_PADDINGS, CARD_SURFACES, CARD_VARIANTS, UiCard };
|
|
146
|
+
//# sourceMappingURL=joanroucoux-cairn-ui-card.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"joanroucoux-cairn-ui-card.mjs","sources":["../../../projects/ui/card/card.ts","../../../projects/ui/card/joanroucoux-cairn-ui-card.ts"],"sourcesContent":["import { Component, booleanAttribute, computed, input } from '@angular/core';\n\n/** Available card variants. `CardVariant` is derived from this tuple. */\nexport const CARD_VARIANTS = ['default', 'elevated', 'inset'] as const;\nexport type CardVariant = (typeof CARD_VARIANTS)[number];\n\n/** Available card paddings. `CardPadding` is derived from this tuple. */\nexport const CARD_PADDINGS = ['none', 'xs', 'sm', 'md', 'list', 'rows', 'recap', 'panel'] as const;\nexport type CardPadding = (typeof CARD_PADDINGS)[number];\n\n/** Where the card surface (fill, border, radius) and its padding apply. `CardSurface` is derived from this tuple. */\nexport const CARD_SURFACES = ['always', 'lg', 'max-lg'] as const;\nexport type CardSurface = (typeof CARD_SURFACES)[number];\n\n/** Border of the card surface; `auto` follows the variant. `CardBorder` is derived from this tuple. */\nexport const CARD_BORDERS = ['auto', 'none', 'border', 'hairline'] as const;\nexport type CardBorder = (typeof CARD_BORDERS)[number];\n\nconst BASE_CLASSES = 'block';\n\nconst VARIANT_CLASSES: Record<CardVariant, Record<CardSurface, string>> = {\n default: {\n always: 'rounded-container bg-(--card) text-(--card-foreground)',\n lg: 'lg:rounded-container lg:bg-(--card) lg:text-(--card-foreground)',\n 'max-lg': 'max-lg:rounded-container max-lg:bg-(--card) max-lg:text-(--card-foreground)',\n },\n elevated: {\n always: 'rounded-container bg-(--elevated) text-(--foreground)',\n lg: 'lg:rounded-container lg:bg-(--elevated) lg:text-(--foreground)',\n 'max-lg': 'max-lg:rounded-container max-lg:bg-(--elevated) max-lg:text-(--foreground)',\n },\n inset: {\n always: 'rounded-control bg-(--background) text-(--foreground)',\n lg: 'lg:rounded-control lg:bg-(--background) lg:text-(--foreground)',\n 'max-lg': 'max-lg:rounded-control max-lg:bg-(--background) max-lg:text-(--foreground)',\n },\n};\n\nconst AUTO_BORDERS: Record<CardVariant, Exclude<CardBorder, 'auto'>> = {\n default: 'border',\n elevated: 'hairline',\n inset: 'none',\n};\n\nconst BORDER_CLASSES: Record<Exclude<CardBorder, 'auto'>, Record<CardSurface, string>> = {\n none: { always: '', lg: '', 'max-lg': '' },\n border: {\n always: 'shadow-[inset_0_0_0_1px_var(--border)]',\n lg: 'lg:shadow-[inset_0_0_0_1px_var(--border)]',\n 'max-lg': 'max-lg:shadow-[inset_0_0_0_1px_var(--border)]',\n },\n hairline: {\n always: 'shadow-[inset_0_0_0_1px_var(--hairline)]',\n lg: 'lg:shadow-[inset_0_0_0_1px_var(--hairline)]',\n 'max-lg': 'max-lg:shadow-[inset_0_0_0_1px_var(--hairline)]',\n },\n};\n\nconst PADDING_CLASSES: Record<CardPadding, Record<CardSurface, string>> = {\n none: {\n always: 'p-0',\n lg: 'lg:p-0',\n 'max-lg': 'max-lg:p-0',\n },\n xs: {\n always: 'p-1',\n lg: 'lg:p-1',\n 'max-lg': 'max-lg:p-1',\n },\n sm: {\n always: 'p-3',\n lg: 'lg:p-3',\n 'max-lg': 'max-lg:p-3',\n },\n md: {\n always: 'p-(--inset-card)',\n lg: 'lg:p-(--inset-card)',\n 'max-lg': 'max-lg:p-(--inset-card)',\n },\n list: {\n always: 'px-2 py-3',\n lg: 'lg:px-2 lg:py-3',\n 'max-lg': 'max-lg:px-2 max-lg:py-3',\n },\n rows: {\n always: 'px-(--inset-card) py-1',\n lg: 'lg:px-(--inset-card) lg:py-1',\n 'max-lg': 'max-lg:px-(--inset-card) max-lg:py-1',\n },\n recap: {\n always: 'px-3 py-1',\n lg: 'lg:px-3 lg:py-1',\n 'max-lg': 'max-lg:px-3 max-lg:py-1',\n },\n panel: {\n always: 'px-3 py-2.5 lg:px-4 lg:py-3',\n lg: 'lg:px-4 lg:py-3',\n 'max-lg': 'max-lg:px-3 max-lg:py-2.5',\n },\n};\n\n/**\n * Surface that groups related content: a summary block, a table, a side panel. `surface` limits the\n * fill, border, radius and padding to one side of `64rem`, for a block that is a card on a desktop and\n * plain on an iPhone, or the reverse. `variant=\"inset\"` is the `--background` box that sits inside a\n * card or a dialog: a picked title, a results list, a recap, an empty-state panel.\n *\n * @example\n * <ui-card variant=\"elevated\" padding=\"sm\">Total</ui-card>\n * <ui-card variant=\"inset\" border=\"hairline\" padding=\"xs\">Results</ui-card>\n * <ui-card padding=\"none\" clip>Table that touches the edges, clipped to the corners</ui-card>\n * <ui-card surface=\"max-lg\">Card on an iPhone, plain on a desktop</ui-card>\n */\n@Component({\n selector: 'ui-card',\n template: '<ng-content />',\n host: {\n '[class]': 'classes()',\n },\n})\nexport class UiCard {\n readonly variant = input<CardVariant>('default');\n readonly padding = input<CardPadding>('md');\n readonly surface = input<CardSurface>('always');\n readonly border = input<CardBorder>('auto');\n readonly clip = input(false, { transform: booleanAttribute });\n\n protected readonly classes = computed(() => {\n const variant = this.variant();\n const surface = this.surface();\n const border = this.border();\n\n return [\n BASE_CLASSES,\n VARIANT_CLASSES[variant][surface],\n BORDER_CLASSES[border === 'auto' ? AUTO_BORDERS[variant] : border][surface],\n PADDING_CLASSES[this.padding()][surface],\n this.clip() ? 'overflow-hidden' : '',\n ]\n .filter(Boolean)\n .join(' ');\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, model, booleanAttribute, output, inject, ElementRef, viewChildren, computed, forwardRef, Component } from '@angular/core';
|
|
3
|
+
import { UI_CONTROL } from '@joanroucoux/cairn-ui/control';
|
|
4
|
+
import { CONTROL_SURFACE_CLASSES } from '@joanroucoux/cairn-ui/input';
|
|
5
|
+
|
|
6
|
+
const CHIP_CLASSES = 'h-10 px-3.5 pointer-fine:h-8 pointer-fine:px-3 rounded-pill text-label font-medium cursor-pointer transition-transform duration-(--duration-press) ease-out active:scale-(--press-scale) disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring)';
|
|
7
|
+
const SELECTED_CLASSES = 'bg-(--primary) text-(--primary-foreground)';
|
|
8
|
+
const UNSELECTED_CLASSES = 'text-(--foreground) shadow-[inset_0_0_0_1px_var(--border)]';
|
|
9
|
+
const nextId = (() => {
|
|
10
|
+
let count = 0;
|
|
11
|
+
return () => `ui-choice-chips-${++count}`;
|
|
12
|
+
})();
|
|
13
|
+
/**
|
|
14
|
+
* Exclusive choice among a handful of named options, shown as wrapping pills: an account envelope,
|
|
15
|
+
* a category, a frequency. Binds to Signal Forms with `[formField]`.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* <ui-choice-chips label="Envelope" [options]="envelopes" [(value)]="envelope" />
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* <ui-choice-chips label="Envelope" [options]="envelopes" [formField]="form.envelope" />
|
|
22
|
+
*/
|
|
23
|
+
class UiChoiceChips {
|
|
24
|
+
options = input.required(/* @ts-ignore */
|
|
25
|
+
...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
26
|
+
value = model.required(/* @ts-ignore */
|
|
27
|
+
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
28
|
+
label = input(/* @ts-ignore */
|
|
29
|
+
...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
|
|
30
|
+
ariaLabel = input(/* @ts-ignore */
|
|
31
|
+
...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
32
|
+
ariaLabelledby = input(/* @ts-ignore */
|
|
33
|
+
...(ngDevMode ? [undefined, { debugName: "ariaLabelledby" }] : /* istanbul ignore next */ []));
|
|
34
|
+
surface = input('background', /* @ts-ignore */
|
|
35
|
+
...(ngDevMode ? [{ debugName: "surface" }] : /* istanbul ignore next */ []));
|
|
36
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
37
|
+
errors = input([], /* @ts-ignore */
|
|
38
|
+
...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
|
|
39
|
+
invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
40
|
+
required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
41
|
+
touched = input(false, /* @ts-ignore */
|
|
42
|
+
...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
|
|
43
|
+
touch = output();
|
|
44
|
+
labelId = nextId();
|
|
45
|
+
#host = inject(ElementRef);
|
|
46
|
+
radios = viewChildren('radio', /* @ts-ignore */
|
|
47
|
+
...(ngDevMode ? [{ debugName: "radios" }] : /* istanbul ignore next */ []));
|
|
48
|
+
labelledBy = computed(() => this.ariaLabelledby() ?? (this.label() ? this.labelId : null), /* @ts-ignore */
|
|
49
|
+
...(ngDevMode ? [{ debugName: "labelledBy" }] : /* istanbul ignore next */ []));
|
|
50
|
+
chipClasses(optionValue) {
|
|
51
|
+
const state = optionValue === this.value()
|
|
52
|
+
? SELECTED_CLASSES
|
|
53
|
+
: `${CONTROL_SURFACE_CLASSES[this.surface()]} ${UNSELECTED_CLASSES}`;
|
|
54
|
+
return `${CHIP_CLASSES} ${state}`;
|
|
55
|
+
}
|
|
56
|
+
tabIndexFor(optionValue) {
|
|
57
|
+
const opts = this.options();
|
|
58
|
+
const tabStopValue = opts.some((option) => option.value === this.value()) ? this.value() : opts[0]?.value;
|
|
59
|
+
return optionValue === tabStopValue ? 0 : -1;
|
|
60
|
+
}
|
|
61
|
+
select(optionValue) {
|
|
62
|
+
this.value.set(optionValue);
|
|
63
|
+
}
|
|
64
|
+
onFocusOut(event) {
|
|
65
|
+
if (!this.#host.nativeElement.contains(event.relatedTarget)) {
|
|
66
|
+
this.touch.emit();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
onKeydown(event, index) {
|
|
70
|
+
const opts = this.options();
|
|
71
|
+
const target = {
|
|
72
|
+
ArrowRight: index + 1,
|
|
73
|
+
ArrowDown: index + 1,
|
|
74
|
+
ArrowLeft: index - 1,
|
|
75
|
+
ArrowUp: index - 1,
|
|
76
|
+
Home: 0,
|
|
77
|
+
End: opts.length - 1,
|
|
78
|
+
}[event.key];
|
|
79
|
+
if (target === undefined) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
const nextIndex = ((target % opts.length) + opts.length) % opts.length;
|
|
84
|
+
this.value.set(opts[nextIndex].value);
|
|
85
|
+
this.radios()[nextIndex].nativeElement.focus();
|
|
86
|
+
}
|
|
87
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiChoiceChips, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
88
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiChoiceChips, isStandalone: true, selector: "ui-choice-chips", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "ariaLabelledby", isSignal: true, isRequired: false, transformFunction: null }, surface: { classPropertyName: "surface", publicName: "surface", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touch: "touch" }, providers: [{ provide: UI_CONTROL, useExisting: forwardRef(() => UiChoiceChips) }], viewQueries: [{ propertyName: "radios", predicate: ["radio"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
89
|
+
<div
|
|
90
|
+
class="flex flex-col gap-1.5"
|
|
91
|
+
role="radiogroup"
|
|
92
|
+
[attr.aria-invalid]="invalid() || null"
|
|
93
|
+
[attr.aria-label]="ariaLabel() ?? null"
|
|
94
|
+
[attr.aria-labelledby]="labelledBy()"
|
|
95
|
+
[attr.aria-required]="required() || null"
|
|
96
|
+
(focusout)="onFocusOut($event)"
|
|
97
|
+
>
|
|
98
|
+
@if (label()) {
|
|
99
|
+
<span class="text-label leading-[17px] font-medium text-(--muted-foreground)" [id]="labelId">{{
|
|
100
|
+
label()
|
|
101
|
+
}}</span>
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
<div class="flex flex-wrap gap-1.5">
|
|
105
|
+
@for (option of options(); track option.value; let index = $index) {
|
|
106
|
+
<button
|
|
107
|
+
#radio
|
|
108
|
+
role="radio"
|
|
109
|
+
type="button"
|
|
110
|
+
[attr.aria-checked]="option.value === value()"
|
|
111
|
+
[attr.tabindex]="tabIndexFor(option.value)"
|
|
112
|
+
[class]="chipClasses(option.value)"
|
|
113
|
+
[disabled]="disabled()"
|
|
114
|
+
(click)="select(option.value)"
|
|
115
|
+
(keydown)="onKeydown($event, index)"
|
|
116
|
+
>
|
|
117
|
+
{{ option.label }}
|
|
118
|
+
</button>
|
|
119
|
+
}
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
`, isInline: true });
|
|
123
|
+
}
|
|
124
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiChoiceChips, decorators: [{
|
|
125
|
+
type: Component,
|
|
126
|
+
args: [{
|
|
127
|
+
selector: 'ui-choice-chips',
|
|
128
|
+
template: `
|
|
129
|
+
<div
|
|
130
|
+
class="flex flex-col gap-1.5"
|
|
131
|
+
role="radiogroup"
|
|
132
|
+
[attr.aria-invalid]="invalid() || null"
|
|
133
|
+
[attr.aria-label]="ariaLabel() ?? null"
|
|
134
|
+
[attr.aria-labelledby]="labelledBy()"
|
|
135
|
+
[attr.aria-required]="required() || null"
|
|
136
|
+
(focusout)="onFocusOut($event)"
|
|
137
|
+
>
|
|
138
|
+
@if (label()) {
|
|
139
|
+
<span class="text-label leading-[17px] font-medium text-(--muted-foreground)" [id]="labelId">{{
|
|
140
|
+
label()
|
|
141
|
+
}}</span>
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
<div class="flex flex-wrap gap-1.5">
|
|
145
|
+
@for (option of options(); track option.value; let index = $index) {
|
|
146
|
+
<button
|
|
147
|
+
#radio
|
|
148
|
+
role="radio"
|
|
149
|
+
type="button"
|
|
150
|
+
[attr.aria-checked]="option.value === value()"
|
|
151
|
+
[attr.tabindex]="tabIndexFor(option.value)"
|
|
152
|
+
[class]="chipClasses(option.value)"
|
|
153
|
+
[disabled]="disabled()"
|
|
154
|
+
(click)="select(option.value)"
|
|
155
|
+
(keydown)="onKeydown($event, index)"
|
|
156
|
+
>
|
|
157
|
+
{{ option.label }}
|
|
158
|
+
</button>
|
|
159
|
+
}
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
`,
|
|
163
|
+
providers: [{ provide: UI_CONTROL, useExisting: forwardRef(() => UiChoiceChips) }],
|
|
164
|
+
}]
|
|
165
|
+
}], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }, { type: i0.Output, args: ["valueChange"] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelledby", required: false }] }], surface: [{ type: i0.Input, args: [{ isSignal: true, alias: "surface", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }], touch: [{ type: i0.Output, args: ["touch"] }], radios: [{ type: i0.ViewChildren, args: ['radio', { isSignal: true }] }] } });
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Generated bundle index. Do not edit.
|
|
169
|
+
*/
|
|
170
|
+
|
|
171
|
+
export { UiChoiceChips };
|
|
172
|
+
//# sourceMappingURL=joanroucoux-cairn-ui-choice-chips.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"joanroucoux-cairn-ui-choice-chips.mjs","sources":["../../../projects/ui/choice-chips/choice-chips.ts","../../../projects/ui/choice-chips/joanroucoux-cairn-ui-choice-chips.ts"],"sourcesContent":["import {\n Component,\n ElementRef,\n booleanAttribute,\n computed,\n forwardRef,\n inject,\n input,\n model,\n output,\n viewChildren,\n} from '@angular/core';\n\nimport { UI_CONTROL, type UiControl, type UiControlError } from '@joanroucoux/cairn-ui/control';\nimport { CONTROL_SURFACE_CLASSES, type ControlSurface } from '@joanroucoux/cairn-ui/input';\n\nexport type ChoiceChipOption = {\n value: string;\n label: string;\n};\n\nconst CHIP_CLASSES =\n 'h-10 px-3.5 pointer-fine:h-8 pointer-fine:px-3 rounded-pill text-label font-medium cursor-pointer transition-transform duration-(--duration-press) ease-out active:scale-(--press-scale) disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring)';\n\nconst SELECTED_CLASSES = 'bg-(--primary) text-(--primary-foreground)';\n\nconst UNSELECTED_CLASSES = 'text-(--foreground) shadow-[inset_0_0_0_1px_var(--border)]';\n\nconst nextId = (() => {\n let count = 0;\n\n return (): string => `ui-choice-chips-${++count}`;\n})();\n\n/**\n * Exclusive choice among a handful of named options, shown as wrapping pills: an account envelope,\n * a category, a frequency. Binds to Signal Forms with `[formField]`.\n *\n * @example\n * <ui-choice-chips label=\"Envelope\" [options]=\"envelopes\" [(value)]=\"envelope\" />\n *\n * @example\n * <ui-choice-chips label=\"Envelope\" [options]=\"envelopes\" [formField]=\"form.envelope\" />\n */\n@Component({\n selector: 'ui-choice-chips',\n template: `\n <div\n class=\"flex flex-col gap-1.5\"\n role=\"radiogroup\"\n [attr.aria-invalid]=\"invalid() || null\"\n [attr.aria-label]=\"ariaLabel() ?? null\"\n [attr.aria-labelledby]=\"labelledBy()\"\n [attr.aria-required]=\"required() || null\"\n (focusout)=\"onFocusOut($event)\"\n >\n @if (label()) {\n <span class=\"text-label leading-[17px] font-medium text-(--muted-foreground)\" [id]=\"labelId\">{{\n label()\n }}</span>\n }\n\n <div class=\"flex flex-wrap gap-1.5\">\n @for (option of options(); track option.value; let index = $index) {\n <button\n #radio\n role=\"radio\"\n type=\"button\"\n [attr.aria-checked]=\"option.value === value()\"\n [attr.tabindex]=\"tabIndexFor(option.value)\"\n [class]=\"chipClasses(option.value)\"\n [disabled]=\"disabled()\"\n (click)=\"select(option.value)\"\n (keydown)=\"onKeydown($event, index)\"\n >\n {{ option.label }}\n </button>\n }\n </div>\n </div>\n `,\n providers: [{ provide: UI_CONTROL, useExisting: forwardRef(() => UiChoiceChips) }],\n})\nexport class UiChoiceChips implements UiControl {\n readonly options = input.required<ChoiceChipOption[]>();\n readonly value = model.required<string>();\n readonly label = input<string>();\n readonly ariaLabel = input<string>();\n readonly ariaLabelledby = input<string>();\n readonly surface = input<ControlSurface>('background');\n readonly disabled = input(false, { transform: booleanAttribute });\n readonly errors = input<readonly UiControlError[]>([]);\n readonly invalid = input(false, { transform: booleanAttribute });\n readonly required = input(false, { transform: booleanAttribute });\n readonly touched = input(false);\n readonly touch = output<void>();\n\n protected readonly labelId = nextId();\n readonly #host = inject<ElementRef<HTMLElement>>(ElementRef);\n protected readonly radios = viewChildren<ElementRef<HTMLButtonElement>>('radio');\n\n protected readonly labelledBy = computed(() => this.ariaLabelledby() ?? (this.label() ? this.labelId : null));\n\n protected chipClasses(optionValue: string): string {\n const state =\n optionValue === this.value()\n ? SELECTED_CLASSES\n : `${CONTROL_SURFACE_CLASSES[this.surface()]} ${UNSELECTED_CLASSES}`;\n\n return `${CHIP_CLASSES} ${state}`;\n }\n\n protected tabIndexFor(optionValue: string): number {\n const opts = this.options();\n const tabStopValue = opts.some((option) => option.value === this.value()) ? this.value() : opts[0]?.value;\n\n return optionValue === tabStopValue ? 0 : -1;\n }\n\n protected select(optionValue: string): void {\n this.value.set(optionValue);\n }\n\n protected onFocusOut(event: FocusEvent): void {\n if (!this.#host.nativeElement.contains(event.relatedTarget as Node | null)) {\n this.touch.emit();\n }\n }\n\n protected onKeydown(event: KeyboardEvent, index: number): void {\n const opts = this.options();\n const target = {\n ArrowRight: index + 1,\n ArrowDown: index + 1,\n ArrowLeft: index - 1,\n ArrowUp: index - 1,\n Home: 0,\n End: opts.length - 1,\n }[event.key];\n\n if (target === undefined) {\n return;\n }\n event.preventDefault();\n\n const nextIndex = ((target % opts.length) + opts.length) % opts.length;\n\n this.value.set(opts[nextIndex]!.value);\n this.radios()[nextIndex]!.nativeElement.focus();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { InjectionToken } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolves to the styled control projected into a field.
|
|
5
|
+
*
|
|
6
|
+
* Angular's `[formField]` directive fills any input named `errors` or `touched` on the directives
|
|
7
|
+
* sitting on its host element, so a control declaring them receives its own validation state with
|
|
8
|
+
* nothing written at the call site. This token is how the field finds whichever control was
|
|
9
|
+
* projected into it.
|
|
10
|
+
*/
|
|
11
|
+
const UI_CONTROL = new InjectionToken('UI_CONTROL');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generated bundle index. Do not edit.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export { UI_CONTROL };
|
|
18
|
+
//# sourceMappingURL=joanroucoux-cairn-ui-control.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"joanroucoux-cairn-ui-control.mjs","sources":["../../../projects/ui/control/control.ts","../../../projects/ui/control/joanroucoux-cairn-ui-control.ts"],"sourcesContent":["import { InjectionToken, type Signal } from '@angular/core';\n\n/**\n * The part of a validation error this library reads. Angular's own `ValidationError` satisfies it,\n * which is how a Signal Forms error reaches a field without this package depending on\n * `@angular/forms`.\n */\nexport type UiControlError = { readonly message?: string };\n\n/** What a styled control offers the field wrapped around it. */\nexport type UiControl = {\n readonly errors: Signal<readonly UiControlError[]>;\n readonly touched: Signal<boolean>;\n};\n\n/**\n * Resolves to the styled control projected into a field.\n *\n * Angular's `[formField]` directive fills any input named `errors` or `touched` on the directives\n * sitting on its host element, so a control declaring them receives its own validation state with\n * nothing written at the call site. This token is how the field finds whichever control was\n * projected into it.\n */\nexport const UI_CONTROL = new InjectionToken<UiControl>('UI_CONTROL');\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;AAeA;;;;;;;AAOG;MACU,UAAU,GAAG,IAAI,cAAc,CAAY,YAAY;;ACvBpE;;AAEG;;;;"}
|