@cfasim-ui/components 0.1.9 → 0.2.0

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 (76) hide show
  1. package/dist/Box/Box.d.ts +24 -0
  2. package/dist/Box/Box.spec.d.ts +1 -0
  3. package/dist/Box/Box.test.d.ts +1 -0
  4. package/dist/Button/Button.d.ts +29 -0
  5. package/dist/Button/Button.spec.d.ts +1 -0
  6. package/dist/Button/Button.test.d.ts +1 -0
  7. package/dist/Expander/Expander.d.ts +28 -0
  8. package/dist/Expander/Expander.spec.d.ts +1 -0
  9. package/dist/Hint/Hint.d.ts +5 -0
  10. package/dist/Hint/Hint.spec.d.ts +1 -0
  11. package/dist/Hint/Hint.test.d.ts +1 -0
  12. package/dist/Icon/Icon.d.ts +18 -0
  13. package/dist/Icon/Icon.spec.d.ts +1 -0
  14. package/dist/LightDarkToggle/LightDarkToggle.d.ts +2 -0
  15. package/dist/NumberInput/NumberInput.d.ts +21 -0
  16. package/dist/NumberInput/NumberInput.spec.d.ts +1 -0
  17. package/dist/NumberInput/NumberInput.test.d.ts +1 -0
  18. package/dist/SelectBox/SelectBox.d.ts +19 -0
  19. package/dist/SelectBox/SelectBox.spec.d.ts +1 -0
  20. package/dist/SelectBox/SelectBox.test.d.ts +1 -0
  21. package/dist/SidebarLayout/SidebarLayout.d.ts +37 -0
  22. package/dist/SidebarLayout/SidebarLayout.test.d.ts +1 -0
  23. package/dist/Spinner/Spinner.d.ts +9 -0
  24. package/dist/Spinner/Spinner.spec.d.ts +1 -0
  25. package/dist/TextInput/TextInput.d.ts +14 -0
  26. package/dist/TextInput/TextInput.spec.d.ts +1 -0
  27. package/dist/TextInput/TextInput.test.d.ts +1 -0
  28. package/dist/Toggle/Toggle.d.ts +14 -0
  29. package/dist/Toggle/Toggle.spec.d.ts +1 -0
  30. package/dist/Toggle/Toggle.test.d.ts +1 -0
  31. package/dist/index.css +2 -0
  32. package/dist/index.d.ts +15 -0
  33. package/dist/index.js +700 -0
  34. package/package.json +17 -3
  35. package/src/Box/Box.md +0 -41
  36. package/src/Box/Box.spec.ts +0 -13
  37. package/src/Box/Box.test.ts +0 -49
  38. package/src/Box/Box.vue +0 -52
  39. package/src/Button/Button.md +0 -55
  40. package/src/Button/Button.spec.ts +0 -18
  41. package/src/Button/Button.test.ts +0 -36
  42. package/src/Button/Button.vue +0 -81
  43. package/src/Expander/Expander.md +0 -23
  44. package/src/Expander/Expander.spec.ts +0 -14
  45. package/src/Expander/Expander.vue +0 -95
  46. package/src/Hint/Hint.md +0 -24
  47. package/src/Hint/Hint.spec.ts +0 -12
  48. package/src/Hint/Hint.test.ts +0 -34
  49. package/src/Hint/Hint.vue +0 -83
  50. package/src/Icon/Icon.md +0 -55
  51. package/src/Icon/Icon.spec.ts +0 -9
  52. package/src/Icon/Icon.vue +0 -112
  53. package/src/LightDarkToggle/LightDarkToggle.vue +0 -49
  54. package/src/NumberInput/NumberInput.md +0 -187
  55. package/src/NumberInput/NumberInput.spec.ts +0 -10
  56. package/src/NumberInput/NumberInput.test.ts +0 -580
  57. package/src/NumberInput/NumberInput.vue +0 -446
  58. package/src/SelectBox/SelectBox.md +0 -56
  59. package/src/SelectBox/SelectBox.spec.ts +0 -9
  60. package/src/SelectBox/SelectBox.test.ts +0 -42
  61. package/src/SelectBox/SelectBox.vue +0 -190
  62. package/src/SidebarLayout/SidebarLayout.md +0 -104
  63. package/src/SidebarLayout/SidebarLayout.test.ts +0 -86
  64. package/src/SidebarLayout/SidebarLayout.vue +0 -465
  65. package/src/Spinner/Spinner.md +0 -45
  66. package/src/Spinner/Spinner.spec.ts +0 -9
  67. package/src/Spinner/Spinner.vue +0 -55
  68. package/src/TextInput/TextInput.md +0 -41
  69. package/src/TextInput/TextInput.spec.ts +0 -10
  70. package/src/TextInput/TextInput.test.ts +0 -70
  71. package/src/TextInput/TextInput.vue +0 -90
  72. package/src/Toggle/Toggle.md +0 -68
  73. package/src/Toggle/Toggle.spec.ts +0 -13
  74. package/src/Toggle/Toggle.test.ts +0 -35
  75. package/src/Toggle/Toggle.vue +0 -81
  76. package/src/index.ts +0 -15
@@ -1,190 +0,0 @@
1
- <script setup lang="ts">
2
- import {
3
- SelectContent,
4
- SelectItem,
5
- SelectItemIndicator,
6
- SelectItemText,
7
- SelectPortal,
8
- SelectRoot,
9
- SelectTrigger,
10
- SelectValue,
11
- SelectViewport,
12
- useId,
13
- } from "reka-ui";
14
-
15
- export interface SelectOption {
16
- value: string;
17
- label: string;
18
- }
19
-
20
- const model = defineModel<string>();
21
-
22
- const props = defineProps<{
23
- label?: string;
24
- ariaLabel?: string;
25
- options: SelectOption[];
26
- placeholder?: string;
27
- }>();
28
-
29
- const id = useId();
30
- </script>
31
-
32
- <template>
33
- <div class="select-box">
34
- <label v-if="label" :id="`${id}-label`" class="select-label">{{
35
- label
36
- }}</label>
37
- <SelectRoot v-model="model">
38
- <SelectTrigger
39
- class="select-trigger"
40
- :aria-labelledby="props.label ? `${id}-label` : undefined"
41
- :aria-label="!props.label ? props.ariaLabel : undefined"
42
- >
43
- <SelectValue :placeholder="placeholder" />
44
- <span class="select-icon" aria-hidden="true">
45
- <svg
46
- width="12"
47
- height="12"
48
- viewBox="0 0 12 12"
49
- fill="none"
50
- stroke="currentColor"
51
- stroke-width="2"
52
- stroke-linecap="round"
53
- stroke-linejoin="round"
54
- >
55
- <path d="M3 4.5L6 7.5L9 4.5" />
56
- </svg>
57
- </span>
58
- </SelectTrigger>
59
- <SelectPortal>
60
- <SelectContent
61
- class="select-content"
62
- position="popper"
63
- :side-offset="4"
64
- :body-lock="false"
65
- >
66
- <SelectViewport class="select-viewport">
67
- <SelectItem
68
- v-for="opt in options"
69
- :key="opt.value"
70
- :value="opt.value"
71
- class="select-item"
72
- >
73
- <SelectItemText>{{ opt.label }}</SelectItemText>
74
- <SelectItemIndicator class="select-indicator">
75
- <svg
76
- width="12"
77
- height="12"
78
- viewBox="0 0 12 12"
79
- fill="none"
80
- stroke="currentColor"
81
- stroke-width="2"
82
- stroke-linecap="round"
83
- stroke-linejoin="round"
84
- >
85
- <path d="M2 6L5 9L10 3" />
86
- </svg>
87
- </SelectItemIndicator>
88
- </SelectItem>
89
- </SelectViewport>
90
- </SelectContent>
91
- </SelectPortal>
92
- </SelectRoot>
93
- </div>
94
- </template>
95
-
96
- <style scoped>
97
- .select-box {
98
- display: flex;
99
- flex-direction: column;
100
- gap: 0.25em;
101
- }
102
-
103
- .select-label {
104
- font-size: var(--font-size-sm);
105
- }
106
-
107
- .select-trigger {
108
- display: inline-flex;
109
- align-items: center;
110
- justify-content: space-between;
111
- gap: 0.5em;
112
- font-size: var(--font-size-sm);
113
- height: 2.5em;
114
- padding: 0 0.75em;
115
- border: 1px solid var(--color-border);
116
- border-radius: 0.375em;
117
- background: var(--color-bg-0);
118
- cursor: pointer;
119
- width: auto;
120
- font-family: inherit;
121
- color: inherit;
122
- line-height: 1.4;
123
- }
124
-
125
- .select-trigger:hover {
126
- border-color: var(--color-border-hover);
127
- }
128
-
129
- .select-trigger:focus-visible {
130
- outline: 2px solid var(--color-primary);
131
- outline-offset: -1px;
132
- }
133
-
134
- .select-trigger[data-placeholder] {
135
- color: var(--color-text-secondary);
136
- }
137
-
138
- .select-icon {
139
- display: flex;
140
- align-items: center;
141
- flex-shrink: 0;
142
- }
143
- </style>
144
-
145
- <style>
146
- .select-content {
147
- z-index: 100;
148
- background: var(--color-bg-0);
149
- border: 1px solid var(--color-border);
150
- border-radius: 0.25em;
151
- box-shadow:
152
- 0 4px 6px -1px rgba(0, 0, 0, 0.1),
153
- 0 2px 4px -2px rgba(0, 0, 0, 0.1);
154
- min-width: var(--reka-select-trigger-width);
155
- max-height: var(--reka-select-content-available-height);
156
- }
157
-
158
- .select-viewport {
159
- padding: 0.25em;
160
- }
161
-
162
- .select-item {
163
- display: flex;
164
- align-items: center;
165
- justify-content: space-between;
166
- gap: 0.5em;
167
- padding: 0.25em 0.5em;
168
- border-radius: 0.25em;
169
- font-size: var(--font-size-sm);
170
- white-space: nowrap;
171
- cursor: pointer;
172
- user-select: none;
173
- outline: none;
174
- }
175
-
176
- .select-item[data-highlighted] {
177
- background: var(--color-primary);
178
- color: white;
179
- }
180
-
181
- .select-item[data-state="checked"] {
182
- font-weight: 600;
183
- }
184
-
185
- .select-indicator {
186
- display: flex;
187
- align-items: center;
188
- flex-shrink: 0;
189
- }
190
- </style>
@@ -1,104 +0,0 @@
1
- # SidebarLayout
2
-
3
- A responsive two-panel layout with a collapsible sidebar and main content area. On mobile, the sidebar becomes an overlay.
4
-
5
- ## Demo
6
-
7
- <a href="/cfa-simulator/docs/demos/sidebar-layout/index.html" target="_blank">Open in full window ↗</a>
8
-
9
- <div style="border: 1px solid var(--vp-c-border); border-radius: 8px; overflow: hidden; height: 500px;">
10
- <iframe src="/cfa-simulator/docs/demos/sidebar-layout/index.html" style="width: 100%; height: 100%; border: none;" />
11
- </div>
12
-
13
- ## Tabs Demo (Router Mode)
14
-
15
- <a href="/cfa-simulator/docs/demos/sidebar-tabs/index.html" target="_blank">Open in full window ↗</a>
16
-
17
- <div style="border: 1px solid var(--vp-c-border); border-radius: 8px; overflow: hidden; height: 500px;">
18
- <iframe src="/cfa-simulator/docs/demos/sidebar-tabs/index.html" style="width: 100%; height: 100%; border: none;" />
19
- </div>
20
-
21
- ## Usage
22
-
23
- ```vue
24
- <SidebarLayout>
25
- <template #sidebar>
26
- <h2>Controls</h2>
27
- <NumberInput v-model="value" label="Parameter" slider live />
28
- </template>
29
- <h1>Main Content</h1>
30
- <p>Your charts and data go here.</p>
31
- </SidebarLayout>
32
- ```
33
-
34
- ## Slots
35
-
36
- | Slot | Description |
37
- | --------- | ------------------------------------------ |
38
- | `sidebar` | Content rendered in the left sidebar panel |
39
- | `default` | Main content area |
40
-
41
- ## Props
42
-
43
- | Prop | Type | Default | Description |
44
- | ------------- | --------- | ----------- | ---------------------------------------------------- |
45
- | `hideTopbar` | `boolean` | `false` | Hides the topbar that contains the light/dark toggle |
46
- | `tabs` | `Tab[]` | `undefined` | Array of tab definitions to render in the main area |
47
- | `v-model:tab` | `string` | `undefined` | The active tab value (two-way binding) |
48
-
49
- ### Tab type
50
-
51
- ```ts
52
- interface Tab {
53
- value: string; // unique identifier
54
- label: string; // display text
55
- to?: string; // optional route path for vue-router integration
56
- }
57
- ```
58
-
59
- ## Tabs
60
-
61
- When the `tabs` prop is provided, a tab bar renders at the top of the main content area. Tabs support two modes:
62
-
63
- ### Local mode
64
-
65
- Use `v-model:tab` to control which tab is active. Render content conditionally in the default slot.
66
-
67
- ```vue
68
- <script setup>
69
- import { ref } from "vue";
70
- const activeTab = ref("chart");
71
- </script>
72
-
73
- <SidebarLayout
74
- v-model:tab="activeTab"
75
- :tabs="[
76
- { value: 'chart', label: 'Chart' },
77
- { value: 'data', label: 'Data' },
78
- ]"
79
- >
80
- <template #sidebar>
81
- <h2>Controls</h2>
82
- </template>
83
- <div v-if="activeTab === 'chart'">Chart content</div>
84
- <div v-if="activeTab === 'data'">Data table</div>
85
- </SidebarLayout>
86
- ```
87
-
88
- ### Router mode
89
-
90
- When tabs include a `to` property and vue-router is installed, clicking a tab navigates to that route. The active tab is automatically determined from the current route.
91
-
92
- ```vue
93
- <SidebarLayout
94
- :tabs="[
95
- { value: 'chart', label: 'Chart', to: '/model/chart' },
96
- { value: 'data', label: 'Data', to: '/model/data' },
97
- ]"
98
- >
99
- <template #sidebar>
100
- <h2>Controls</h2>
101
- </template>
102
- <RouterView />
103
- </SidebarLayout>
104
- ```
@@ -1,86 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
2
- import { mount } from "@vue/test-utils";
3
- import SidebarLayout from "./SidebarLayout.vue";
4
-
5
- beforeEach(() => {
6
- // SidebarLayout uses window.matchMedia
7
- window.matchMedia = vi.fn().mockReturnValue({
8
- matches: false,
9
- addEventListener: vi.fn(),
10
- removeEventListener: vi.fn(),
11
- });
12
- });
13
-
14
- const tabs = [
15
- { value: "chart", label: "Chart" },
16
- { value: "data", label: "Data" },
17
- { value: "table", label: "Table" },
18
- ];
19
-
20
- describe("SidebarLayout tabs", () => {
21
- it("renders without tabs by default", () => {
22
- const wrapper = mount(SidebarLayout);
23
- expect(wrapper.find("[role='tablist']").exists()).toBe(false);
24
- });
25
-
26
- it("renders tab triggers when tabs prop is provided", () => {
27
- const wrapper = mount(SidebarLayout, { props: { tabs } });
28
- const triggers = wrapper.findAll("[role='tab']");
29
- expect(triggers).toHaveLength(3);
30
- expect(triggers[0].text()).toBe("Chart");
31
- expect(triggers[1].text()).toBe("Data");
32
- expect(triggers[2].text()).toBe("Table");
33
- });
34
-
35
- it("defaults to first tab as active", () => {
36
- const wrapper = mount(SidebarLayout, { props: { tabs } });
37
- const triggers = wrapper.findAll("[role='tab']");
38
- expect(triggers[0].attributes("data-state")).toBe("active");
39
- expect(triggers[1].attributes("data-state")).toBe("inactive");
40
- });
41
-
42
- it("activates tab matching v-model:tab", () => {
43
- const wrapper = mount(SidebarLayout, {
44
- props: { tabs, tab: "data" },
45
- });
46
- const triggers = wrapper.findAll("[role='tab']");
47
- expect(triggers[1].attributes("data-state")).toBe("active");
48
- });
49
-
50
- it("emits update:tab when a tab is clicked", async () => {
51
- let updated: string | undefined;
52
- const wrapper = mount(SidebarLayout, {
53
- props: {
54
- tabs,
55
- tab: "chart",
56
- "onUpdate:tab": (v: string | undefined) => {
57
- updated = v;
58
- },
59
- },
60
- });
61
- const triggers = wrapper.findAll("[role='tab']");
62
- // reka-ui triggers need mousedown for activation in happy-dom
63
- await triggers[2].trigger("mousedown");
64
- await triggers[2].trigger("focus");
65
- await triggers[2].trigger("mouseup");
66
- await triggers[2].trigger("click");
67
- expect(updated).toBe("table");
68
- });
69
-
70
- it("renders default slot content inside tabbed layout", () => {
71
- const wrapper = mount(SidebarLayout, {
72
- props: { tabs },
73
- slots: { default: '<p class="test-content">Hello</p>' },
74
- });
75
- expect(wrapper.find(".test-content").text()).toBe("Hello");
76
- });
77
-
78
- it("renders sidebar slot alongside tabs", () => {
79
- const wrapper = mount(SidebarLayout, {
80
- props: { tabs },
81
- slots: { sidebar: '<div class="sidebar-content">Sidebar</div>' },
82
- });
83
- expect(wrapper.find(".sidebar-content").text()).toBe("Sidebar");
84
- expect(wrapper.findAll("[role='tab']")).toHaveLength(3);
85
- });
86
- });