@torpor/ui 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE.md +5 -0
  2. package/README.md +16 -0
  3. package/dist/Accordion/Accordion.torp +208 -0
  4. package/dist/Accordion/AccordionContent.torp +36 -0
  5. package/dist/Accordion/AccordionHeader.torp +33 -0
  6. package/dist/Accordion/AccordionItem.torp +75 -0
  7. package/dist/Accordion/AccordionTrigger.torp +59 -0
  8. package/dist/Accordion/AccordionTypes.ts +32 -0
  9. package/dist/Accordion/index.cjs +53 -0
  10. package/dist/Accordion/index.cjs.map +1 -0
  11. package/dist/Accordion/index.js +22 -0
  12. package/dist/Accordion/index.js.map +1 -0
  13. package/dist/metafile-cjs.json +1 -0
  14. package/dist/metafile-esm.json +1 -0
  15. package/dist/motion.cjs +34 -0
  16. package/dist/motion.cjs.map +1 -0
  17. package/dist/motion.d.cts +5 -0
  18. package/dist/motion.d.ts +5 -0
  19. package/dist/motion.js +7 -0
  20. package/dist/motion.js.map +1 -0
  21. package/dist/utils/getId.ts +6 -0
  22. package/package.json +52 -0
  23. package/scripts/postbuild.ts +62 -0
  24. package/src/Accordion/Accordion.torp +208 -0
  25. package/src/Accordion/AccordionContent.torp +36 -0
  26. package/src/Accordion/AccordionHeader.torp +33 -0
  27. package/src/Accordion/AccordionItem.torp +75 -0
  28. package/src/Accordion/AccordionTrigger.torp +59 -0
  29. package/src/Accordion/AccordionTypes.ts +32 -0
  30. package/src/Accordion/index.ts +7 -0
  31. package/src/Accordion.ts +7 -0
  32. package/src/motion/fade.ts +2 -0
  33. package/src/motion.ts +3 -0
  34. package/src/utils/getId.ts +6 -0
  35. package/test/Accordion/accessibility.test.ts +62 -0
  36. package/test/Accordion/components/AccordionAccessibility.torp +44 -0
  37. package/test/Accordion/components/AccordionKeyboard.torp +32 -0
  38. package/test/Accordion/components/AccordionList.torp +23 -0
  39. package/test/Accordion/components/AccordionMultiple.torp +32 -0
  40. package/test/Accordion/components/AccordionSingle.torp +32 -0
  41. package/test/Accordion/for-items.test.ts +54 -0
  42. package/test/Accordion/keyboard.test.ts +67 -0
  43. package/test/Accordion/multiple-item-toggle.test.ts +34 -0
  44. package/test/Accordion/multiple-item-value.test.ts +17 -0
  45. package/test/Accordion/single-item-toggle.test.ts +33 -0
  46. package/test/Accordion/single-item-value.test.ts +17 -0
  47. package/tsconfig.json +4 -0
  48. package/tsup.config.ts +14 -0
  49. package/vitest.config.ts +12 -0
@@ -0,0 +1,44 @@
1
+ import {
2
+ Accordion,
3
+ AccordionItem,
4
+ AccordionHeader,
5
+ AccordionTrigger,
6
+ AccordionContent,
7
+ } from "../../../src/Accordion/index";
8
+
9
+ export default function AccordionAccessibility() {
10
+ @render {
11
+ <Accordion value={$props.value} type="multiple">
12
+ <AccordionItem>
13
+ <AccordionHeader as="h4">
14
+ <AccordionTrigger>Role Header</AccordionTrigger>
15
+ </AccordionHeader>
16
+ <AccordionContent>Role Content</AccordionContent>
17
+ </AccordionItem>
18
+ <AccordionItem>
19
+ <AccordionHeader as="h4">
20
+ <AccordionTrigger>Open Header</AccordionTrigger>
21
+ </AccordionHeader>
22
+ <AccordionContent>Open Content</AccordionContent>
23
+ </AccordionItem>
24
+ <AccordionItem>
25
+ <AccordionHeader as="h4">
26
+ <AccordionTrigger>Closed Header</AccordionTrigger>
27
+ </AccordionHeader>
28
+ <AccordionContent>Closed Content</AccordionContent>
29
+ </AccordionItem>
30
+ <AccordionItem>
31
+ <AccordionHeader as="h4">
32
+ <AccordionTrigger>Region Header</AccordionTrigger>
33
+ </AccordionHeader>
34
+ <AccordionContent ariaRegion>Region Content</AccordionContent>
35
+ </AccordionItem>
36
+ <AccordionItem disabled>
37
+ <AccordionHeader as="h4">
38
+ <AccordionTrigger>Disabled Header</AccordionTrigger>
39
+ </AccordionHeader>
40
+ <AccordionContent>Disabled Content</AccordionContent>
41
+ </AccordionItem>
42
+ </Accordion>
43
+ }
44
+ }
@@ -0,0 +1,32 @@
1
+ import {
2
+ Accordion,
3
+ AccordionItem,
4
+ AccordionHeader,
5
+ AccordionTrigger,
6
+ AccordionContent,
7
+ } from "../../../src/Accordion/index";
8
+
9
+ export default function AccordionKeyboard() {
10
+ @render {
11
+ <Accordion value={$props.value} type="multiple">
12
+ <AccordionItem>
13
+ <AccordionHeader as="h4">
14
+ <AccordionTrigger>Header 1</AccordionTrigger>
15
+ </AccordionHeader>
16
+ <AccordionContent>Content 1</AccordionContent>
17
+ </AccordionItem>
18
+ <AccordionItem>
19
+ <AccordionHeader as="h4">
20
+ <AccordionTrigger>Header 2</AccordionTrigger>
21
+ </AccordionHeader>
22
+ <AccordionContent>Content 2</AccordionContent>
23
+ </AccordionItem>
24
+ <AccordionItem>
25
+ <AccordionHeader as="h4">
26
+ <AccordionTrigger>Header 3</AccordionTrigger>
27
+ </AccordionHeader>
28
+ <AccordionContent>Content 3</AccordionContent>
29
+ </AccordionItem>
30
+ </Accordion>
31
+ }
32
+ }
@@ -0,0 +1,23 @@
1
+ import {
2
+ Accordion,
3
+ AccordionItem,
4
+ AccordionHeader,
5
+ AccordionTrigger,
6
+ AccordionContent,
7
+ } from "../../../src/Accordion/index";
8
+
9
+ export default function AccordionList() {
10
+ @render {
11
+ <Accordion value={$props.value}>
12
+ @for (let item of $props.items) {
13
+ @key = item.id
14
+ <AccordionItem>
15
+ <AccordionHeader as="h4">
16
+ <AccordionTrigger>{item.header}</AccordionTrigger>
17
+ </AccordionHeader>
18
+ <AccordionContent>{item.text}</AccordionContent>
19
+ </AccordionItem>
20
+ }
21
+ </Accordion>
22
+ }
23
+ }
@@ -0,0 +1,32 @@
1
+ import {
2
+ Accordion,
3
+ AccordionItem,
4
+ AccordionHeader,
5
+ AccordionTrigger,
6
+ AccordionContent,
7
+ } from "../../../src/Accordion/index";
8
+
9
+ export default function AccordionMultiple() {
10
+ @render {
11
+ <Accordion value={$props.value} type="multiple">
12
+ <AccordionItem>
13
+ <AccordionHeader as="h4">
14
+ <AccordionTrigger>Header 1</AccordionTrigger>
15
+ </AccordionHeader>
16
+ <AccordionContent>Content 1</AccordionContent>
17
+ </AccordionItem>
18
+ <AccordionItem>
19
+ <AccordionHeader as="h4">
20
+ <AccordionTrigger>Header 2</AccordionTrigger>
21
+ </AccordionHeader>
22
+ <AccordionContent>Content 2</AccordionContent>
23
+ </AccordionItem>
24
+ <AccordionItem>
25
+ <AccordionHeader as="h4">
26
+ <AccordionTrigger>Header 3</AccordionTrigger>
27
+ </AccordionHeader>
28
+ <AccordionContent>Content 3</AccordionContent>
29
+ </AccordionItem>
30
+ </Accordion>
31
+ }
32
+ }
@@ -0,0 +1,32 @@
1
+ import {
2
+ Accordion,
3
+ AccordionItem,
4
+ AccordionHeader,
5
+ AccordionTrigger,
6
+ AccordionContent,
7
+ } from "../../../src/Accordion/index";
8
+
9
+ export default function AccordionSingle() {
10
+ @render {
11
+ <Accordion value={$props.value}>
12
+ <AccordionItem>
13
+ <AccordionHeader as="h4">
14
+ <AccordionTrigger>Header 1</AccordionTrigger>
15
+ </AccordionHeader>
16
+ <AccordionContent>Content 1</AccordionContent>
17
+ </AccordionItem>
18
+ <AccordionItem>
19
+ <AccordionHeader as="h4">
20
+ <AccordionTrigger>Header 2</AccordionTrigger>
21
+ </AccordionHeader>
22
+ <AccordionContent>Content 2</AccordionContent>
23
+ </AccordionItem>
24
+ <AccordionItem>
25
+ <AccordionHeader as="h4">
26
+ <AccordionTrigger>Header 3</AccordionTrigger>
27
+ </AccordionHeader>
28
+ <AccordionContent>Content 3</AccordionContent>
29
+ </AccordionItem>
30
+ </Accordion>
31
+ }
32
+ }
@@ -0,0 +1,54 @@
1
+ import { getByText, queryByText } from "@testing-library/dom";
2
+ import "@testing-library/jest-dom/vitest";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { $watch, mount } from "@torpor/view";
5
+ import { describe, expect, it } from "vitest";
6
+ import AccordionList from "./components/AccordionList.torp";
7
+
8
+ describe("Accordion", () => {
9
+ it("Items in a for loop", async () => {
10
+ const user = userEvent.setup();
11
+
12
+ let $state = $watch({
13
+ value: "1",
14
+ items: [
15
+ { id: 0, header: "Item 1", text: "Content 1" },
16
+ { id: 1, header: "Item 2", text: "Content 2" },
17
+ { id: 2, header: "Item 3", text: "Content 3" },
18
+ ],
19
+ });
20
+
21
+ const container = document.createElement("div");
22
+ document.body.appendChild(container);
23
+ mount(container, AccordionList, $state);
24
+
25
+ expect(queryByText(container, "Content 1")).not.toBeInTheDocument();
26
+ expect(queryByText(container, "Item 2")).toBeInTheDocument();
27
+ expect(queryByText(container, "Content 2")).toBeInTheDocument();
28
+ expect(queryByText(container, "Content 3")).not.toBeInTheDocument();
29
+
30
+ $state.items.splice(1, 1);
31
+
32
+ expect(queryByText(container, "Content 1")).not.toBeInTheDocument();
33
+ expect(queryByText(container, "Item 2")).not.toBeInTheDocument();
34
+ expect(queryByText(container, "Content 2")).not.toBeInTheDocument();
35
+ expect(queryByText(container, "Content 3")).not.toBeInTheDocument();
36
+
37
+ // Clicking header 1 should reveal body 1
38
+ await user.click(getByText(container, "Item 1"));
39
+ expect(queryByText(container, "Content 1")).toBeInTheDocument();
40
+
41
+ $state.items.push({ id: 3, header: "Item 4", text: "Content 4" });
42
+
43
+ expect(queryByText(container, "Content 1")).toBeInTheDocument();
44
+ expect(queryByText(container, "Content 2")).not.toBeInTheDocument();
45
+ expect(queryByText(container, "Content 3")).not.toBeInTheDocument();
46
+ expect(queryByText(container, "Item 4")).toBeInTheDocument();
47
+ expect(queryByText(container, "Content 4")).not.toBeInTheDocument();
48
+
49
+ // Clicking header 4 should reveal body 4
50
+ await user.click(getByText(container, "Item 4"));
51
+ expect(queryByText(container, "Content 1")).not.toBeInTheDocument();
52
+ expect(queryByText(container, "Content 4")).toBeInTheDocument();
53
+ });
54
+ });
@@ -0,0 +1,67 @@
1
+ import { fireEvent, getByText, queryByText } from "@testing-library/dom";
2
+ import "@testing-library/jest-dom/vitest";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { mount } from "@torpor/view";
5
+ import { describe, expect, it } from "vitest";
6
+ import AccordionKeyboard from "./components/AccordionKeyboard.torp";
7
+
8
+ describe("Accordion", () => {
9
+ it("Keyboard", async () => {
10
+ const user = userEvent.setup();
11
+
12
+ const container = document.createElement("div");
13
+ document.body.appendChild(container);
14
+ mount(container, AccordionKeyboard, { value: ["1", "2"] });
15
+
16
+ // Tests from https://www.w3.org/WAI/ARIA/apg/patterns/accordion/
17
+
18
+ // Enter or Space:
19
+ // * When focus is on the accordion header for a collapsed panel, expands the associated panel.
20
+ // If the implementation allows only one panel to be expanded, and if another panel is expanded,
21
+ // collapses that panel
22
+ // * When focus is on the accordion header for an expanded panel, collapses the panel if the
23
+ // implementation supports collapsing. Some implementations require one panel to be expanded
24
+ // at all times and allow only one panel to be expanded; so, they do not support a collapse
25
+ // function
26
+ // NOTE: I don't think we can test this
27
+ // NOTE: Accordion headers are buttons so this functionality should be baked in by default
28
+ //screen.queryByText('Header 1').focus();
29
+ //expect(screen.queryByText('Content 1')).not.toBeInTheDocument();
30
+ //fireEvent(screen.getByText('Header 1'), { key: 'Space' });
31
+ //expect(screen.queryByText('Content 1')).toBeInTheDocument();
32
+ //fireEvent(screen.getByText('Header 1'), { key: 'Space' });
33
+ //expect(screen.queryByText('Content 1')).not.toBeInTheDocument();
34
+
35
+ // Tab: Moves focus to the next focusable element; all focusable elements in the accordion are
36
+ // included in the page Tab sequence
37
+ // NOTE: I don't think we can test this
38
+ //fireEvent(screen.getByText('Header 1'), { key: 'Tab' });
39
+ //expect(document.activeElement).toBe(screen.queryByText('Header 2'));
40
+
41
+ // Shift + Tab: Moves focus to the previous focusable element; all focusable elements in the
42
+ // accordion are included in the page Tab sequence
43
+ // NOTE: I don't think we can test this
44
+ //fireEvent(screen.getByText('Header 2'), { key: 'Tab', shiftKey: true });
45
+ //expect(document.activeElement).toBe(screen.queryByText('Header 1'));
46
+
47
+ // Down Arrow: If focus is on an accordion header, moves focus to the next accordion header.
48
+ // If focus is on the last accordion header, either does nothing or moves focus to the first
49
+ // accordion header
50
+ fireEvent(getByText(container, "Header 1"), new KeyboardEvent("keydown", { key: "ArrowDown" }));
51
+ expect(document.activeElement).toBe(queryByText(container, "Header 2"));
52
+
53
+ // Up Arrow: If focus is on an accordion header, moves focus to the previous accordion header.
54
+ // If focus is on the first accordion header, either does nothing or moves focus to the last
55
+ // accordion header
56
+ fireEvent(getByText(container, "Header 2"), new KeyboardEvent("keydown", { key: "ArrowUp" }));
57
+ expect(document.activeElement).toBe(queryByText(container, "Header 1"));
58
+
59
+ // Home: When focus is on an accordion header, moves focus to the first accordion header
60
+ fireEvent(getByText(container, "Header 3"), new KeyboardEvent("keydown", { key: "Home" }));
61
+ expect(document.activeElement).toBe(queryByText(container, "Header 1"));
62
+
63
+ // End: When focus is on an accordion header, moves focus to the last accordion header
64
+ fireEvent(getByText(container, "Header 1"), new KeyboardEvent("keydown", { key: "End" }));
65
+ expect(document.activeElement).toBe(queryByText(container, "Header 3"));
66
+ });
67
+ });
@@ -0,0 +1,34 @@
1
+ import { getByText, queryByText } from "@testing-library/dom";
2
+ import "@testing-library/jest-dom/vitest";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { mount } from "@torpor/view";
5
+ import { describe, expect, it } from "vitest";
6
+ import AccordionMultiple from "./components/AccordionMultiple.torp";
7
+
8
+ describe("Accordion", () => {
9
+ it("Multiple item toggle", async () => {
10
+ const user = userEvent.setup();
11
+
12
+ const container = document.createElement("div");
13
+ document.body.appendChild(container);
14
+ mount(container, AccordionMultiple);
15
+
16
+ expect(queryByText(container, "Content 1")).not.toBeInTheDocument();
17
+ expect(queryByText(container, "Content 2")).not.toBeInTheDocument();
18
+ expect(queryByText(container, "Content 3")).not.toBeInTheDocument();
19
+
20
+ // Clicking header 1 should reveal body 1
21
+ await user.click(getByText(container, "Header 1"));
22
+ expect(queryByText(container, "Content 1")).toBeInTheDocument();
23
+
24
+ // Clicking header 2 should reveal body 1 and body 2
25
+ await user.click(getByText(container, "Header 2"));
26
+ expect(queryByText(container, "Content 1")).toBeInTheDocument();
27
+ expect(queryByText(container, "Content 2")).toBeInTheDocument();
28
+
29
+ // Clicking header 2 again should hide body 2
30
+ await user.click(getByText(container, "Header 2"));
31
+ expect(queryByText(container, "Content 1")).toBeInTheDocument();
32
+ expect(queryByText(container, "Content 2")).not.toBeInTheDocument();
33
+ });
34
+ });
@@ -0,0 +1,17 @@
1
+ import { queryByText } from "@testing-library/dom";
2
+ import "@testing-library/jest-dom/vitest";
3
+ import { mount } from "@torpor/view";
4
+ import { describe, expect, it } from "vitest";
5
+ import AccordionMultiple from "./components/AccordionMultiple.torp";
6
+
7
+ describe("Accordion", () => {
8
+ it("Multiple item default value", async () => {
9
+ const container = document.createElement("div");
10
+ document.body.appendChild(container);
11
+ mount(container, AccordionMultiple, { value: "1" });
12
+
13
+ expect(queryByText(container, "Content 1")).not.toBeInTheDocument();
14
+ expect(queryByText(container, "Content 2")).toBeInTheDocument();
15
+ expect(queryByText(container, "Content 3")).not.toBeInTheDocument();
16
+ });
17
+ });
@@ -0,0 +1,33 @@
1
+ import { getByText, queryByText } from "@testing-library/dom";
2
+ import "@testing-library/jest-dom/vitest";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { mount } from "@torpor/view";
5
+ import { describe, expect, it } from "vitest";
6
+ import AccordionSingle from "./components/AccordionSingle.torp";
7
+
8
+ describe("Accordion", () => {
9
+ it("Single item toggle", async () => {
10
+ const user = userEvent.setup();
11
+
12
+ const container = document.createElement("div");
13
+ document.body.appendChild(container);
14
+ mount(container, AccordionSingle);
15
+
16
+ expect(queryByText(container, "Content 1")).not.toBeInTheDocument();
17
+ expect(queryByText(container, "Content 2")).not.toBeInTheDocument();
18
+ expect(queryByText(container, "Content 3")).not.toBeInTheDocument();
19
+
20
+ // Clicking header 1 should reveal body 1
21
+ await user.click(getByText(container, "Header 1"));
22
+ expect(queryByText(container, "Content 1")).toBeInTheDocument();
23
+
24
+ // Clicking header 2 should hide body 1 and reveal body 2
25
+ await user.click(getByText(container, "Header 2"));
26
+ expect(queryByText(container, "Content 1")).not.toBeInTheDocument();
27
+ expect(queryByText(container, "Content 2")).toBeInTheDocument();
28
+
29
+ // Clicking header 2 again should hide body 2
30
+ await user.click(getByText(container, "Header 2"));
31
+ expect(queryByText(container, "Content 2")).not.toBeInTheDocument();
32
+ });
33
+ });
@@ -0,0 +1,17 @@
1
+ import { queryByText } from "@testing-library/dom";
2
+ import "@testing-library/jest-dom/vitest";
3
+ import { mount } from "@torpor/view";
4
+ import { describe, expect, it } from "vitest";
5
+ import AccordionSingle from "./components/AccordionSingle.torp";
6
+
7
+ describe("Accordion", () => {
8
+ it("Single item default value", async () => {
9
+ const container = document.createElement("div");
10
+ document.body.appendChild(container);
11
+ mount(container, AccordionSingle, { value: "1" });
12
+
13
+ expect(queryByText(container, "Content 1")).not.toBeInTheDocument();
14
+ expect(queryByText(container, "Content 2")).toBeInTheDocument();
15
+ expect(queryByText(container, "Content 3")).not.toBeInTheDocument();
16
+ });
17
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "../tsconfig",
3
+ "include": ["tsup.config.ts"]
4
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ export default defineConfig({
4
+ entry: ["src/Accordion/index.ts", "src/motion.ts"],
5
+ format: ["esm", "cjs"],
6
+ dts: { entry: ["src/motion.ts"] },
7
+ clean: true,
8
+ metafile: true,
9
+ sourcemap: true,
10
+ loader: {
11
+ ".torp": "file",
12
+ },
13
+ onSuccess: "npm run build:fix",
14
+ });
@@ -0,0 +1,12 @@
1
+ import torpor from "@torpor/unplugin/vite";
2
+ import { defineConfig } from "vitest/config";
3
+
4
+ export default defineConfig(({ mode }) => ({
5
+ plugins: [torpor()],
6
+ resolve: {
7
+ conditions: mode === "test" ? ["browser"] : [],
8
+ },
9
+ test: {
10
+ environment: "jsdom",
11
+ },
12
+ }));