@kouji-ui/core 0.0.4 → 0.1.2
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/fesm2022/kouji-ui-core.mjs +26789 -0
- package/fesm2022/kouji-ui-core.mjs.map +1 -0
- package/icon/icon.css +40 -0
- package/package.json +20 -9
- package/types/kouji-ui-core.d.ts +13545 -0
- package/typography/prose.css +312 -0
- package/.turbo/turbo-build.log +0 -27
- package/CHANGELOG.md +0 -21
- package/ng-package.json +0 -7
- package/src/a11y/.gitkeep +0 -0
- package/src/a11y/aria-describedby.spec.ts +0 -55
- package/src/a11y/aria-describedby.ts +0 -33
- package/src/a11y/focus-trap.spec.ts +0 -86
- package/src/a11y/focus-trap.ts +0 -70
- package/src/a11y/index.ts +0 -9
- package/src/a11y/live-region.spec.ts +0 -58
- package/src/a11y/live-region.ts +0 -48
- package/src/a11y/roving-tabindex.spec.ts +0 -62
- package/src/a11y/roving-tabindex.ts +0 -104
- package/src/a11y/visually-hidden.spec.ts +0 -34
- package/src/a11y/visually-hidden.ts +0 -24
- package/src/accordion/accordion.context.ts +0 -22
- package/src/accordion/accordion.spec.ts +0 -32
- package/src/accordion/accordion.ts +0 -129
- package/src/accordion/index.ts +0 -2
- package/src/avatar/avatar.spec.ts +0 -18
- package/src/avatar/avatar.ts +0 -26
- package/src/avatar/index.ts +0 -1
- package/src/badge/badge.spec.ts +0 -20
- package/src/badge/badge.ts +0 -14
- package/src/badge/index.ts +0 -1
- package/src/button/button.example.ts +0 -30
- package/src/button/button.finance.example.ts +0 -37
- package/src/button/button.retro.example.ts +0 -47
- package/src/button/button.sizes.example.ts +0 -25
- package/src/button/button.spec.ts +0 -32
- package/src/button/button.ts +0 -44
- package/src/button/index.ts +0 -1
- package/src/chart/chart.spec.ts +0 -27
- package/src/chart/chart.ts +0 -48
- package/src/chart/index.ts +0 -1
- package/src/checkbox/checkbox.spec.ts +0 -35
- package/src/checkbox/checkbox.ts +0 -82
- package/src/checkbox/index.ts +0 -1
- package/src/dialog/dialog.confirm.example.ts +0 -57
- package/src/dialog/dialog.context.ts +0 -11
- package/src/dialog/dialog.example.ts +0 -40
- package/src/dialog/dialog.finance.example.ts +0 -47
- package/src/dialog/dialog.retro.example.ts +0 -46
- package/src/dialog/dialog.service.ts +0 -176
- package/src/dialog/dialog.spec.ts +0 -122
- package/src/dialog/dialog.ts +0 -201
- package/src/dialog/index.ts +0 -9
- package/src/example-components.ts +0 -33
- package/src/form/form.context.ts +0 -3
- package/src/form/form.spec.ts +0 -25
- package/src/form/form.ts +0 -46
- package/src/form/index.ts +0 -2
- package/src/input/index.ts +0 -1
- package/src/input/input.spec.ts +0 -43
- package/src/input/input.ts +0 -80
- package/src/menu/index.ts +0 -2
- package/src/menu/menu.context.ts +0 -10
- package/src/menu/menu.example.ts +0 -39
- package/src/menu/menu.spec.ts +0 -168
- package/src/menu/menu.ts +0 -176
- package/src/popover/index.ts +0 -12
- package/src/popover/popover.context.ts +0 -18
- package/src/popover/popover.example.ts +0 -50
- package/src/popover/popover.finance.example.ts +0 -52
- package/src/popover/popover.retro.example.ts +0 -53
- package/src/popover/popover.spec.ts +0 -52
- package/src/popover/popover.ts +0 -124
- package/src/primitives/.gitkeep +0 -0
- package/src/primitives/forms/form-control.spec.ts +0 -59
- package/src/primitives/forms/form-control.ts +0 -90
- package/src/primitives/forms/index.ts +0 -1
- package/src/primitives/index.ts +0 -3
- package/src/primitives/interaction/disabled.spec.ts +0 -47
- package/src/primitives/interaction/disabled.ts +0 -24
- package/src/primitives/interaction/focus-ring.spec.ts +0 -62
- package/src/primitives/interaction/focus-ring.ts +0 -57
- package/src/primitives/interaction/index.ts +0 -2
- package/src/primitives/overlay/index.ts +0 -1
- package/src/primitives/overlay/overlay.spec.ts +0 -90
- package/src/primitives/overlay/overlay.ts +0 -106
- package/src/public-api.ts +0 -35
- package/src/radio/index.ts +0 -2
- package/src/radio/radio.context.ts +0 -3
- package/src/radio/radio.spec.ts +0 -36
- package/src/radio/radio.ts +0 -55
- package/src/select/index.ts +0 -2
- package/src/select/select.context.ts +0 -3
- package/src/select/select.spec.ts +0 -145
- package/src/select/select.ts +0 -250
- package/src/styles/docs-themes.css +0 -82
- package/src/table/index.ts +0 -1
- package/src/table/table.spec.ts +0 -113
- package/src/table/table.ts +0 -126
- package/src/tabs/index.ts +0 -2
- package/src/tabs/tabs.context.ts +0 -9
- package/src/tabs/tabs.spec.ts +0 -40
- package/src/tabs/tabs.ts +0 -130
- package/src/test-setup.ts +0 -5
- package/src/toast/index.ts +0 -21
- package/src/toast/toast.example.ts +0 -134
- package/src/toast/toast.finance.example.ts +0 -68
- package/src/toast/toast.retro.example.ts +0 -69
- package/src/toast/toast.service.ts +0 -153
- package/src/toast/toast.spec.ts +0 -95
- package/src/toast/toast.strategy.ts +0 -102
- package/src/toast/toast.ts +0 -307
- package/src/toast/toast.types.ts +0 -5
- package/src/toggle/index.ts +0 -1
- package/src/toggle/toggle.spec.ts +0 -24
- package/src/toggle/toggle.ts +0 -40
- package/src/tooltip/index.ts +0 -5
- package/src/tooltip/tooltip.example.ts +0 -46
- package/src/tooltip/tooltip.finance.example.ts +0 -54
- package/src/tooltip/tooltip.placements.example.ts +0 -56
- package/src/tooltip/tooltip.retro.example.ts +0 -65
- package/src/tooltip/tooltip.spec.ts +0 -104
- package/src/tooltip/tooltip.ts +0 -129
- package/tsconfig.lib.json +0 -13
- package/tsconfig.lib.prod.json +0 -11
- package/tsconfig.spec.json +0 -10
- package/vite.config.ts +0 -19
package/src/toast/toast.ts
DELETED
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DestroyRef, Directive, ElementRef, TemplateRef,
|
|
3
|
-
afterNextRender, computed, inject, input, signal,
|
|
4
|
-
} from '@angular/core';
|
|
5
|
-
import { KjToastService, KjToastTemplateContext, KjToastVariant } from './toast.service';
|
|
6
|
-
import { KJ_TOAST_STRATEGY } from './toast.strategy';
|
|
7
|
-
import { KjToastPositionX, KjToastPositionY } from './toast.types';
|
|
8
|
-
|
|
9
|
-
export type { KjToastVariant, KjToastContext, KjToastOptions } from './toast.service';
|
|
10
|
-
export type { KjToastPositionX, KjToastPositionY } from './toast.types';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Marks an element as a single toast item. Sets `role`, `aria-atomic`, `data-variant`,
|
|
14
|
-
* `data-front`, and exposes positional state as CSS custom properties so client CSS
|
|
15
|
-
* can drive stacking, scaling, or spread-out animations.
|
|
16
|
-
*
|
|
17
|
-
* **CSS variables exposed (when `[kjToastId]` is bound):**
|
|
18
|
-
* - `--kj-toast-index` — 1-based position from the front (newest = 1)
|
|
19
|
-
* - `--kj-toast-before` — number of toasts behind this one
|
|
20
|
-
* - `--kj-toast-after` — number of toasts in front of this one
|
|
21
|
-
* - `--kj-toast-height` — measured pixel height (kept in sync via ResizeObserver)
|
|
22
|
-
*
|
|
23
|
-
* @doc
|
|
24
|
-
* @doc-example Basic
|
|
25
|
-
* @doc-theme default
|
|
26
|
-
* @doc-file toast.example.ts
|
|
27
|
-
* @doc-theme retro
|
|
28
|
-
* @doc-file toast.retro.example.ts
|
|
29
|
-
* @doc-theme finance
|
|
30
|
-
* @doc-file toast.finance.example.ts
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
* ```html
|
|
34
|
-
* <ng-template #defaultTpl let-ctx>
|
|
35
|
-
* <div kjToast [kjToastVariant]="ctx.variant" [kjToastId]="ctx.id">
|
|
36
|
-
* <span>{{ ctx.message }}</span>
|
|
37
|
-
* <button (click)="ctx.dismiss()" aria-label="Dismiss">×</button>
|
|
38
|
-
* </div>
|
|
39
|
-
* </ng-template>
|
|
40
|
-
* ```
|
|
41
|
-
* @category Core/Overlays
|
|
42
|
-
*/
|
|
43
|
-
@Directive({
|
|
44
|
-
selector: '[kjToast]',
|
|
45
|
-
standalone: true,
|
|
46
|
-
host: {
|
|
47
|
-
'[attr.role]': 'role()',
|
|
48
|
-
'[attr.aria-atomic]': '"true"',
|
|
49
|
-
'[attr.data-variant]': 'kjToastVariant()',
|
|
50
|
-
'[attr.data-front]': 'isFront()',
|
|
51
|
-
'[style.--kj-toast-index]': 'index()',
|
|
52
|
-
'[style.--kj-toast-before]': 'before()',
|
|
53
|
-
'[style.--kj-toast-after]': 'after()',
|
|
54
|
-
'[style.--kj-toast-height]': 'heightPx()',
|
|
55
|
-
},
|
|
56
|
-
})
|
|
57
|
-
export class KjToast {
|
|
58
|
-
/** Visual / semantic variant. Defaults to `'default'`. */
|
|
59
|
-
kjToastVariant = input<KjToastVariant>('default');
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* The toast id this element renders. Bind from the template context (`ctx.id`)
|
|
63
|
-
* so the directive can compute its position in the live queue and expose
|
|
64
|
-
* `--kj-toast-index`, `--kj-toast-before`, `--kj-toast-after`, and `data-front`.
|
|
65
|
-
* Optional — without it, the toast still renders with role/variant only.
|
|
66
|
-
*/
|
|
67
|
-
kjToastId = input<string | null>(null);
|
|
68
|
-
|
|
69
|
-
private readonly svc = inject(KjToastService);
|
|
70
|
-
private readonly el = inject(ElementRef<HTMLElement>);
|
|
71
|
-
private readonly destroyRef = inject(DestroyRef);
|
|
72
|
-
private readonly _height = signal<number>(0);
|
|
73
|
-
|
|
74
|
-
readonly role = computed(() =>
|
|
75
|
-
this.kjToastVariant() === 'destructive' ? 'alert' : 'status',
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
/** 1-based position from the front (1 = newest). `null` when no id is bound. */
|
|
79
|
-
readonly index = computed<number | null>(() => {
|
|
80
|
-
const id = this.kjToastId();
|
|
81
|
-
if (!id) return null;
|
|
82
|
-
const list = this.svc.toasts();
|
|
83
|
-
const i = list.findIndex(t => t.id === id);
|
|
84
|
-
return i >= 0 ? list.length - i : null;
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
/** Toasts older than this one (behind in the stack). */
|
|
88
|
-
readonly before = computed<number | null>(() => {
|
|
89
|
-
const idx = this.index();
|
|
90
|
-
return idx == null ? null : idx - 1;
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
/** Toasts newer than this one (in front of it). */
|
|
94
|
-
readonly after = computed<number | null>(() => {
|
|
95
|
-
const idx = this.index();
|
|
96
|
-
if (idx == null) return null;
|
|
97
|
-
return this.svc.toasts().length - idx;
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
/** True when this is the front-most (newest) toast. */
|
|
101
|
-
readonly isFront = computed(() => this.index() === 1);
|
|
102
|
-
|
|
103
|
-
/** Measured height as a pixel string (`"42px"`). Empty when unmeasured. */
|
|
104
|
-
readonly heightPx = computed(() => {
|
|
105
|
-
const h = this._height();
|
|
106
|
-
return h > 0 ? `${h}px` : '';
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
constructor() {
|
|
110
|
-
afterNextRender(() => {
|
|
111
|
-
const node = this.el.nativeElement;
|
|
112
|
-
const measure = () => this._height.set(Math.round(node.getBoundingClientRect().height));
|
|
113
|
-
measure();
|
|
114
|
-
// Use border-box geometry via getBoundingClientRect — contentRect strips padding.
|
|
115
|
-
const ro = new ResizeObserver(measure);
|
|
116
|
-
ro.observe(node);
|
|
117
|
-
this.destroyRef.onDestroy(() => ro.disconnect());
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/** Per-toast renderable: the resolved template + the bound context to render with. */
|
|
123
|
-
export interface KjToastRenderable<TData = unknown> {
|
|
124
|
-
readonly id: string;
|
|
125
|
-
readonly template: TemplateRef<KjToastTemplateContext<TData>> | null;
|
|
126
|
-
readonly context: KjToastTemplateContext<TData>;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Container for active toasts. Reads the queue from `KjToastService`, applies
|
|
131
|
-
* a visibility cap (`kjToastMaxVisible`), and exposes layout state both as
|
|
132
|
-
* inputs-driven CSS variables and `data-position-x` / `data-position-y`
|
|
133
|
-
* attributes so client CSS can drive position, gap, stacking strategy, and
|
|
134
|
-
* z-index.
|
|
135
|
-
*
|
|
136
|
-
* **CSS variables exposed on the host:**
|
|
137
|
-
* - `--kj-toast-gap` — gap between stacked toasts
|
|
138
|
-
* - `--kj-toast-z-index` — base z-index for toasts
|
|
139
|
-
* - `--kj-toasts-count` — number of currently-rendered toasts
|
|
140
|
-
* - `--kj-toast-front-height` — measured height of the front-most toast
|
|
141
|
-
*
|
|
142
|
-
* **Data attributes:**
|
|
143
|
-
* - `data-position-x="start|center|end"`
|
|
144
|
-
* - `data-position-y="top|bottom"`
|
|
145
|
-
* - `data-expanded="true|false"`
|
|
146
|
-
*
|
|
147
|
-
* @example
|
|
148
|
-
* ```html
|
|
149
|
-
* <ol kjToastViewport
|
|
150
|
-
* [kjToastDefaultTemplate]="defaultTpl"
|
|
151
|
-
* [kjToastMaxVisible]="3"
|
|
152
|
-
* [kjToastGap]="8"
|
|
153
|
-
* kjToastPositionX="end"
|
|
154
|
-
* kjToastPositionY="bottom"
|
|
155
|
-
* #vp="kjToastViewport"
|
|
156
|
-
* aria-label="Notifications">
|
|
157
|
-
* @for (r of vp.renderable(); track r.id) {
|
|
158
|
-
* <li><ng-container *ngTemplateOutlet="r.template; context: { $implicit: r.context }" /></li>
|
|
159
|
-
* }
|
|
160
|
-
* </ol>
|
|
161
|
-
* ```
|
|
162
|
-
* @category Core/Overlays
|
|
163
|
-
*/
|
|
164
|
-
@Directive({
|
|
165
|
-
selector: '[kjToastViewport]',
|
|
166
|
-
standalone: true,
|
|
167
|
-
exportAs: 'kjToastViewport',
|
|
168
|
-
host: {
|
|
169
|
-
'role': 'region',
|
|
170
|
-
'[attr.aria-live]': '"polite"',
|
|
171
|
-
'[attr.aria-atomic]': '"false"',
|
|
172
|
-
'[attr.aria-relevant]': '"additions removals"',
|
|
173
|
-
'[attr.data-position-x]': 'kjToastPositionX()',
|
|
174
|
-
'[attr.data-position-y]': 'kjToastPositionY()',
|
|
175
|
-
'[attr.data-expanded]': 'expanded()',
|
|
176
|
-
'[style.--kj-toast-gap]': 'gapPx()',
|
|
177
|
-
'[style.--kj-toast-z-index]': 'kjToastBaseZIndex()',
|
|
178
|
-
'[style.--kj-toasts-count]': 'renderable().length',
|
|
179
|
-
'[style.--kj-toast-front-height]': 'frontHeightPx()',
|
|
180
|
-
'(mouseenter)': 'onHover(true)',
|
|
181
|
-
'(mouseleave)': 'onHover(false)',
|
|
182
|
-
'(focusin)': 'onHover(true)',
|
|
183
|
-
'(focusout)': 'onHover(false)',
|
|
184
|
-
},
|
|
185
|
-
})
|
|
186
|
-
export class KjToastViewport {
|
|
187
|
-
private readonly svc = inject(KjToastService);
|
|
188
|
-
private readonly el = inject(ElementRef<HTMLElement>);
|
|
189
|
-
private readonly destroyRef = inject(DestroyRef);
|
|
190
|
-
private readonly strategy = inject(KJ_TOAST_STRATEGY);
|
|
191
|
-
|
|
192
|
-
/** Default template used for string-based toasts. Per-call templates override this. */
|
|
193
|
-
readonly kjToastDefaultTemplate = input<TemplateRef<KjToastTemplateContext> | null>(null);
|
|
194
|
-
|
|
195
|
-
/** Cap on simultaneously-rendered toasts. Excess stays queued and surfaces as space frees. Defaults from the active `KjToastStrategy`. */
|
|
196
|
-
readonly kjToastMaxVisible = input<number>(this.strategy.maxVisible);
|
|
197
|
-
|
|
198
|
-
/** Pixel gap between stacked toasts — exposed as `--kj-toast-gap`. Defaults from the active `KjToastStrategy`. */
|
|
199
|
-
readonly kjToastGap = input<number>(this.strategy.gap);
|
|
200
|
-
|
|
201
|
-
/** Base `z-index` exposed as `--kj-toast-z-index`. Defaults from the active `KjToastStrategy`. */
|
|
202
|
-
readonly kjToastBaseZIndex = input<number>(this.strategy.baseZIndex);
|
|
203
|
-
|
|
204
|
-
/** Horizontal anchor — written as `data-position-x` for client CSS. Defaults from the active `KjToastStrategy`. */
|
|
205
|
-
readonly kjToastPositionX = input<KjToastPositionX>(this.strategy.positionX);
|
|
206
|
-
|
|
207
|
-
/** Vertical anchor — written as `data-position-y` for client CSS. Defaults from the active `KjToastStrategy`. */
|
|
208
|
-
readonly kjToastPositionY = input<KjToastPositionY>(this.strategy.positionY);
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Manual override for the spread-out state. When unset (`undefined`) and the active
|
|
212
|
-
* strategy has `expandOnHover: true`, the viewport flips automatically on hover/focus.
|
|
213
|
-
* Set explicitly to force one state or to drive expansion from custom logic.
|
|
214
|
-
*/
|
|
215
|
-
readonly kjToastExpand = input<boolean | undefined>(undefined);
|
|
216
|
-
|
|
217
|
-
private readonly _hovered = signal(false);
|
|
218
|
-
|
|
219
|
-
/** Resolved expand state — explicit input wins; otherwise falls back to hover when the strategy enables it. */
|
|
220
|
-
readonly expanded = computed(() => {
|
|
221
|
-
const override = this.kjToastExpand();
|
|
222
|
-
if (override !== undefined) return override;
|
|
223
|
-
return this.strategy.expandOnHover && this._hovered();
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
protected onHover(state: boolean): void {
|
|
227
|
-
if (this.strategy.expandOnHover) this._hovered.set(state);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/** Live list of queued toasts (uncapped). */
|
|
231
|
-
readonly toasts = this.svc.toasts;
|
|
232
|
-
|
|
233
|
-
/** Renderables — capped by `kjToastMaxVisible`, paired with a template + bound context. */
|
|
234
|
-
readonly renderable = computed<KjToastRenderable[]>(() => {
|
|
235
|
-
const fallback = this.kjToastDefaultTemplate();
|
|
236
|
-
const cap = this.kjToastMaxVisible();
|
|
237
|
-
const list = this.toasts();
|
|
238
|
-
// Newest are at the END of the service array; the front of the stack is the
|
|
239
|
-
// last item. When capping, drop the OLDEST first.
|
|
240
|
-
const visible = Number.isFinite(cap) && list.length > cap
|
|
241
|
-
? list.slice(list.length - cap)
|
|
242
|
-
: list;
|
|
243
|
-
return visible.map(t => ({
|
|
244
|
-
id: t.id,
|
|
245
|
-
template: t.template ?? fallback,
|
|
246
|
-
context: this.svc.contextFor(t),
|
|
247
|
-
}));
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
private readonly _frontHeight = signal<number>(0);
|
|
251
|
-
|
|
252
|
-
/** `--kj-toast-gap` value (`"8px"`). */
|
|
253
|
-
readonly gapPx = computed(() => `${this.kjToastGap()}px`);
|
|
254
|
-
|
|
255
|
-
/** `--kj-toast-front-height` value (`"42px"`) — the measured height of the front toast. */
|
|
256
|
-
readonly frontHeightPx = computed(() => {
|
|
257
|
-
const h = this._frontHeight();
|
|
258
|
-
return h > 0 ? `${h}px` : '';
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
constructor() {
|
|
262
|
-
afterNextRender(() => {
|
|
263
|
-
const root = this.el.nativeElement as HTMLElement;
|
|
264
|
-
const measure = () => {
|
|
265
|
-
const front = root.querySelector('[kjToast][data-front="true"]') as HTMLElement | null;
|
|
266
|
-
if (!front) { this._frontHeight.set(0); return; }
|
|
267
|
-
this._frontHeight.set(Math.round(front.getBoundingClientRect().height));
|
|
268
|
-
};
|
|
269
|
-
measure();
|
|
270
|
-
const ro = new ResizeObserver(measure);
|
|
271
|
-
// Observe the viewport — children added/resized trigger remeasure.
|
|
272
|
-
ro.observe(root);
|
|
273
|
-
// MutationObserver picks up new children (data-front="true" added on a new toast).
|
|
274
|
-
const mo = new MutationObserver(measure);
|
|
275
|
-
mo.observe(root, { childList: true, subtree: true, attributes: true, attributeFilter: ['data-front'] });
|
|
276
|
-
this.destroyRef.onDestroy(() => { ro.disconnect(); mo.disconnect(); });
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Optional dismiss button. Use `(click)="ctx.dismiss()"` directly in the template
|
|
283
|
-
* for new code — this directive is for cases where the dismiss target id is
|
|
284
|
-
* known outside the template context.
|
|
285
|
-
*
|
|
286
|
-
* @example
|
|
287
|
-
* ```html
|
|
288
|
-
* <button [kjToastClose]="toast.id" aria-label="Dismiss">×</button>
|
|
289
|
-
* ```
|
|
290
|
-
* @category Core/Overlays
|
|
291
|
-
*/
|
|
292
|
-
@Directive({
|
|
293
|
-
selector: '[kjToastClose]',
|
|
294
|
-
standalone: true,
|
|
295
|
-
host: {
|
|
296
|
-
'(click)': 'dismiss()',
|
|
297
|
-
},
|
|
298
|
-
})
|
|
299
|
-
export class KjToastClose {
|
|
300
|
-
private readonly svc = inject(KjToastService);
|
|
301
|
-
/** The id of the toast to dismiss on click. */
|
|
302
|
-
kjToastClose = input.required<string>();
|
|
303
|
-
|
|
304
|
-
dismiss(): void {
|
|
305
|
-
this.svc.dismiss(this.kjToastClose());
|
|
306
|
-
}
|
|
307
|
-
}
|
package/src/toast/toast.types.ts
DELETED
package/src/toggle/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { KjToggle } from './toggle';
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { render, fireEvent } from '@testing-library/angular';
|
|
2
|
-
import { axe, toHaveNoViolations } from 'jest-axe';
|
|
3
|
-
import { KjToggle } from './toggle';
|
|
4
|
-
|
|
5
|
-
expect.extend(toHaveNoViolations);
|
|
6
|
-
|
|
7
|
-
describe('KjToggle', () => {
|
|
8
|
-
it('has aria-pressed=false by default', async () => {
|
|
9
|
-
const { getByRole } = await render(`<button kjToggle>Bold</button>`, { imports: [KjToggle] });
|
|
10
|
-
expect(getByRole('button')).toHaveAttribute('aria-pressed', 'false');
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it('toggles aria-pressed on click', async () => {
|
|
14
|
-
const { getByRole } = await render(`<button kjToggle>Bold</button>`, { imports: [KjToggle] });
|
|
15
|
-
fireEvent.click(getByRole('button'));
|
|
16
|
-
expect(getByRole('button')).toHaveAttribute('aria-pressed', 'true');
|
|
17
|
-
expect(getByRole('button')).toHaveAttribute('data-pressed', '');
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('passes axe audit', async () => {
|
|
21
|
-
const { container } = await render(`<button kjToggle>Bold</button>`, { imports: [KjToggle] });
|
|
22
|
-
expect(await axe(container)).toHaveNoViolations();
|
|
23
|
-
});
|
|
24
|
-
});
|
package/src/toggle/toggle.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { Directive, inject, model } from '@angular/core';
|
|
2
|
-
import { KjDisabled, KjFocusRing, KjFormControl } from '../primitives';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Adds toggle (press) behavior with Angular forms integration.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```html
|
|
9
|
-
* <button kjToggle [(kjPressed)]="isBold" aria-label="Bold">B</button>
|
|
10
|
-
* ```
|
|
11
|
-
* @category Core/Inputs
|
|
12
|
-
*/
|
|
13
|
-
@Directive({
|
|
14
|
-
selector: '[kjToggle]',
|
|
15
|
-
standalone: true,
|
|
16
|
-
hostDirectives: [
|
|
17
|
-
{ directive: KjDisabled, inputs: ['kjDisabled'] },
|
|
18
|
-
KjFocusRing,
|
|
19
|
-
KjFormControl,
|
|
20
|
-
],
|
|
21
|
-
host: {
|
|
22
|
-
'[attr.aria-pressed]': 'kjPressed().toString()',
|
|
23
|
-
'[attr.data-pressed]': 'kjPressed() ? "" : null',
|
|
24
|
-
'(click)': 'toggle()',
|
|
25
|
-
'(blur)': 'formCtrl.notifyTouched()',
|
|
26
|
-
},
|
|
27
|
-
})
|
|
28
|
-
export class KjToggle {
|
|
29
|
-
readonly formCtrl = inject(KjFormControl);
|
|
30
|
-
|
|
31
|
-
/** Whether the toggle is pressed. Supports two-way binding. */
|
|
32
|
-
kjPressed = model<boolean>(false);
|
|
33
|
-
|
|
34
|
-
/** @internal */
|
|
35
|
-
toggle(): void {
|
|
36
|
-
const next = !this.kjPressed();
|
|
37
|
-
this.kjPressed.set(next);
|
|
38
|
-
this.formCtrl.notifyChange(next);
|
|
39
|
-
}
|
|
40
|
-
}
|
package/src/tooltip/index.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { Component } from '@angular/core';
|
|
2
|
-
import { KjTooltipTrigger, KjTooltipContent } from './tooltip';
|
|
3
|
-
import { KjButton } from '../button/button';
|
|
4
|
-
|
|
5
|
-
@Component({
|
|
6
|
-
selector: 'kj-example-tooltip-basic',
|
|
7
|
-
standalone: true,
|
|
8
|
-
imports: [KjTooltipTrigger, KjTooltipContent, KjButton],
|
|
9
|
-
styleUrls: ['../styles/docs-themes.css'],
|
|
10
|
-
styles: [`
|
|
11
|
-
:host { display: flex; align-items: center; justify-content: center; padding: 5rem 3rem; background: var(--kj-bg); font-family: var(--kj-font); min-height: 180px; }
|
|
12
|
-
.row { display: flex; gap: 1.5rem; flex-wrap: wrap; align-items: center; justify-content: center; }
|
|
13
|
-
.tip-wrap { position: relative; display: inline-block; }
|
|
14
|
-
button[kjButton] { padding: 0.5rem 1.25rem; font-family: var(--kj-font); font-size: 0.875rem; border: var(--kj-btn-border); cursor: pointer; transition: var(--kj-transition); }
|
|
15
|
-
[data-variant="default"] { background: var(--kj-accent); color: var(--kj-accent-on); }
|
|
16
|
-
[data-variant="link"] { background: transparent; color: var(--kj-accent); border: none; padding: 0.5rem 0.5rem; text-decoration: none; text-underline-offset: 4px; }
|
|
17
|
-
[data-variant="link"]:hover { text-decoration: underline; }
|
|
18
|
-
[data-variant="ghost"] { background: transparent; color: var(--kj-btn-ghost-color); }
|
|
19
|
-
[kjTooltipContent] {
|
|
20
|
-
position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%);
|
|
21
|
-
background: var(--kj-accent); color: var(--kj-accent-on); padding: 0.25rem 0.625rem; font-size: 0.75rem;
|
|
22
|
-
white-space: nowrap; pointer-events: none; transition: var(--kj-transition);
|
|
23
|
-
}
|
|
24
|
-
[kjTooltipContent][hidden] { display: none; }
|
|
25
|
-
[data-side="bottom"] { top: calc(100% + 8px); bottom: auto; }
|
|
26
|
-
[data-side="left"] { right: calc(100% + 8px); left: auto; bottom: auto; top: 50%; transform: translateY(-50%); }
|
|
27
|
-
[data-side="right"] { left: calc(100% + 8px); bottom: auto; top: 50%; transform: translateY(-50%); }
|
|
28
|
-
`],
|
|
29
|
-
template: `
|
|
30
|
-
<div class="row">
|
|
31
|
-
<div class="tip-wrap">
|
|
32
|
-
<button kjButton [kjVariant]="'default'" [kjTooltipTrigger]="tip1">Default</button>
|
|
33
|
-
<span #tip1 kjTooltipContent>Copy to clipboard</span>
|
|
34
|
-
</div>
|
|
35
|
-
<div class="tip-wrap">
|
|
36
|
-
<button kjButton [kjVariant]="'link'" [kjTooltipTrigger]="tip2">Link</button>
|
|
37
|
-
<span #tip2 kjTooltipContent [kjTooltipSide]="'bottom'">Opens below</span>
|
|
38
|
-
</div>
|
|
39
|
-
<div class="tip-wrap">
|
|
40
|
-
<button kjButton [kjVariant]="'ghost'" [kjTooltipTrigger]="tip3">Ghost</button>
|
|
41
|
-
<span #tip3 kjTooltipContent [kjTooltipSide]="'right'">Opens right</span>
|
|
42
|
-
</div>
|
|
43
|
-
</div>
|
|
44
|
-
`,
|
|
45
|
-
})
|
|
46
|
-
export class TooltipBasicExample {}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { Component } from '@angular/core';
|
|
2
|
-
import { KjTooltipTrigger, KjTooltipContent } from './tooltip';
|
|
3
|
-
import { KjButton } from '../button/button';
|
|
4
|
-
|
|
5
|
-
@Component({
|
|
6
|
-
selector: 'kj-example-tooltip-finance',
|
|
7
|
-
standalone: true,
|
|
8
|
-
imports: [KjTooltipTrigger, KjTooltipContent, KjButton],
|
|
9
|
-
styleUrls: ['../styles/docs-themes.css'],
|
|
10
|
-
styles: [`
|
|
11
|
-
:host { display: flex; align-items: center; justify-content: center; padding: 5rem 3rem; background: var(--kj-bg); font-family: var(--kj-font); min-height: 180px; color: var(--kj-text); }
|
|
12
|
-
.row { display: flex; gap: 1.5rem; flex-wrap: wrap; align-items: center; justify-content: center; }
|
|
13
|
-
.tip-wrap { position: relative; display: inline-block; }
|
|
14
|
-
button[kjButton] {
|
|
15
|
-
padding: 4px 15px; font-family: var(--kj-font); font-size: 14px; font-weight: 400;
|
|
16
|
-
border: 1px solid transparent; border-radius: 6px; cursor: pointer;
|
|
17
|
-
transition: var(--kj-transition); line-height: 1.5;
|
|
18
|
-
}
|
|
19
|
-
button[kjButton][data-variant="default"] { background: var(--kj-accent); color: var(--kj-accent-on); border-color: var(--kj-accent); }
|
|
20
|
-
button[kjButton][data-variant="default"]:hover { background: #4096ff; border-color: #4096ff; }
|
|
21
|
-
button[kjButton][data-variant="link"] { background: transparent; color: var(--kj-accent); border-color: transparent; }
|
|
22
|
-
button[kjButton][data-variant="link"]:hover { color: #4096ff; }
|
|
23
|
-
button[kjButton][data-variant="ghost"] { background: transparent; color: var(--kj-text); border: 1px solid var(--kj-text); }
|
|
24
|
-
button[kjButton][data-variant="ghost"]:hover { color: var(--kj-accent); border-color: var(--kj-accent); }
|
|
25
|
-
[kjTooltipContent] {
|
|
26
|
-
position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%);
|
|
27
|
-
background: #1f2937; color: #f9fafb;
|
|
28
|
-
padding: 0.3rem 0.625rem; font-size: 0.75rem; border-radius: var(--kj-radius-sm);
|
|
29
|
-
white-space: nowrap; pointer-events: none; box-shadow: var(--kj-shadow-sm);
|
|
30
|
-
}
|
|
31
|
-
[kjTooltipContent][hidden] { display: none; }
|
|
32
|
-
[data-side="bottom"] { top: calc(100% + 8px); bottom: auto; }
|
|
33
|
-
[data-side="right"] { left: calc(100% + 8px); bottom: auto; top: 50%; transform: translateY(-50%); }
|
|
34
|
-
[data-side="left"] { right: calc(100% + 8px); left: auto; bottom: auto; top: 50%; transform: translateY(-50%); }
|
|
35
|
-
`],
|
|
36
|
-
host: { class: 'kj-theme-finance' },
|
|
37
|
-
template: `
|
|
38
|
-
<div class="row">
|
|
39
|
-
<div class="tip-wrap">
|
|
40
|
-
<button kjButton [kjVariant]="'default'" [kjTooltipTrigger]="tip1">Default</button>
|
|
41
|
-
<span #tip1 kjTooltipContent>Copy to clipboard</span>
|
|
42
|
-
</div>
|
|
43
|
-
<div class="tip-wrap">
|
|
44
|
-
<button kjButton [kjVariant]="'link'" [kjTooltipTrigger]="tip2">Link</button>
|
|
45
|
-
<span #tip2 kjTooltipContent [kjTooltipSide]="'bottom'">Opens below</span>
|
|
46
|
-
</div>
|
|
47
|
-
<div class="tip-wrap">
|
|
48
|
-
<button kjButton [kjVariant]="'ghost'" [kjTooltipTrigger]="tip3">Ghost</button>
|
|
49
|
-
<span #tip3 kjTooltipContent [kjTooltipSide]="'right'">Opens right</span>
|
|
50
|
-
</div>
|
|
51
|
-
</div>
|
|
52
|
-
`,
|
|
53
|
-
})
|
|
54
|
-
export class TooltipFinanceExample {}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { Component } from '@angular/core';
|
|
2
|
-
import { KjTooltipTrigger, KjTooltipContent } from './tooltip';
|
|
3
|
-
import { KjButton } from '../button/button';
|
|
4
|
-
|
|
5
|
-
@Component({
|
|
6
|
-
selector: 'kj-example-tooltip-placements',
|
|
7
|
-
standalone: true,
|
|
8
|
-
imports: [KjTooltipTrigger, KjTooltipContent, KjButton],
|
|
9
|
-
styleUrls: ['../styles/docs-themes.css'],
|
|
10
|
-
styles: [`
|
|
11
|
-
:host { display: flex; align-items: center; justify-content: center; padding: 5rem 4rem; background: var(--kj-bg); font-family: var(--kj-font); min-height: 220px; }
|
|
12
|
-
.grid { display: grid; grid-template-columns: 1fr 1fr 1fr; grid-template-rows: auto auto auto; gap: 0.75rem; place-items: center; }
|
|
13
|
-
.tip-wrap { position: relative; display: inline-block; }
|
|
14
|
-
button[kjButton] { padding: 0.5rem 1.25rem; font-family: var(--kj-font); font-size: 0.875rem; border: var(--kj-btn-border); cursor: pointer; transition: var(--kj-transition); }
|
|
15
|
-
[data-variant="default"] { background: var(--kj-accent); color: var(--kj-accent-on); }
|
|
16
|
-
[kjTooltipContent] {
|
|
17
|
-
position: absolute; z-index: 10;
|
|
18
|
-
background: var(--kj-accent); color: var(--kj-accent-on); padding: 0.25rem 0.625rem; font-size: 0.75rem;
|
|
19
|
-
white-space: nowrap; pointer-events: none; transition: var(--kj-transition);
|
|
20
|
-
}
|
|
21
|
-
[kjTooltipContent][hidden] { display: none; }
|
|
22
|
-
[data-side="top"] { bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%); }
|
|
23
|
-
[data-side="bottom"] { top: calc(100% + 8px); left: 50%; transform: translateX(-50%); }
|
|
24
|
-
[data-side="left"] { right: calc(100% + 8px); top: 50%; transform: translateY(-50%); }
|
|
25
|
-
[data-side="right"] { left: calc(100% + 8px); top: 50%; transform: translateY(-50%); }
|
|
26
|
-
.empty { width: 80px; }
|
|
27
|
-
`],
|
|
28
|
-
template: `
|
|
29
|
-
<div class="grid">
|
|
30
|
-
<div class="empty"></div>
|
|
31
|
-
<div class="tip-wrap">
|
|
32
|
-
<button kjButton [kjVariant]="'default'" [kjTooltipTrigger]="tipTop">Top</button>
|
|
33
|
-
<span #tipTop kjTooltipContent [kjTooltipSide]="'top'">Top tooltip</span>
|
|
34
|
-
</div>
|
|
35
|
-
<div class="empty"></div>
|
|
36
|
-
|
|
37
|
-
<div class="tip-wrap">
|
|
38
|
-
<button kjButton [kjVariant]="'default'" [kjTooltipTrigger]="tipLeft">Left</button>
|
|
39
|
-
<span #tipLeft kjTooltipContent [kjTooltipSide]="'left'">Left tooltip</span>
|
|
40
|
-
</div>
|
|
41
|
-
<div class="empty"></div>
|
|
42
|
-
<div class="tip-wrap">
|
|
43
|
-
<button kjButton [kjVariant]="'default'" [kjTooltipTrigger]="tipRight">Right</button>
|
|
44
|
-
<span #tipRight kjTooltipContent [kjTooltipSide]="'right'">Right tooltip</span>
|
|
45
|
-
</div>
|
|
46
|
-
|
|
47
|
-
<div class="empty"></div>
|
|
48
|
-
<div class="tip-wrap">
|
|
49
|
-
<button kjButton [kjVariant]="'default'" [kjTooltipTrigger]="tipBottom">Bottom</button>
|
|
50
|
-
<span #tipBottom kjTooltipContent [kjTooltipSide]="'bottom'">Bottom tooltip</span>
|
|
51
|
-
</div>
|
|
52
|
-
<div class="empty"></div>
|
|
53
|
-
</div>
|
|
54
|
-
`,
|
|
55
|
-
})
|
|
56
|
-
export class TooltipPlacementsExample {}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { Component } from '@angular/core';
|
|
2
|
-
import { KjTooltipTrigger, KjTooltipContent } from './tooltip';
|
|
3
|
-
import { KjButton } from '../button/button';
|
|
4
|
-
|
|
5
|
-
@Component({
|
|
6
|
-
selector: 'kj-example-tooltip-retro',
|
|
7
|
-
standalone: true,
|
|
8
|
-
imports: [KjTooltipTrigger, KjTooltipContent, KjButton],
|
|
9
|
-
styleUrls: ['../styles/docs-themes.css'],
|
|
10
|
-
styles: [`
|
|
11
|
-
:host { display: flex; align-items: center; justify-content: center; padding: 5rem 3rem; background: var(--kj-bg); font-family: var(--kj-font); min-height: 180px; color: var(--kj-text); }
|
|
12
|
-
.row { display: flex; gap: 1.5rem; flex-wrap: wrap; align-items: center; justify-content: center; }
|
|
13
|
-
.tip-wrap { position: relative; display: inline-block; }
|
|
14
|
-
button[kjButton] {
|
|
15
|
-
padding: 0.35rem 0.875rem; font-family: var(--kj-font); font-size: 0.75rem;
|
|
16
|
-
font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
|
|
17
|
-
border: var(--kj-btn-border); border-radius: var(--kj-radius-md); cursor: pointer;
|
|
18
|
-
box-shadow: var(--kj-shadow-sm); transition: var(--kj-transition);
|
|
19
|
-
}
|
|
20
|
-
button[kjButton]:not([data-variant="link"]):not([data-variant="ghost"]):hover {
|
|
21
|
-
transform: translate(-1px, -1px); box-shadow: var(--kj-shadow-md);
|
|
22
|
-
}
|
|
23
|
-
button[kjButton][data-variant="default"] { background: var(--kj-text); color: var(--kj-bg); }
|
|
24
|
-
button[kjButton][data-variant="link"] {
|
|
25
|
-
background: transparent; color: var(--kj-text); border: none; box-shadow: none;
|
|
26
|
-
border-radius: 0; padding: 0.35rem 0.875rem; text-decoration: none;
|
|
27
|
-
}
|
|
28
|
-
button[kjButton][data-variant="link"]:hover {
|
|
29
|
-
text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 4px;
|
|
30
|
-
}
|
|
31
|
-
button[kjButton][data-variant="ghost"] {
|
|
32
|
-
background: transparent; color: var(--kj-text); border: 2px solid transparent; box-shadow: none; opacity: 0.7;
|
|
33
|
-
}
|
|
34
|
-
button[kjButton][data-variant="ghost"]:hover { opacity: 1; background: rgba(0,0,0,0.06); }
|
|
35
|
-
[kjTooltipContent] {
|
|
36
|
-
position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%);
|
|
37
|
-
background: var(--kj-text); color: var(--kj-bg);
|
|
38
|
-
padding: 0.2rem 0.5rem; font-size: 0.6875rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
|
|
39
|
-
white-space: nowrap; pointer-events: none; border: 1px solid var(--kj-border);
|
|
40
|
-
box-shadow: var(--kj-shadow-sm);
|
|
41
|
-
}
|
|
42
|
-
[kjTooltipContent][hidden] { display: none; }
|
|
43
|
-
[data-side="bottom"] { top: calc(100% + 8px); bottom: auto; }
|
|
44
|
-
[data-side="right"] { left: calc(100% + 8px); bottom: auto; top: 50%; transform: translateY(-50%); }
|
|
45
|
-
[data-side="left"] { right: calc(100% + 8px); left: auto; bottom: auto; top: 50%; transform: translateY(-50%); }
|
|
46
|
-
`],
|
|
47
|
-
host: { class: 'kj-theme-retro' },
|
|
48
|
-
template: `
|
|
49
|
-
<div class="row">
|
|
50
|
-
<div class="tip-wrap">
|
|
51
|
-
<button kjButton [kjVariant]="'default'" [kjTooltipTrigger]="tip1">Default</button>
|
|
52
|
-
<span #tip1 kjTooltipContent>Copy to clipboard</span>
|
|
53
|
-
</div>
|
|
54
|
-
<div class="tip-wrap">
|
|
55
|
-
<button kjButton [kjVariant]="'link'" [kjTooltipTrigger]="tip2">Link</button>
|
|
56
|
-
<span #tip2 kjTooltipContent [kjTooltipSide]="'bottom'">Opens below</span>
|
|
57
|
-
</div>
|
|
58
|
-
<div class="tip-wrap">
|
|
59
|
-
<button kjButton [kjVariant]="'ghost'" [kjTooltipTrigger]="tip3">Ghost</button>
|
|
60
|
-
<span #tip3 kjTooltipContent [kjTooltipSide]="'right'">Opens right</span>
|
|
61
|
-
</div>
|
|
62
|
-
</div>
|
|
63
|
-
`,
|
|
64
|
-
})
|
|
65
|
-
export class TooltipRetroExample {}
|