@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.
Files changed (127) hide show
  1. package/fesm2022/kouji-ui-core.mjs +26789 -0
  2. package/fesm2022/kouji-ui-core.mjs.map +1 -0
  3. package/icon/icon.css +40 -0
  4. package/package.json +20 -9
  5. package/types/kouji-ui-core.d.ts +13545 -0
  6. package/typography/prose.css +312 -0
  7. package/.turbo/turbo-build.log +0 -27
  8. package/CHANGELOG.md +0 -21
  9. package/ng-package.json +0 -7
  10. package/src/a11y/.gitkeep +0 -0
  11. package/src/a11y/aria-describedby.spec.ts +0 -55
  12. package/src/a11y/aria-describedby.ts +0 -33
  13. package/src/a11y/focus-trap.spec.ts +0 -86
  14. package/src/a11y/focus-trap.ts +0 -70
  15. package/src/a11y/index.ts +0 -9
  16. package/src/a11y/live-region.spec.ts +0 -58
  17. package/src/a11y/live-region.ts +0 -48
  18. package/src/a11y/roving-tabindex.spec.ts +0 -62
  19. package/src/a11y/roving-tabindex.ts +0 -104
  20. package/src/a11y/visually-hidden.spec.ts +0 -34
  21. package/src/a11y/visually-hidden.ts +0 -24
  22. package/src/accordion/accordion.context.ts +0 -22
  23. package/src/accordion/accordion.spec.ts +0 -32
  24. package/src/accordion/accordion.ts +0 -129
  25. package/src/accordion/index.ts +0 -2
  26. package/src/avatar/avatar.spec.ts +0 -18
  27. package/src/avatar/avatar.ts +0 -26
  28. package/src/avatar/index.ts +0 -1
  29. package/src/badge/badge.spec.ts +0 -20
  30. package/src/badge/badge.ts +0 -14
  31. package/src/badge/index.ts +0 -1
  32. package/src/button/button.example.ts +0 -30
  33. package/src/button/button.finance.example.ts +0 -37
  34. package/src/button/button.retro.example.ts +0 -47
  35. package/src/button/button.sizes.example.ts +0 -25
  36. package/src/button/button.spec.ts +0 -32
  37. package/src/button/button.ts +0 -44
  38. package/src/button/index.ts +0 -1
  39. package/src/chart/chart.spec.ts +0 -27
  40. package/src/chart/chart.ts +0 -48
  41. package/src/chart/index.ts +0 -1
  42. package/src/checkbox/checkbox.spec.ts +0 -35
  43. package/src/checkbox/checkbox.ts +0 -82
  44. package/src/checkbox/index.ts +0 -1
  45. package/src/dialog/dialog.confirm.example.ts +0 -57
  46. package/src/dialog/dialog.context.ts +0 -11
  47. package/src/dialog/dialog.example.ts +0 -40
  48. package/src/dialog/dialog.finance.example.ts +0 -47
  49. package/src/dialog/dialog.retro.example.ts +0 -46
  50. package/src/dialog/dialog.service.ts +0 -176
  51. package/src/dialog/dialog.spec.ts +0 -122
  52. package/src/dialog/dialog.ts +0 -201
  53. package/src/dialog/index.ts +0 -9
  54. package/src/example-components.ts +0 -33
  55. package/src/form/form.context.ts +0 -3
  56. package/src/form/form.spec.ts +0 -25
  57. package/src/form/form.ts +0 -46
  58. package/src/form/index.ts +0 -2
  59. package/src/input/index.ts +0 -1
  60. package/src/input/input.spec.ts +0 -43
  61. package/src/input/input.ts +0 -80
  62. package/src/menu/index.ts +0 -2
  63. package/src/menu/menu.context.ts +0 -10
  64. package/src/menu/menu.example.ts +0 -39
  65. package/src/menu/menu.spec.ts +0 -168
  66. package/src/menu/menu.ts +0 -176
  67. package/src/popover/index.ts +0 -12
  68. package/src/popover/popover.context.ts +0 -18
  69. package/src/popover/popover.example.ts +0 -50
  70. package/src/popover/popover.finance.example.ts +0 -52
  71. package/src/popover/popover.retro.example.ts +0 -53
  72. package/src/popover/popover.spec.ts +0 -52
  73. package/src/popover/popover.ts +0 -124
  74. package/src/primitives/.gitkeep +0 -0
  75. package/src/primitives/forms/form-control.spec.ts +0 -59
  76. package/src/primitives/forms/form-control.ts +0 -90
  77. package/src/primitives/forms/index.ts +0 -1
  78. package/src/primitives/index.ts +0 -3
  79. package/src/primitives/interaction/disabled.spec.ts +0 -47
  80. package/src/primitives/interaction/disabled.ts +0 -24
  81. package/src/primitives/interaction/focus-ring.spec.ts +0 -62
  82. package/src/primitives/interaction/focus-ring.ts +0 -57
  83. package/src/primitives/interaction/index.ts +0 -2
  84. package/src/primitives/overlay/index.ts +0 -1
  85. package/src/primitives/overlay/overlay.spec.ts +0 -90
  86. package/src/primitives/overlay/overlay.ts +0 -106
  87. package/src/public-api.ts +0 -35
  88. package/src/radio/index.ts +0 -2
  89. package/src/radio/radio.context.ts +0 -3
  90. package/src/radio/radio.spec.ts +0 -36
  91. package/src/radio/radio.ts +0 -55
  92. package/src/select/index.ts +0 -2
  93. package/src/select/select.context.ts +0 -3
  94. package/src/select/select.spec.ts +0 -145
  95. package/src/select/select.ts +0 -250
  96. package/src/styles/docs-themes.css +0 -82
  97. package/src/table/index.ts +0 -1
  98. package/src/table/table.spec.ts +0 -113
  99. package/src/table/table.ts +0 -126
  100. package/src/tabs/index.ts +0 -2
  101. package/src/tabs/tabs.context.ts +0 -9
  102. package/src/tabs/tabs.spec.ts +0 -40
  103. package/src/tabs/tabs.ts +0 -130
  104. package/src/test-setup.ts +0 -5
  105. package/src/toast/index.ts +0 -21
  106. package/src/toast/toast.example.ts +0 -134
  107. package/src/toast/toast.finance.example.ts +0 -68
  108. package/src/toast/toast.retro.example.ts +0 -69
  109. package/src/toast/toast.service.ts +0 -153
  110. package/src/toast/toast.spec.ts +0 -95
  111. package/src/toast/toast.strategy.ts +0 -102
  112. package/src/toast/toast.ts +0 -307
  113. package/src/toast/toast.types.ts +0 -5
  114. package/src/toggle/index.ts +0 -1
  115. package/src/toggle/toggle.spec.ts +0 -24
  116. package/src/toggle/toggle.ts +0 -40
  117. package/src/tooltip/index.ts +0 -5
  118. package/src/tooltip/tooltip.example.ts +0 -46
  119. package/src/tooltip/tooltip.finance.example.ts +0 -54
  120. package/src/tooltip/tooltip.placements.example.ts +0 -56
  121. package/src/tooltip/tooltip.retro.example.ts +0 -65
  122. package/src/tooltip/tooltip.spec.ts +0 -104
  123. package/src/tooltip/tooltip.ts +0 -129
  124. package/tsconfig.lib.json +0 -13
  125. package/tsconfig.lib.prod.json +0 -11
  126. package/tsconfig.spec.json +0 -10
  127. package/vite.config.ts +0 -19
@@ -1,48 +0,0 @@
1
- import { Directive, ElementRef, inject, input } from '@angular/core';
2
-
3
- /** Politeness setting for ARIA live regions. */
4
- export type KjLivePoliteness = 'off' | 'polite' | 'assertive';
5
-
6
- /**
7
- * Marks an element as an ARIA live region and exposes an `announce` method
8
- * for programmatically pushing announcements to screen readers via DOM text content.
9
- *
10
- * @example
11
- * ```html
12
- * <div kjLiveRegion [kjPoliteness]="'polite'" #region="kjLiveRegion"></div>
13
- * <button (click)="region.announce('Item saved')">Save</button>
14
- * ```
15
- * @category Core/Accessibility
16
- */
17
- @Directive({
18
- selector: '[kjLiveRegion]',
19
- standalone: true,
20
- exportAs: 'kjLiveRegion',
21
- host: {
22
- '[attr.aria-live]': 'kjPoliteness()',
23
- '[attr.aria-atomic]': '"true"',
24
- },
25
- })
26
- export class KjLiveRegion {
27
- private readonly el = inject(ElementRef<HTMLElement>);
28
-
29
- /** The ARIA live politeness setting. Defaults to `'polite'`. */
30
- kjPoliteness = input<KjLivePoliteness>('polite');
31
-
32
- /**
33
- * Announces a message to screen readers by briefly clearing and re-setting text.
34
- * @param message - The message to announce.
35
- * @param durationMs - Optional duration in ms before clearing the announcement.
36
- */
37
- announce(message: string, durationMs?: number): void {
38
- const el = this.el.nativeElement;
39
- el.textContent = '';
40
- // Brief timeout lets screen readers detect the content change.
41
- setTimeout(() => {
42
- el.textContent = message;
43
- if (durationMs != null) {
44
- setTimeout(() => { el.textContent = ''; }, durationMs);
45
- }
46
- }, 50);
47
- }
48
- }
@@ -1,62 +0,0 @@
1
- import { render, fireEvent } from '@testing-library/angular';
2
- import { axe, toHaveNoViolations } from 'jest-axe';
3
- import {
4
- KjRovingTabindex,
5
- KjRovingTabindexItemDirective,
6
- } from './roving-tabindex';
7
-
8
- expect.extend(toHaveNoViolations);
9
-
10
- const template = `
11
- <div kjRovingTabindex role="toolbar" aria-label="Formatting">
12
- <button kjRovingTabindexItem>Bold</button>
13
- <button kjRovingTabindexItem>Italic</button>
14
- <button kjRovingTabindexItem>Underline</button>
15
- </div>`;
16
- const imports = [KjRovingTabindex, KjRovingTabindexItemDirective];
17
-
18
- describe('KjRovingTabindex', () => {
19
- it('renders all items', async () => {
20
- const { getAllByRole } = await render(template, { imports });
21
- expect(getAllByRole('button')).toHaveLength(3);
22
- });
23
-
24
- it('first item has tabindex 0, others -1', async () => {
25
- const { getAllByRole } = await render(template, { imports });
26
- const [first, second, third] = getAllByRole('button');
27
- expect(first).toHaveAttribute('tabindex', '0');
28
- expect(second).toHaveAttribute('tabindex', '-1');
29
- expect(third).toHaveAttribute('tabindex', '-1');
30
- });
31
-
32
- it('moves focus right on ArrowRight', async () => {
33
- const { getAllByRole, container } = await render(template, { imports });
34
- const [first, second] = getAllByRole('button');
35
- first.focus();
36
- fireEvent.keyDown(container.querySelector('[kjRovingTabindex]')!, { key: 'ArrowRight' });
37
- expect(second).toHaveAttribute('tabindex', '0');
38
- expect(first).toHaveAttribute('tabindex', '-1');
39
- });
40
-
41
- it('moves focus left on ArrowLeft', async () => {
42
- const { getAllByRole, container } = await render(template, { imports });
43
- const [first, second] = getAllByRole('button');
44
- second.focus();
45
- fireEvent.keyDown(container.querySelector('[kjRovingTabindex]')!, { key: 'ArrowLeft' });
46
- expect(first).toHaveAttribute('tabindex', '0');
47
- expect(second).toHaveAttribute('tabindex', '-1');
48
- });
49
-
50
- it('wraps to last item on ArrowLeft from first', async () => {
51
- const { getAllByRole, container } = await render(template, { imports });
52
- const buttons = getAllByRole('button');
53
- buttons[0].focus();
54
- fireEvent.keyDown(container.querySelector('[kjRovingTabindex]')!, { key: 'ArrowLeft' });
55
- expect(buttons[2]).toHaveAttribute('tabindex', '0');
56
- });
57
-
58
- it('passes axe accessibility audit', async () => {
59
- const { container } = await render(template, { imports });
60
- expect(await axe(container)).toHaveNoViolations();
61
- });
62
- });
@@ -1,104 +0,0 @@
1
- import {
2
- Directive,
3
- ElementRef,
4
- InjectionToken,
5
- contentChildren,
6
- effect,
7
- inject,
8
- signal,
9
- } from '@angular/core';
10
-
11
- /** Context token for roving tabindex coordination. */
12
- export const KJ_ROVING_TABINDEX = new InjectionToken<KjRovingTabindex>(
13
- 'KjRovingTabindex',
14
- );
15
-
16
- /**
17
- * Marks an individual item within a `[kjRovingTabindex]` container.
18
- * Its `tabindex` is managed automatically by the parent directive.
19
- *
20
- * @example
21
- * ```html
22
- * <button kjRovingTabindexItem>Item</button>
23
- * ```
24
- * @category Core/Accessibility
25
- */
26
- @Directive({
27
- selector: '[kjRovingTabindexItem]',
28
- standalone: true,
29
- host: {
30
- '[attr.tabindex]': 'active() ? "0" : "-1"',
31
- },
32
- })
33
- export class KjRovingTabindexItemDirective {
34
- /** @internal */
35
- readonly el = inject(ElementRef<HTMLElement>);
36
- /** @internal */
37
- readonly active = signal(false);
38
- }
39
-
40
- /**
41
- * Implements the roving tabindex pattern for composite widgets such as toolbars and tab lists.
42
- * Only one item has `tabindex="0"` at a time; arrow keys move focus between items.
43
- *
44
- * @example
45
- * ```html
46
- * <div kjRovingTabindex role="toolbar" aria-label="Formatting">
47
- * <button kjRovingTabindexItem>Bold</button>
48
- * <button kjRovingTabindexItem>Italic</button>
49
- * </div>
50
- * ```
51
- * @category Core/Accessibility
52
- */
53
- @Directive({
54
- selector: '[kjRovingTabindex]',
55
- standalone: true,
56
- providers: [{ provide: KJ_ROVING_TABINDEX, useExisting: KjRovingTabindex }],
57
- host: {
58
- '(keydown)': 'onKeydown($event)',
59
- '(focusin)': 'onFocusIn($event)',
60
- },
61
- })
62
- export class KjRovingTabindex {
63
- private readonly items = contentChildren(KjRovingTabindexItemDirective);
64
- private readonly activeIndex = signal(0);
65
-
66
- constructor() {
67
- effect(() => {
68
- const all = this.items();
69
- all.forEach((item, i) => item.active.set(i === this.activeIndex()));
70
- });
71
- }
72
-
73
- /** @internal — syncs activeIndex when focus moves into an item programmatically */
74
- onFocusIn(event: FocusEvent): void {
75
- const all = this.items();
76
- const idx = all.findIndex((item) => item.el.nativeElement === event.target);
77
- if (idx !== -1) {
78
- this.activeIndex.set(idx);
79
- }
80
- }
81
-
82
- /** @internal */
83
- onKeydown(event: KeyboardEvent): void {
84
- const all = this.items();
85
- if (!all.length) return;
86
- let next = this.activeIndex();
87
-
88
- if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
89
- next = (next + 1) % all.length;
90
- } else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
91
- next = (next - 1 + all.length) % all.length;
92
- } else if (event.key === 'Home') {
93
- next = 0;
94
- } else if (event.key === 'End') {
95
- next = all.length - 1;
96
- } else {
97
- return;
98
- }
99
-
100
- event.preventDefault();
101
- this.activeIndex.set(next);
102
- all[next].el.nativeElement.focus();
103
- }
104
- }
@@ -1,34 +0,0 @@
1
- import { render } from '@testing-library/angular';
2
- import { axe, toHaveNoViolations } from 'jest-axe';
3
- import { KjVisuallyHidden } from './visually-hidden';
4
-
5
- expect.extend(toHaveNoViolations);
6
-
7
- describe('KjVisuallyHidden', () => {
8
- it('applies visually-hidden inline styles', async () => {
9
- const { container } = await render(
10
- `<span kjVisuallyHidden>Screen reader only</span>`,
11
- { imports: [KjVisuallyHidden] },
12
- );
13
- const el = container.querySelector('span')!;
14
- const style = el.getAttribute('style') ?? '';
15
- expect(style).toContain('position');
16
- expect(style).toContain('absolute');
17
- });
18
-
19
- it('content remains in the DOM for screen readers', async () => {
20
- const { getByText } = await render(
21
- `<span kjVisuallyHidden>Screen reader only</span>`,
22
- { imports: [KjVisuallyHidden] },
23
- );
24
- expect(getByText('Screen reader only')).toBeInTheDocument();
25
- });
26
-
27
- it('passes axe accessibility audit', async () => {
28
- const { container } = await render(
29
- `<button><span kjVisuallyHidden>Close dialog</span></button>`,
30
- { imports: [KjVisuallyHidden] },
31
- );
32
- expect(await axe(container)).toHaveNoViolations();
33
- });
34
- });
@@ -1,24 +0,0 @@
1
- import { Directive } from '@angular/core';
2
-
3
- /**
4
- * Visually hides an element while keeping it accessible to screen readers.
5
- * Uses the standard visually-hidden CSS technique via inline styles.
6
- *
7
- * @example
8
- * ```html
9
- * <button>
10
- * <kj-icon name="close" />
11
- * <span kjVisuallyHidden>Close dialog</span>
12
- * </button>
13
- * ```
14
- * @category Core/Accessibility
15
- */
16
- @Directive({
17
- selector: '[kjVisuallyHidden]',
18
- standalone: true,
19
- host: {
20
- style:
21
- 'position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0',
22
- },
23
- })
24
- export class KjVisuallyHidden {}
@@ -1,22 +0,0 @@
1
- import { InjectionToken, Signal } from '@angular/core';
2
-
3
- /** Context interface for the root accordion directive. */
4
- export interface KjAccordionContext {
5
- readonly openIds: Signal<ReadonlySet<string>>;
6
- toggle(id: string): void;
7
- isOpen(id: string): boolean;
8
- }
9
-
10
- /** Context interface for an individual accordion item directive. */
11
- export interface KjAccordionItemContext {
12
- readonly expanded: Signal<boolean>;
13
- toggle(): void;
14
- open(): void;
15
- close(): void;
16
- }
17
-
18
- /** Injection token for the root accordion directive. */
19
- export const KJ_ACCORDION = new InjectionToken<KjAccordionContext>('KjAccordion');
20
-
21
- /** Injection token for an individual accordion item directive. */
22
- export const KJ_ACCORDION_ITEM = new InjectionToken<KjAccordionItemContext>('KjAccordionItem');
@@ -1,32 +0,0 @@
1
- import { render, fireEvent } from '@testing-library/angular';
2
- import { axe, toHaveNoViolations } from 'jest-axe';
3
- import { KjAccordion, KjAccordionItem, KjAccordionTrigger, KjAccordionContent } from './accordion';
4
-
5
- expect.extend(toHaveNoViolations);
6
- const imports = [KjAccordion, KjAccordionItem, KjAccordionTrigger, KjAccordionContent];
7
- const template = `<div kjAccordion><div kjAccordionItem [kjItemValue]="'i1'"><button kjAccordionTrigger>Section</button><div kjAccordionContent>Content</div></div></div>`;
8
-
9
- describe('KjAccordion', () => {
10
- it('content hidden by default', async () => {
11
- const { container } = await render(template, { imports });
12
- expect(container.querySelector('[kjAccordionContent]')).toHaveAttribute('hidden', '');
13
- });
14
- it('click trigger expands', async () => {
15
- const { container } = await render(template, { imports });
16
- fireEvent.click(container.querySelector('[kjAccordionTrigger]')!);
17
- expect(container.querySelector('[kjAccordionContent]')).not.toHaveAttribute('hidden');
18
- });
19
- it('trigger has aria-expanded=false when closed', async () => {
20
- const { container } = await render(template, { imports });
21
- expect(container.querySelector('[kjAccordionTrigger]')).toHaveAttribute('aria-expanded', 'false');
22
- });
23
- it('trigger has aria-expanded=true when open', async () => {
24
- const { container } = await render(template, { imports });
25
- fireEvent.click(container.querySelector('[kjAccordionTrigger]')!);
26
- expect(container.querySelector('[kjAccordionTrigger]')).toHaveAttribute('aria-expanded', 'true');
27
- });
28
- it('passes axe audit', async () => {
29
- const { container } = await render(template, { imports });
30
- expect(await axe(container)).toHaveNoViolations();
31
- });
32
- });
@@ -1,129 +0,0 @@
1
- import { Directive, computed, inject, input, signal } from '@angular/core';
2
- import { KJ_ACCORDION, KJ_ACCORDION_ITEM } from './accordion.context';
3
-
4
- /**
5
- * Root accordion container. Manages open/close state for accordion items.
6
- * Supports `'single'` (only one item open) or `'multiple'` (any number open).
7
- *
8
- * @example
9
- * ```html
10
- * <div kjAccordion [kjAccordionType]="'multiple'">
11
- * <div kjAccordionItem [kjItemValue]="'item-1'">...</div>
12
- * </div>
13
- * ```
14
- * @category Core/Navigation
15
- */
16
- @Directive({
17
- selector: '[kjAccordion]',
18
- standalone: true,
19
- providers: [{ provide: KJ_ACCORDION, useExisting: KjAccordion }],
20
- })
21
- export class KjAccordion {
22
- /** Whether multiple items can be open simultaneously. Defaults to `'single'`. */
23
- readonly kjAccordionType = input<'single' | 'multiple'>('single');
24
-
25
- private readonly _openIds = signal<ReadonlySet<string>>(new Set());
26
- /** Set of currently open item values. */
27
- readonly openIds = this._openIds.asReadonly();
28
-
29
- /** Toggles an item open or closed. In single mode, closes all others first. */
30
- toggle(id: string): void {
31
- this._openIds.update(ids => {
32
- const next = new Set(ids);
33
- if (next.has(id)) {
34
- next.delete(id);
35
- } else {
36
- if (this.kjAccordionType() === 'single') next.clear();
37
- next.add(id);
38
- }
39
- return next;
40
- });
41
- }
42
-
43
- /** Returns whether the given item value is currently open. */
44
- isOpen(id: string): boolean {
45
- return this._openIds().has(id);
46
- }
47
- }
48
-
49
- /**
50
- * Individual accordion item. Manages its own expanded state via the parent accordion context.
51
- *
52
- * @example
53
- * ```html
54
- * <div kjAccordionItem [kjItemValue]="'section-1'">
55
- * <button kjAccordionTrigger>Section 1</button>
56
- * <div kjAccordionContent>Content</div>
57
- * </div>
58
- * ```
59
- * @category Core/Navigation
60
- */
61
- @Directive({
62
- selector: '[kjAccordionItem]',
63
- standalone: true,
64
- providers: [{ provide: KJ_ACCORDION_ITEM, useExisting: KjAccordionItem }],
65
- })
66
- export class KjAccordionItem {
67
- private readonly accordion = inject(KJ_ACCORDION) as KjAccordion;
68
-
69
- /** The unique value identifying this item within the accordion. */
70
- readonly kjItemValue = input.required<string>();
71
-
72
- /** Whether this item is currently expanded. */
73
- readonly expanded = computed(() => this.accordion.isOpen(this.kjItemValue()));
74
-
75
- /** Toggles this item's expanded state. */
76
- toggle(): void { this.accordion.toggle(this.kjItemValue()); }
77
-
78
- /** Opens this item. */
79
- open(): void { if (!this.expanded()) this.toggle(); }
80
-
81
- /** Closes this item. */
82
- close(): void { if (this.expanded()) this.toggle(); }
83
- }
84
-
85
- /**
86
- * Trigger button for an accordion item. Controls expand/collapse.
87
- * Sets `aria-expanded` and `data-open` attributes based on the item's expanded state.
88
- *
89
- * @example
90
- * ```html
91
- * <button kjAccordionTrigger>Section title</button>
92
- * ```
93
- * @category Core/Navigation
94
- */
95
- @Directive({
96
- selector: '[kjAccordionTrigger]',
97
- standalone: true,
98
- host: {
99
- '[attr.aria-expanded]': 'item.expanded().toString()',
100
- '[attr.data-open]': 'item.expanded() ? "" : null',
101
- '(click)': 'item.toggle()',
102
- },
103
- })
104
- export class KjAccordionTrigger {
105
- /** The parent accordion item context. */
106
- readonly item = inject(KJ_ACCORDION_ITEM);
107
- }
108
-
109
- /**
110
- * Accordion content panel. Hidden when the parent item is collapsed.
111
- * Uses the `hidden` attribute for accessibility-compliant visibility.
112
- *
113
- * @example
114
- * ```html
115
- * <div kjAccordionContent>Panel content</div>
116
- * ```
117
- * @category Core/Navigation
118
- */
119
- @Directive({
120
- selector: '[kjAccordionContent]',
121
- standalone: true,
122
- host: {
123
- '[attr.hidden]': '!item.expanded() ? "" : null',
124
- },
125
- })
126
- export class KjAccordionContent {
127
- /** The parent accordion item context. */
128
- readonly item = inject(KJ_ACCORDION_ITEM);
129
- }
@@ -1,2 +0,0 @@
1
- export { KjAccordion, KjAccordionItem, KjAccordionTrigger, KjAccordionContent } from './accordion';
2
- export { KJ_ACCORDION, KJ_ACCORDION_ITEM } from './accordion.context';
@@ -1,18 +0,0 @@
1
- import { render } from '@testing-library/angular';
2
- import { axe, toHaveNoViolations } from 'jest-axe';
3
- import { KjAvatar, KjAvatarImage, KjAvatarFallback } from './avatar';
4
-
5
- expect.extend(toHaveNoViolations);
6
-
7
- const imports = [KjAvatar, KjAvatarImage, KjAvatarFallback];
8
-
9
- describe('KjAvatar', () => {
10
- it('renders container', async () => {
11
- const { container } = await render(`<span kjAvatar><img kjAvatarImage src="" alt="JD" /><span kjAvatarFallback>JD</span></span>`, { imports });
12
- expect(container.querySelector('[kjAvatar]')).toBeInTheDocument();
13
- });
14
- it('passes axe audit', async () => {
15
- const { container } = await render(`<span kjAvatar><img kjAvatarImage src="/p.jpg" alt="Jane Doe" /><span kjAvatarFallback aria-hidden="true">JD</span></span>`, { imports });
16
- expect(await axe(container)).toHaveNoViolations();
17
- });
18
- });
@@ -1,26 +0,0 @@
1
- import { Directive, InjectionToken, inject, signal } from '@angular/core';
2
-
3
- export interface KjAvatarContext { imageLoaded: ReturnType<typeof signal<boolean>>; }
4
- export const KJ_AVATAR = new InjectionToken<KjAvatarContext>('KjAvatar');
5
-
6
- /**
7
- * Container for avatar. Tracks image load state.
8
- * @example `<span kjAvatar><img kjAvatarImage src="..." alt="..." /><span kjAvatarFallback>JD</span></span>`
9
- * @category Core/Display
10
- */
11
- @Directive({ selector: '[kjAvatar]', standalone: true, providers: [{ provide: KJ_AVATAR, useExisting: KjAvatar }] })
12
- export class KjAvatar implements KjAvatarContext { readonly imageLoaded = signal(false); }
13
-
14
- /**
15
- * Image element within a `[kjAvatar]` container.
16
- * @category Core/Display
17
- */
18
- @Directive({ selector: '[kjAvatarImage]', standalone: true, host: { '(load)': 'ctx.imageLoaded.set(true)', '(error)': 'ctx.imageLoaded.set(false)', '[attr.data-loaded]': 'ctx.imageLoaded() ? "" : null' } })
19
- export class KjAvatarImage { readonly ctx = inject(KJ_AVATAR); }
20
-
21
- /**
22
- * Fallback shown when image is unavailable.
23
- * @category Core/Display
24
- */
25
- @Directive({ selector: '[kjAvatarFallback]', standalone: true, host: { '[attr.data-visible]': '!ctx.imageLoaded() ? "" : null' } })
26
- export class KjAvatarFallback { readonly ctx = inject(KJ_AVATAR); }
@@ -1 +0,0 @@
1
- export { KjAvatar, KjAvatarImage, KjAvatarFallback, KJ_AVATAR } from './avatar';
@@ -1,20 +0,0 @@
1
- import { render } from '@testing-library/angular';
2
- import { axe, toHaveNoViolations } from 'jest-axe';
3
- import { KjBadge } from './badge';
4
-
5
- expect.extend(toHaveNoViolations);
6
-
7
- describe('KjBadge', () => {
8
- it('sets data-variant attribute', async () => {
9
- const { container } = await render(`<span kjBadge [kjBadgeVariant]="'destructive'">New</span>`, { imports: [KjBadge] });
10
- expect(container.querySelector('span')).toHaveAttribute('data-variant', 'destructive');
11
- });
12
- it('defaults to default variant', async () => {
13
- const { container } = await render(`<span kjBadge>Beta</span>`, { imports: [KjBadge] });
14
- expect(container.querySelector('span')).toHaveAttribute('data-variant', 'default');
15
- });
16
- it('passes axe audit', async () => {
17
- const { container } = await render(`<span kjBadge>Beta</span>`, { imports: [KjBadge] });
18
- expect(await axe(container)).toHaveNoViolations();
19
- });
20
- });
@@ -1,14 +0,0 @@
1
- import { Directive, input } from '@angular/core';
2
-
3
- export type KjBadgeVariant = 'default' | 'secondary' | 'destructive' | 'outline';
4
-
5
- /**
6
- * Marks an element as a badge via data attributes.
7
- * @example `<span kjBadge [kjBadgeVariant]="'destructive'">Critical</span>`
8
- * @category Core/Display
9
- */
10
- @Directive({ selector: '[kjBadge]', standalone: true, host: { '[attr.data-variant]': 'kjBadgeVariant()' } })
11
- export class KjBadge {
12
- /** The visual variant of the badge. Defaults to `'default'`. */
13
- kjBadgeVariant = input<KjBadgeVariant>('default');
14
- }
@@ -1 +0,0 @@
1
- export { KjBadge, type KjBadgeVariant } from './badge';
@@ -1,30 +0,0 @@
1
- import { Component } from '@angular/core';
2
- import { KjButton } from './button';
3
-
4
- @Component({
5
- selector: 'kj-example-button',
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; gap: 1rem; flex-wrap: wrap; align-items: center; }
12
- 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); }
13
- [data-variant="default"] { background: var(--kj-accent); color: var(--kj-accent-on); }
14
- [data-variant="destructive"] { background: var(--kj-destructive); color: #fff; }
15
- [data-variant="link"] { background: transparent; color: var(--kj-accent); border: none; padding: 0.5rem 0.5rem; text-decoration: none; text-underline-offset: 4px; }
16
- [data-variant="link"]:hover { text-decoration: underline; }
17
- [data-variant="ghost"] { background: transparent; color: var(--kj-btn-ghost-color); }
18
- [aria-disabled="true"] { opacity: 0.4; cursor: not-allowed; }
19
- `],
20
- template: `
21
- <div class="row">
22
- <button kjButton [kjVariant]="'default'">Default</button>
23
- <button kjButton [kjVariant]="'destructive'">Destructive</button>
24
- <button kjButton [kjVariant]="'link'">Link</button>
25
- <button kjButton [kjVariant]="'ghost'">Ghost</button>
26
- <button kjButton [kjDisabled]="true">Disabled</button>
27
- </div>
28
- `,
29
- })
30
- export class ButtonExample {}
@@ -1,37 +0,0 @@
1
- import { Component } from '@angular/core';
2
- import { KjButton } from './button';
3
-
4
- @Component({
5
- selector: 'kj-example-button-finance',
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: 4px 15px; font-family: var(--kj-font); font-size: 14px; font-weight: 400;
14
- border: 1px solid transparent; border-radius: 6px; cursor: pointer;
15
- transition: var(--kj-transition); line-height: 1.5;
16
- }
17
- button[kjButton][data-variant="default"] { background: var(--kj-accent); color: var(--kj-accent-on); border-color: var(--kj-accent); }
18
- button[kjButton][data-variant="default"]:hover { background: #4096ff; border-color: #4096ff; }
19
- button[kjButton][data-variant="destructive"] { background: var(--kj-destructive); color: #fff; border-color: var(--kj-destructive); }
20
- button[kjButton][data-variant="link"] { background: transparent; color: var(--kj-accent); border-color: transparent; }
21
- button[kjButton][data-variant="link"]:hover { color: #4096ff; }
22
- button[kjButton][data-variant="ghost"] { background: transparent; color: var(--kj-text); border: 1px solid var(--kj-text); }
23
- button[kjButton][data-variant="ghost"]:hover { color: var(--kj-accent); border-color: var(--kj-accent); }
24
- [aria-disabled="true"] { opacity: 0.45; cursor: not-allowed; }
25
- `],
26
- host: { class: 'kj-theme-finance' },
27
- template: `
28
- <div class="row">
29
- <button kjButton [kjVariant]="'default'">Default</button>
30
- <button kjButton [kjVariant]="'destructive'">Destructive</button>
31
- <button kjButton [kjVariant]="'link'">Link</button>
32
- <button kjButton [kjVariant]="'ghost'">Ghost</button>
33
- <button kjButton [kjDisabled]="true">Disabled</button>
34
- </div>
35
- `,
36
- })
37
- export class ButtonFinanceExample {}