Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
382 changes: 382 additions & 0 deletions docs/adr/001-component-architecture.md

Large diffs are not rendered by default.

946 changes: 723 additions & 223 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,22 +55,25 @@
},
"devDependencies": {
"@stencil-community/postcss": "^2.2.0",
"@stencil/core": "^4.27.1",
"@stencil/core": "^4.42.1",
"@stencil/eslint-plugin": "^1.1.0",
"@types/node": "^25.1.0",
"@typescript-eslint/eslint-plugin": "^8.54.0",
"@typescript-eslint/parser": "^8.54.0",
"eslint": "^9.39.2",
"eslint-plugin-react": "^7.37.5",
"happy-dom": "^20.4.0",
"jsdom": "^28.0.0",
"postcss": "^8.5.6",
"postcss-custom-media": "^12.0.0",
"postcss-nesting": "^14.0.0",
"rimraf": "^6.1.2",
"stylelint": "^17.1.0",
"stylelint-config-standard": "^40.0.0",
"unplugin-stencil": "^0.4.1",
"vitest": "^4.0.18"
"vite-tsconfig-paths": "^6.1.0",
"vitest": "^4.0.18",
"vitest-axe": "^0.1.0"
},
"dependencies": {
"@ankh-studio/themes": "^0.1.3",
Expand Down
28 changes: 22 additions & 6 deletions src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,19 +127,35 @@ declare namespace LocalJSX {
*/
"disabled"?: boolean;
}

interface AnkhButtonAttributes {
"variant": ButtonVariant;
"size": ButtonSize;
"fullWidth": boolean;
"disabled": boolean;
"type": 'button' | 'submit' | 'reset';
}
interface AnkhFocusRingAttributes {
"visible": boolean;
"inward": boolean;
}
interface AnkhRippleAttributes {
"disabled": boolean;
}

interface IntrinsicElements {
"ankh-button": AnkhButton;
"ankh-focus-ring": AnkhFocusRing;
"ankh-ripple": AnkhRipple;
"ankh-button": Omit<AnkhButton, keyof AnkhButtonAttributes> & { [K in keyof AnkhButton & keyof AnkhButtonAttributes]?: AnkhButton[K] } & { [K in keyof AnkhButton & keyof AnkhButtonAttributes as `attr:${K}`]?: AnkhButtonAttributes[K] } & { [K in keyof AnkhButton & keyof AnkhButtonAttributes as `prop:${K}`]?: AnkhButton[K] };
"ankh-focus-ring": Omit<AnkhFocusRing, keyof AnkhFocusRingAttributes> & { [K in keyof AnkhFocusRing & keyof AnkhFocusRingAttributes]?: AnkhFocusRing[K] } & { [K in keyof AnkhFocusRing & keyof AnkhFocusRingAttributes as `attr:${K}`]?: AnkhFocusRingAttributes[K] } & { [K in keyof AnkhFocusRing & keyof AnkhFocusRingAttributes as `prop:${K}`]?: AnkhFocusRing[K] };
"ankh-ripple": Omit<AnkhRipple, keyof AnkhRippleAttributes> & { [K in keyof AnkhRipple & keyof AnkhRippleAttributes]?: AnkhRipple[K] } & { [K in keyof AnkhRipple & keyof AnkhRippleAttributes as `attr:${K}`]?: AnkhRippleAttributes[K] } & { [K in keyof AnkhRipple & keyof AnkhRippleAttributes as `prop:${K}`]?: AnkhRipple[K] };
}
}
export { LocalJSX as JSX };
declare module "@stencil/core" {
export namespace JSX {
interface IntrinsicElements {
"ankh-button": LocalJSX.AnkhButton & JSXBase.HTMLAttributes<HTMLAnkhButtonElement>;
"ankh-focus-ring": LocalJSX.AnkhFocusRing & JSXBase.HTMLAttributes<HTMLAnkhFocusRingElement>;
"ankh-ripple": LocalJSX.AnkhRipple & JSXBase.HTMLAttributes<HTMLAnkhRippleElement>;
"ankh-button": LocalJSX.IntrinsicElements["ankh-button"] & JSXBase.HTMLAttributes<HTMLAnkhButtonElement>;
"ankh-focus-ring": LocalJSX.IntrinsicElements["ankh-focus-ring"] & JSXBase.HTMLAttributes<HTMLAnkhFocusRingElement>;
"ankh-ripple": LocalJSX.IntrinsicElements["ankh-ripple"] & JSXBase.HTMLAttributes<HTMLAnkhRippleElement>;
}
}
}
47 changes: 47 additions & 0 deletions src/components/ankh-button/ankh-button.a11y.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// @vitest-environment jsdom
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { axe } from 'vitest-axe';
import '@/test-utils/a11y';
import { createElement, createContainer } from '@/test-utils';
import './ankh-button.js';

describe('ankh-button accessibility', () => {
let container: HTMLDivElement;
let cleanup: () => void;

const createButton = async (attrs: Record<string, string> = {}, content?: string) => {
const el = await createElement<HTMLElement>('ankh-button', container, attrs, content);
const button = el.querySelector('button');
if (!button) throw new Error('button element not found');
return { el, button };
};

beforeEach(() => {
({ container, cleanup } = createContainer());
});

afterEach(() => {
cleanup();
});

it('has no axe violations with default props', async () => {
const { el } = await createButton({}, 'Click me');
const results = await axe(el);
expect(results).toHaveNoViolations();
});

it('has no axe violations when disabled', async () => {
const { el } = await createButton({ disabled: 'true' }, 'Disabled');
const results = await axe(el);
expect(results).toHaveNoViolations();
});

it.each(['filled', 'outlined', 'text', 'elevated', 'tonal'] as const)(
'has no axe violations for variant="%s"',
async (variant) => {
const { el } = await createButton({ variant }, 'Click me');
const results = await axe(el);
expect(results).toHaveNoViolations();
}
);
});
Loading