@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
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { Component } from '@angular/core';
|
|
2
|
-
import { KjButton } from './button';
|
|
3
|
-
|
|
4
|
-
@Component({
|
|
5
|
-
selector: 'kj-example-button-retro',
|
|
6
|
-
standalone: true,
|
|
7
|
-
imports: [KjButton],
|
|
8
|
-
styleUrls: ['../styles/docs-themes.css'],
|
|
9
|
-
styles: [`
|
|
10
|
-
:host { display: block; padding: 2rem; background: var(--kj-bg); font-family: var(--kj-font); color: var(--kj-text); }
|
|
11
|
-
.row { display: flex; gap: 0.625rem; flex-wrap: wrap; }
|
|
12
|
-
button[kjButton] {
|
|
13
|
-
padding: 0.35rem 0.875rem; font-family: var(--kj-font); font-size: 0.75rem;
|
|
14
|
-
font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
|
|
15
|
-
border: var(--kj-btn-border); border-radius: var(--kj-radius-md); cursor: pointer;
|
|
16
|
-
box-shadow: var(--kj-shadow-sm); transition: var(--kj-transition);
|
|
17
|
-
}
|
|
18
|
-
button[kjButton]:not([data-variant="link"]):not([data-variant="ghost"]):hover {
|
|
19
|
-
transform: translate(-1px, -1px); box-shadow: var(--kj-shadow-md);
|
|
20
|
-
}
|
|
21
|
-
button[kjButton][data-variant="default"] { background: var(--kj-text); color: var(--kj-bg); }
|
|
22
|
-
button[kjButton][data-variant="destructive"] { background: var(--kj-destructive); color: #fff; }
|
|
23
|
-
button[kjButton][data-variant="link"] {
|
|
24
|
-
background: transparent; color: var(--kj-text); border: none; box-shadow: none;
|
|
25
|
-
border-radius: 0; padding: 0.35rem 0.875rem; text-decoration: none;
|
|
26
|
-
}
|
|
27
|
-
button[kjButton][data-variant="link"]:hover {
|
|
28
|
-
text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 4px;
|
|
29
|
-
}
|
|
30
|
-
button[kjButton][data-variant="ghost"] {
|
|
31
|
-
background: transparent; color: var(--kj-text); border: 2px solid transparent; box-shadow: none; opacity: 0.7;
|
|
32
|
-
}
|
|
33
|
-
button[kjButton][data-variant="ghost"]:hover { opacity: 1; background: rgba(0,0,0,0.06); }
|
|
34
|
-
[aria-disabled="true"] { opacity: 0.4; pointer-events: none; }
|
|
35
|
-
`],
|
|
36
|
-
host: { class: 'kj-theme-retro' },
|
|
37
|
-
template: `
|
|
38
|
-
<div class="row">
|
|
39
|
-
<button kjButton [kjVariant]="'default'">Default</button>
|
|
40
|
-
<button kjButton [kjVariant]="'destructive'">Destructive</button>
|
|
41
|
-
<button kjButton [kjVariant]="'link'">Link</button>
|
|
42
|
-
<button kjButton [kjVariant]="'ghost'">Ghost</button>
|
|
43
|
-
<button kjButton [kjDisabled]="true">Disabled</button>
|
|
44
|
-
</div>
|
|
45
|
-
`,
|
|
46
|
-
})
|
|
47
|
-
export class ButtonRetroExample {}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { Component } from '@angular/core';
|
|
2
|
-
import { KjButton } from './button';
|
|
3
|
-
|
|
4
|
-
@Component({
|
|
5
|
-
selector: 'kj-example-button-sizes',
|
|
6
|
-
standalone: true,
|
|
7
|
-
imports: [KjButton],
|
|
8
|
-
styleUrls: ['../styles/docs-themes.css'],
|
|
9
|
-
styles: [`
|
|
10
|
-
:host { display: block; padding: 2rem; background: var(--kj-bg); font-family: var(--kj-font); }
|
|
11
|
-
.row { display: flex; flex-direction: row; gap: 0.75rem; align-items: center; flex-wrap: wrap; }
|
|
12
|
-
button[kjButton] { background: var(--kj-accent); color: var(--kj-accent-on); border: var(--kj-btn-border); cursor: pointer; font-family: var(--kj-font); transition: var(--kj-transition); }
|
|
13
|
-
[data-size="sm"] { padding: 0.3rem 0.75rem; font-size: 0.75rem; }
|
|
14
|
-
[data-size="md"] { padding: 0.5rem 1.25rem; font-size: 0.875rem; }
|
|
15
|
-
[data-size="lg"] { padding: 0.75rem 1.75rem; font-size: 1rem; }
|
|
16
|
-
`],
|
|
17
|
-
template: `
|
|
18
|
-
<div class="row">
|
|
19
|
-
<button kjButton [kjSize]="'sm'">Small</button>
|
|
20
|
-
<button kjButton [kjSize]="'md'">Medium (default)</button>
|
|
21
|
-
<button kjButton [kjSize]="'lg'">Large</button>
|
|
22
|
-
</div>
|
|
23
|
-
`,
|
|
24
|
-
})
|
|
25
|
-
export class ButtonSizesExample {}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { render } from '@testing-library/angular';
|
|
2
|
-
import { axe, toHaveNoViolations } from 'jest-axe';
|
|
3
|
-
import { KjButton } from './button';
|
|
4
|
-
|
|
5
|
-
expect.extend(toHaveNoViolations);
|
|
6
|
-
|
|
7
|
-
describe('KjButton', () => {
|
|
8
|
-
it('renders a button element', async () => {
|
|
9
|
-
const { getByRole } = await render(`<button kjButton>Click</button>`, { imports: [KjButton] });
|
|
10
|
-
expect(getByRole('button')).toBeInTheDocument();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it('sets data-variant attribute', async () => {
|
|
14
|
-
const { getByRole } = await render(`<button kjButton [kjVariant]="'destructive'">Delete</button>`, { imports: [KjButton] });
|
|
15
|
-
expect(getByRole('button')).toHaveAttribute('data-variant', 'destructive');
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('sets data-size attribute', async () => {
|
|
19
|
-
const { getByRole } = await render(`<button kjButton [kjSize]="'sm'">Small</button>`, { imports: [KjButton] });
|
|
20
|
-
expect(getByRole('button')).toHaveAttribute('data-size', 'sm');
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('sets aria-disabled when disabled via hostDirective', async () => {
|
|
24
|
-
const { getByRole } = await render(`<button kjButton [kjDisabled]="true">Disabled</button>`, { imports: [KjButton] });
|
|
25
|
-
expect(getByRole('button')).toHaveAttribute('aria-disabled', 'true');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('passes axe audit', async () => {
|
|
29
|
-
const { container } = await render(`<button kjButton>Action</button>`, { imports: [KjButton] });
|
|
30
|
-
expect(await axe(container)).toHaveNoViolations();
|
|
31
|
-
});
|
|
32
|
-
});
|
package/src/button/button.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { Directive, input } from '@angular/core';
|
|
2
|
-
import { KjDisabled, KjFocusRing } from '../primitives';
|
|
3
|
-
|
|
4
|
-
export type KjButtonVariant = 'default' | 'destructive' | 'outline' | 'ghost' | 'link';
|
|
5
|
-
export type KjButtonSize = 'sm' | 'md' | 'lg' | 'icon';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Enhances a native ```<button>``` with variant, size, disabled state, and focus-ring behavior.
|
|
9
|
-
* Composes `KjDisabled` and `KjFocusRing` via `hostDirectives`.
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```html
|
|
13
|
-
* <button kjButton [kjVariant]="'destructive'" [kjDisabled]="isLoading()">Delete</button>
|
|
14
|
-
* ```
|
|
15
|
-
* @doc
|
|
16
|
-
* @doc-example Variants
|
|
17
|
-
* @doc-theme default
|
|
18
|
-
* @doc-file button.example.ts
|
|
19
|
-
* @doc-theme retro
|
|
20
|
-
* @doc-file button.retro.example.ts
|
|
21
|
-
* @doc-theme finance
|
|
22
|
-
* @doc-file button.finance.example.ts
|
|
23
|
-
* @doc-example Sizes
|
|
24
|
-
* @doc-file button.sizes.example.ts
|
|
25
|
-
* @category Core/Base
|
|
26
|
-
*/
|
|
27
|
-
@Directive({
|
|
28
|
-
selector: '[kjButton]',
|
|
29
|
-
standalone: true,
|
|
30
|
-
hostDirectives: [
|
|
31
|
-
{ directive: KjDisabled, inputs: ['kjDisabled'] },
|
|
32
|
-
KjFocusRing,
|
|
33
|
-
],
|
|
34
|
-
host: {
|
|
35
|
-
'[attr.data-variant]': 'kjVariant()',
|
|
36
|
-
'[attr.data-size]': 'kjSize()',
|
|
37
|
-
},
|
|
38
|
-
})
|
|
39
|
-
export class KjButton {
|
|
40
|
-
/** The visual variant of the button. Defaults to `'default'`. */
|
|
41
|
-
kjVariant = input<KjButtonVariant>('default');
|
|
42
|
-
/** The size of the button. Defaults to `'md'`. */
|
|
43
|
-
kjSize = input<KjButtonSize>('md');
|
|
44
|
-
}
|
package/src/button/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { KjButton, type KjButtonVariant, type KjButtonSize } from './button';
|
package/src/chart/chart.spec.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { render } from '@testing-library/angular';
|
|
2
|
-
import { axe, toHaveNoViolations } from 'jest-axe';
|
|
3
|
-
import { KjChart } from './chart';
|
|
4
|
-
|
|
5
|
-
expect.extend(toHaveNoViolations);
|
|
6
|
-
|
|
7
|
-
describe('KjChart', () => {
|
|
8
|
-
it('renders the host element', async () => {
|
|
9
|
-
const { container } = await render(`<div kjChart [kjChartOption]="{}"></div>`, { imports: [KjChart] });
|
|
10
|
-
expect(container.querySelector('[kjChart]')).toBeInTheDocument();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it('sets role=img', async () => {
|
|
14
|
-
const { container } = await render(`<div kjChart [kjChartOption]="{}" kjChartLabel="Sales"></div>`, { imports: [KjChart] });
|
|
15
|
-
expect(container.querySelector('[kjChart]')).toHaveAttribute('role', 'img');
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('sets aria-label', async () => {
|
|
19
|
-
const { container } = await render(`<div kjChart [kjChartOption]="{}" kjChartLabel="Revenue chart"></div>`, { imports: [KjChart] });
|
|
20
|
-
expect(container.querySelector('[kjChart]')).toHaveAttribute('aria-label', 'Revenue chart');
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('passes axe audit', async () => {
|
|
24
|
-
const { container } = await render(`<div kjChart [kjChartOption]="{}" kjChartLabel="Revenue chart"></div>`, { imports: [KjChart] });
|
|
25
|
-
expect(await axe(container)).toHaveNoViolations();
|
|
26
|
-
});
|
|
27
|
-
});
|
package/src/chart/chart.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { Directive, DestroyRef, ElementRef, inject, input, afterNextRender, afterEveryRender } from '@angular/core';
|
|
2
|
-
import type { EChartsOption } from 'echarts';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Wraps Apache ECharts. Initializes after first render, updates reactively, disposes on destroy.
|
|
6
|
-
* Always provide `kjChartLabel` for WCAG AAA compliance.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* ```html
|
|
10
|
-
* <div kjChart [kjChartOption]="chartOption()" kjChartLabel="Monthly revenue" style="height:300px"></div>
|
|
11
|
-
* ```
|
|
12
|
-
* @category Core/Data
|
|
13
|
-
*/
|
|
14
|
-
@Directive({
|
|
15
|
-
selector: '[kjChart]', standalone: true,
|
|
16
|
-
host: { role: 'img', '[attr.aria-label]': 'kjChartLabel() || null' },
|
|
17
|
-
})
|
|
18
|
-
export class KjChart {
|
|
19
|
-
private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);
|
|
20
|
-
private readonly destroyRef = inject(DestroyRef);
|
|
21
|
-
|
|
22
|
-
/** ECharts option object defining the chart. */
|
|
23
|
-
kjChartOption = input.required<EChartsOption>();
|
|
24
|
-
/** Accessible label for the chart. Required for WCAG AAA compliance. */
|
|
25
|
-
kjChartLabel = input<string>('');
|
|
26
|
-
|
|
27
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
|
-
private chart: any;
|
|
29
|
-
|
|
30
|
-
constructor() {
|
|
31
|
-
afterNextRender(async () => {
|
|
32
|
-
try {
|
|
33
|
-
const echarts = await import('echarts');
|
|
34
|
-
this.chart = echarts.init(this.el.nativeElement);
|
|
35
|
-
this.chart.setOption(this.kjChartOption());
|
|
36
|
-
this.destroyRef.onDestroy(() => this.chart?.dispose());
|
|
37
|
-
} catch {
|
|
38
|
-
// ECharts cannot initialize in non-browser environments (jsdom, SSR)
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
afterEveryRender(() => {
|
|
43
|
-
if (this.chart) {
|
|
44
|
-
this.chart.setOption(this.kjChartOption());
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
}
|
package/src/chart/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { KjChart } from './chart';
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { render, fireEvent } from '@testing-library/angular';
|
|
2
|
-
import { axe, toHaveNoViolations } from 'jest-axe';
|
|
3
|
-
import { KjCheckbox } from './checkbox';
|
|
4
|
-
|
|
5
|
-
expect.extend(toHaveNoViolations);
|
|
6
|
-
|
|
7
|
-
describe('KjCheckbox', () => {
|
|
8
|
-
it('sets role=checkbox', async () => {
|
|
9
|
-
const { getByRole } = await render(`<div kjCheckbox tabindex="0">Check</div>`, { imports: [KjCheckbox] });
|
|
10
|
-
expect(getByRole('checkbox')).toBeInTheDocument();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it('has aria-checked=false by default', async () => {
|
|
14
|
-
const { getByRole } = await render(`<div kjCheckbox tabindex="0">Check</div>`, { imports: [KjCheckbox] });
|
|
15
|
-
expect(getByRole('checkbox')).toHaveAttribute('aria-checked', 'false');
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('toggles checked on click', async () => {
|
|
19
|
-
const { getByRole } = await render(`<div kjCheckbox tabindex="0">Check</div>`, { imports: [KjCheckbox] });
|
|
20
|
-
fireEvent.click(getByRole('checkbox'));
|
|
21
|
-
expect(getByRole('checkbox')).toHaveAttribute('aria-checked', 'true');
|
|
22
|
-
expect(getByRole('checkbox')).toHaveAttribute('data-checked', '');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('toggles on Space key', async () => {
|
|
26
|
-
const { getByRole } = await render(`<div kjCheckbox tabindex="0">Check</div>`, { imports: [KjCheckbox] });
|
|
27
|
-
fireEvent.keyDown(getByRole('checkbox'), { key: ' ' });
|
|
28
|
-
expect(getByRole('checkbox')).toHaveAttribute('aria-checked', 'true');
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('passes axe audit', async () => {
|
|
32
|
-
const { container } = await render(`<div kjCheckbox tabindex="0" aria-label="Accept terms">Accept</div>`, { imports: [KjCheckbox] });
|
|
33
|
-
expect(await axe(container)).toHaveNoViolations();
|
|
34
|
-
});
|
|
35
|
-
});
|
package/src/checkbox/checkbox.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { Directive, inject, model } from '@angular/core';
|
|
2
|
-
import { KjDisabled, KjFocusRing, KjFormControl } from '../primitives';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Adds checkbox semantics with Angular forms integration.
|
|
6
|
-
* Supports `formControl`, `formControlName`, and `ngModel` bindings.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* ```html
|
|
10
|
-
* <div kjCheckbox tabindex="0" [(kjChecked)]="accepted" aria-label="Accept terms">Accept</div>
|
|
11
|
-
* ```
|
|
12
|
-
* @doc
|
|
13
|
-
* @doc-file checkbox.example.ts
|
|
14
|
-
* ```typescript
|
|
15
|
-
* import { Component, signal } from '@angular/core';
|
|
16
|
-
* import { KjCheckbox } from '@kouji-ui/core';
|
|
17
|
-
*
|
|
18
|
-
* @Component({
|
|
19
|
-
* standalone: true,
|
|
20
|
-
* imports: [KjCheckbox],
|
|
21
|
-
* styles: [`
|
|
22
|
-
* .row { display: flex; flex-direction: column; gap: 1rem; padding: 2rem; background: #0c0c0c; }
|
|
23
|
-
* .item { display: flex; align-items: center; gap: 0.75rem; }
|
|
24
|
-
* [kjCheckbox] { width: 18px; height: 18px; border: 2px solid #333; background: #111; cursor: pointer; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
|
25
|
-
* [data-checked] { background: #b8f500; border-color: #b8f500; }
|
|
26
|
-
* [data-checked]::after { content: '✓'; color: #0c0c0c; font-size: 0.75rem; font-weight: bold; }
|
|
27
|
-
* span { color: #888; font-family: 'JetBrains Mono', monospace; font-size: 0.875rem; }
|
|
28
|
-
* `],
|
|
29
|
-
* template: `
|
|
30
|
-
* <div class="row">
|
|
31
|
-
* <div class="item">
|
|
32
|
-
* <div kjCheckbox tabindex="0" [(kjChecked)]="terms" aria-label="Accept terms"></div>
|
|
33
|
-
* <span>Accept terms — {{ terms() ? 'checked' : 'unchecked' }}</span>
|
|
34
|
-
* </div>
|
|
35
|
-
* <div class="item">
|
|
36
|
-
* <div kjCheckbox tabindex="0" [(kjChecked)]="newsletter" aria-label="Newsletter"></div>
|
|
37
|
-
* <span>Subscribe to newsletter</span>
|
|
38
|
-
* </div>
|
|
39
|
-
* </div>
|
|
40
|
-
* `,
|
|
41
|
-
* })
|
|
42
|
-
* export class CheckboxExampleComponent {
|
|
43
|
-
* terms = signal(false);
|
|
44
|
-
* newsletter = signal(true);
|
|
45
|
-
* }
|
|
46
|
-
* ```
|
|
47
|
-
* @category Core/Inputs
|
|
48
|
-
*/
|
|
49
|
-
@Directive({
|
|
50
|
-
selector: '[kjCheckbox]',
|
|
51
|
-
standalone: true,
|
|
52
|
-
hostDirectives: [
|
|
53
|
-
{ directive: KjDisabled, inputs: ['kjDisabled'] },
|
|
54
|
-
KjFocusRing,
|
|
55
|
-
KjFormControl,
|
|
56
|
-
],
|
|
57
|
-
host: {
|
|
58
|
-
role: 'checkbox',
|
|
59
|
-
'[attr.aria-checked]': 'kjChecked().toString()',
|
|
60
|
-
'[attr.data-checked]': 'kjChecked() ? "" : null',
|
|
61
|
-
'[attr.disabled]': 'formCtrl.disabled() ? "" : null',
|
|
62
|
-
'(click)': 'toggle()',
|
|
63
|
-
'(keydown.space)': 'onSpace($event)',
|
|
64
|
-
'(blur)': 'formCtrl.notifyTouched()',
|
|
65
|
-
},
|
|
66
|
-
})
|
|
67
|
-
export class KjCheckbox {
|
|
68
|
-
readonly formCtrl = inject(KjFormControl);
|
|
69
|
-
|
|
70
|
-
/** Whether the checkbox is checked. Supports two-way binding. */
|
|
71
|
-
kjChecked = model<boolean>(false);
|
|
72
|
-
|
|
73
|
-
/** @internal */
|
|
74
|
-
toggle(): void {
|
|
75
|
-
const next = !this.kjChecked();
|
|
76
|
-
this.kjChecked.set(next);
|
|
77
|
-
this.formCtrl.notifyChange(next);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** @internal */
|
|
81
|
-
onSpace(e: Event): void { e.preventDefault(); this.toggle(); }
|
|
82
|
-
}
|
package/src/checkbox/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { KjCheckbox } from './checkbox';
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { Component, signal } from '@angular/core';
|
|
2
|
-
import { KjDialogTrigger, KjDialog, KjDialogOverlay, KjDialogTitle } from './dialog';
|
|
3
|
-
import { KjButton } from '../button/button';
|
|
4
|
-
|
|
5
|
-
@Component({
|
|
6
|
-
selector: 'kj-example-dialog-confirm',
|
|
7
|
-
standalone: true,
|
|
8
|
-
imports: [KjDialogTrigger, KjDialog, KjDialogOverlay, KjDialogTitle, KjButton],
|
|
9
|
-
styleUrls: ['../styles/docs-themes.css'],
|
|
10
|
-
styles: [`
|
|
11
|
-
:host { display: flex; align-items: center; justify-content: center; gap: 1rem; padding: 3rem 2rem; background: var(--kj-bg); font-family: var(--kj-font); min-height: 160px; flex-direction: column; color: var(--kj-text); }
|
|
12
|
-
.status { font-size: 0.8125rem; color: var(--kj-text-muted); min-height: 1.25rem; }
|
|
13
|
-
.status.confirmed { color: var(--kj-accent); }
|
|
14
|
-
.status.cancelled { color: var(--kj-destructive); }
|
|
15
|
-
button[kjButton] { padding: 0.5rem 1.5rem; border: var(--kj-btn-border); cursor: pointer; font-family: var(--kj-font); font-size: 0.875rem; }
|
|
16
|
-
[data-variant="destructive"] { background: var(--kj-destructive); color: #fff; }
|
|
17
|
-
[data-variant="outline"] { background: transparent; color: var(--kj-text); border: 1px solid var(--kj-border); }
|
|
18
|
-
::ng-deep .kj-dialog-backdrop { background: var(--kj-backdrop); }
|
|
19
|
-
::ng-deep .kj-dialog-panel { background: transparent; box-shadow: none; }
|
|
20
|
-
::ng-deep [kjDialogOverlay] { display: flex; align-items: center; justify-content: center; position: fixed; inset: 0; }
|
|
21
|
-
::ng-deep [kjDialog] { background: var(--kj-surface); border: 1px solid var(--kj-border); border-radius: var(--kj-radius-lg); padding: 1.5rem; min-width: 22rem; color: var(--kj-text); font-family: var(--kj-font); box-shadow: var(--kj-shadow-hard); }
|
|
22
|
-
::ng-deep [kjDialogTitle] { margin: 0 0 0.5rem; font-size: 1.125rem; color: var(--kj-text); }
|
|
23
|
-
::ng-deep .dialog-body { margin: 0 0 1.5rem; font-size: 0.875rem; color: var(--kj-text-muted); line-height: 1.6; }
|
|
24
|
-
::ng-deep .dialog-actions { display: flex; gap: 0.75rem; justify-content: flex-end; }
|
|
25
|
-
::ng-deep .dialog-actions button[kjButton] { padding: 0.4rem 1rem; font-size: 0.8125rem; }
|
|
26
|
-
`],
|
|
27
|
-
template: `
|
|
28
|
-
<button kjButton [kjVariant]="'destructive'"
|
|
29
|
-
[kjDialogTrigger]="confirmDialog"
|
|
30
|
-
(kjDialogClosed)="onResult($event)">
|
|
31
|
-
Delete Account
|
|
32
|
-
</button>
|
|
33
|
-
<ng-template #confirmDialog>
|
|
34
|
-
<div kjDialogOverlay>
|
|
35
|
-
<div kjDialog #dlg="kjDialog">
|
|
36
|
-
<h2 kjDialogTitle>Are you absolutely sure?</h2>
|
|
37
|
-
<p class="dialog-body">This action cannot be undone. All your data will be permanently removed.</p>
|
|
38
|
-
<div class="dialog-actions">
|
|
39
|
-
<button kjButton [kjVariant]="'outline'" (click)="dlg.close('cancelled')">Cancel</button>
|
|
40
|
-
<button kjButton [kjVariant]="'destructive'" (click)="dlg.close('confirmed')">Yes, delete</button>
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
</div>
|
|
44
|
-
</ng-template>
|
|
45
|
-
<span class="status"
|
|
46
|
-
[class.confirmed]="result() === 'confirmed'"
|
|
47
|
-
[class.cancelled]="result() === 'cancelled'">
|
|
48
|
-
{{ result() === 'confirmed' ? '✓ Account deleted' : result() === 'cancelled' ? '✕ Cancelled' : '' }}
|
|
49
|
-
</span>
|
|
50
|
-
`,
|
|
51
|
-
})
|
|
52
|
-
export class DialogConfirmExample {
|
|
53
|
-
readonly result = signal<'confirmed' | 'cancelled' | null>(null);
|
|
54
|
-
onResult(value: unknown): void {
|
|
55
|
-
if (value === 'confirmed' || value === 'cancelled') this.result.set(value);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { InjectionToken, Signal } from '@angular/core';
|
|
2
|
-
|
|
3
|
-
export interface KjDialogContext {
|
|
4
|
-
readonly open: Signal<boolean>;
|
|
5
|
-
readonly dialogId: string;
|
|
6
|
-
readonly closeOnEscape: Signal<boolean>;
|
|
7
|
-
readonly closeOnBackdrop: Signal<boolean>;
|
|
8
|
-
close(result?: unknown): void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const KJ_DIALOG = new InjectionToken<KjDialogContext>('KjDialog');
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { Component } from '@angular/core';
|
|
2
|
-
import { KjDialogTrigger, KjDialog, KjDialogOverlay, KjDialogTitle, KjDialogClose } from './dialog';
|
|
3
|
-
import { KjButton } from '../button/button';
|
|
4
|
-
|
|
5
|
-
@Component({
|
|
6
|
-
selector: 'kj-example-dialog-basic',
|
|
7
|
-
standalone: true,
|
|
8
|
-
imports: [KjDialogTrigger, KjDialog, KjDialogOverlay, KjDialogTitle, KjDialogClose, KjButton],
|
|
9
|
-
styleUrls: ['../styles/docs-themes.css'],
|
|
10
|
-
styles: [`
|
|
11
|
-
:host { display: flex; align-items: center; justify-content: center; padding: 3rem 2rem; background: var(--kj-bg); font-family: var(--kj-font); min-height: 160px; }
|
|
12
|
-
button[kjButton] { padding: 0.5rem 1.5rem; border: var(--kj-btn-border); cursor: pointer; font-family: var(--kj-font); font-size: 0.875rem; transition: var(--kj-transition); }
|
|
13
|
-
[data-variant="default"] { background: var(--kj-accent); color: var(--kj-accent-on); }
|
|
14
|
-
[data-variant="outline"] { background: transparent; color: var(--kj-text); border: 1px solid var(--kj-border); }
|
|
15
|
-
::ng-deep .kj-dialog-backdrop { background: var(--kj-backdrop); }
|
|
16
|
-
::ng-deep .kj-dialog-panel { background: transparent; box-shadow: none; }
|
|
17
|
-
::ng-deep [kjDialogOverlay] { display: flex; align-items: center; justify-content: center; position: fixed; inset: 0; }
|
|
18
|
-
::ng-deep [kjDialog] { background: var(--kj-surface); border: 1px solid var(--kj-border); border-radius: var(--kj-radius-lg); padding: 1.5rem; min-width: 20rem; color: var(--kj-text); font-family: var(--kj-font); box-shadow: var(--kj-shadow-hard); }
|
|
19
|
-
::ng-deep [kjDialogTitle] { margin: 0 0 0.5rem; font-size: 1.125rem; color: var(--kj-text); }
|
|
20
|
-
::ng-deep .dialog-body { margin: 0 0 1.5rem; font-size: 0.875rem; color: var(--kj-text-muted); line-height: 1.6; }
|
|
21
|
-
::ng-deep .dialog-actions { display: flex; gap: 0.75rem; justify-content: flex-end; }
|
|
22
|
-
::ng-deep .dialog-actions button[kjButton] { padding: 0.4rem 1rem; font-size: 0.8125rem; }
|
|
23
|
-
`],
|
|
24
|
-
template: `
|
|
25
|
-
<button kjButton [kjVariant]="'default'" [kjDialogTrigger]="myDialog">Open Dialog</button>
|
|
26
|
-
<ng-template #myDialog>
|
|
27
|
-
<div kjDialogOverlay>
|
|
28
|
-
<div kjDialog>
|
|
29
|
-
<h2 kjDialogTitle>Edit Profile</h2>
|
|
30
|
-
<p class="dialog-body">Make changes to your profile settings here.</p>
|
|
31
|
-
<div class="dialog-actions">
|
|
32
|
-
<button kjButton [kjVariant]="'outline'" kjDialogClose>Cancel</button>
|
|
33
|
-
<button kjButton [kjVariant]="'default'" kjDialogClose>Save Changes</button>
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
</ng-template>
|
|
38
|
-
`,
|
|
39
|
-
})
|
|
40
|
-
export class DialogBasicExample {}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { Component } from '@angular/core';
|
|
2
|
-
import { KjDialogTrigger, KjDialog, KjDialogOverlay, KjDialogTitle, KjDialogClose } from './dialog';
|
|
3
|
-
|
|
4
|
-
@Component({
|
|
5
|
-
selector: 'kj-example-dialog-finance',
|
|
6
|
-
standalone: true,
|
|
7
|
-
imports: [KjDialogTrigger, KjDialog, KjDialogOverlay, KjDialogTitle, KjDialogClose],
|
|
8
|
-
styleUrls: ['../styles/docs-themes.css'],
|
|
9
|
-
styles: [`
|
|
10
|
-
:host { display: flex; align-items: center; justify-content: center; padding: 3rem 2rem; background: var(--kj-bg); font-family: var(--kj-font); min-height: 160px; }
|
|
11
|
-
button {
|
|
12
|
-
padding: 0.45rem 1.125rem; background: var(--kj-accent); color: var(--kj-accent-on);
|
|
13
|
-
border: var(--kj-btn-border); border-color: var(--kj-accent);
|
|
14
|
-
border-radius: var(--kj-radius-md); cursor: pointer; font-family: var(--kj-font); font-size: 0.875rem; font-weight: 500;
|
|
15
|
-
}
|
|
16
|
-
button:hover { background: #2563eb; }
|
|
17
|
-
::ng-deep .kj-dialog-backdrop { background: var(--kj-backdrop); }
|
|
18
|
-
::ng-deep .kj-dialog-panel { background: transparent; box-shadow: none; }
|
|
19
|
-
::ng-deep [kjDialogOverlay] { display: flex; align-items: center; justify-content: center; position: fixed; inset: 0; }
|
|
20
|
-
::ng-deep [kjDialog] { background: var(--kj-surface); border: 1px solid var(--kj-border); border-radius: var(--kj-radius-lg); padding: 1.5rem; min-width: 22rem; box-shadow: var(--kj-shadow-hard); font-family: var(--kj-font); color: var(--kj-text); }
|
|
21
|
-
::ng-deep [kjDialogTitle] { margin: 0 0 0.375rem; font-size: 1.0625rem; font-weight: 600; color: var(--kj-text); }
|
|
22
|
-
::ng-deep .dialog-body { margin: 0 0 1.5rem; font-size: 0.875rem; color: var(--kj-text-muted); line-height: 1.6; }
|
|
23
|
-
::ng-deep .dialog-actions { display: flex; gap: 0.5rem; justify-content: flex-end; }
|
|
24
|
-
::ng-deep [kjDialogOverlay] button { padding: 0.4rem 1rem; font-size: 0.8125rem; font-weight: 500; border-radius: var(--kj-radius-md); }
|
|
25
|
-
::ng-deep .btn-cancel { background: var(--kj-surface); color: var(--kj-text); border: 1px solid var(--kj-border); }
|
|
26
|
-
::ng-deep .btn-cancel:hover { background: var(--kj-bg); }
|
|
27
|
-
::ng-deep .btn-primary { background: var(--kj-accent); color: var(--kj-accent-on); border-color: var(--kj-accent); }
|
|
28
|
-
::ng-deep .btn-primary:hover { background: #2563eb; }
|
|
29
|
-
`],
|
|
30
|
-
host: { class: 'kj-theme-finance' },
|
|
31
|
-
template: `
|
|
32
|
-
<button [kjDialogTrigger]="myDialog">Open Dialog</button>
|
|
33
|
-
<ng-template #myDialog>
|
|
34
|
-
<div kjDialogOverlay>
|
|
35
|
-
<div kjDialog>
|
|
36
|
-
<h2 kjDialogTitle>Edit Profile</h2>
|
|
37
|
-
<p class="dialog-body">Make changes to your profile settings here.</p>
|
|
38
|
-
<div class="dialog-actions">
|
|
39
|
-
<button class="btn-cancel" kjDialogClose>Cancel</button>
|
|
40
|
-
<button class="btn-primary" kjDialogClose>Save Changes</button>
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
</div>
|
|
44
|
-
</ng-template>
|
|
45
|
-
`,
|
|
46
|
-
})
|
|
47
|
-
export class DialogFinanceExample {}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { Component } from '@angular/core';
|
|
2
|
-
import { KjDialogTrigger, KjDialog, KjDialogOverlay, KjDialogTitle, KjDialogClose } from './dialog';
|
|
3
|
-
|
|
4
|
-
@Component({
|
|
5
|
-
selector: 'kj-example-dialog-retro',
|
|
6
|
-
standalone: true,
|
|
7
|
-
imports: [KjDialogTrigger, KjDialog, KjDialogOverlay, KjDialogTitle, KjDialogClose],
|
|
8
|
-
styleUrls: ['../styles/docs-themes.css'],
|
|
9
|
-
styles: [`
|
|
10
|
-
:host { display: flex; align-items: center; justify-content: center; padding: 3rem 2rem; background: var(--kj-bg); font-family: var(--kj-font); min-height: 160px; color: var(--kj-text); }
|
|
11
|
-
button {
|
|
12
|
-
padding: 0.4rem 1rem; font-family: var(--kj-font); font-size: 0.8rem; font-weight: 700;
|
|
13
|
-
letter-spacing: 0.06em; text-transform: uppercase; background: var(--kj-surface); color: var(--kj-text);
|
|
14
|
-
border: var(--kj-btn-border); cursor: pointer;
|
|
15
|
-
box-shadow: var(--kj-shadow-sm); transition: var(--kj-transition);
|
|
16
|
-
}
|
|
17
|
-
button:hover { transform: translate(-1px, -1px); box-shadow: var(--kj-shadow-md); }
|
|
18
|
-
::ng-deep .kj-dialog-backdrop { background: var(--kj-backdrop); }
|
|
19
|
-
::ng-deep .kj-dialog-panel { background: transparent; box-shadow: none; }
|
|
20
|
-
::ng-deep [kjDialogOverlay] { display: flex; align-items: center; justify-content: center; position: fixed; inset: 0; }
|
|
21
|
-
::ng-deep [kjDialog] { background: var(--kj-surface); border: var(--kj-btn-border); padding: 1.5rem; min-width: 20rem; box-shadow: var(--kj-shadow-hard); font-family: var(--kj-font); color: var(--kj-text); }
|
|
22
|
-
::ng-deep [kjDialogTitle] { margin: 0 0 0.5rem; font-size: 1rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; border-bottom: 2px solid var(--kj-border); padding-bottom: 0.5rem; color: var(--kj-text); }
|
|
23
|
-
::ng-deep .dialog-body { margin: 0.75rem 0 1.5rem; font-size: 0.8rem; color: var(--kj-text-muted); line-height: 1.6; }
|
|
24
|
-
::ng-deep .dialog-actions { display: flex; gap: 0.5rem; justify-content: flex-end; }
|
|
25
|
-
::ng-deep [kjDialogOverlay] button { font-size: 0.75rem; padding: 0.3rem 0.875rem; box-shadow: var(--kj-shadow-sm); }
|
|
26
|
-
::ng-deep [kjDialogOverlay] button:hover { box-shadow: var(--kj-shadow-md); }
|
|
27
|
-
::ng-deep .btn-primary { background: var(--kj-accent); color: var(--kj-accent-on); }
|
|
28
|
-
`],
|
|
29
|
-
host: { class: 'kj-theme-retro' },
|
|
30
|
-
template: `
|
|
31
|
-
<button [kjDialogTrigger]="myDialog">Open Dialog</button>
|
|
32
|
-
<ng-template #myDialog>
|
|
33
|
-
<div kjDialogOverlay>
|
|
34
|
-
<div kjDialog>
|
|
35
|
-
<h2 kjDialogTitle>Edit Profile</h2>
|
|
36
|
-
<p class="dialog-body">Make changes to your profile settings here.</p>
|
|
37
|
-
<div class="dialog-actions">
|
|
38
|
-
<button kjDialogClose>Cancel</button>
|
|
39
|
-
<button class="btn-primary" kjDialogClose>Save Changes</button>
|
|
40
|
-
</div>
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
</ng-template>
|
|
44
|
-
`,
|
|
45
|
-
})
|
|
46
|
-
export class DialogRetroExample {}
|