@aggc/ui 0.4.0 → 0.5.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 (158) hide show
  1. package/README.md +19 -0
  2. package/dist/chunks/UiSkeleton.vue_vue_type_script_setup_true_lang-Lghyrtms.js +1201 -0
  3. package/dist/chunks/pageHeader-CcJrPX_8.js +522 -0
  4. package/dist/components/PageSurface.styles.d.ts +1 -0
  5. package/dist/components/PageSurface.vue.d.ts +13 -0
  6. package/dist/components/ResultPanel.styles.d.ts +64 -0
  7. package/dist/components/ResultPanel.vue.d.ts +16 -0
  8. package/dist/components/SectionCard.styles.d.ts +13 -0
  9. package/dist/components/SectionCard.vue.d.ts +27 -0
  10. package/dist/components/StatusBadge.styles.d.ts +24 -0
  11. package/dist/components/StatusBadge.vue.d.ts +18 -0
  12. package/dist/components/UiButton.styles.d.ts +54 -0
  13. package/dist/components/UiButton.vue.d.ts +28 -0
  14. package/dist/components/UiCheckbox.styles.d.ts +42 -0
  15. package/dist/components/UiCheckbox.vue.d.ts +37 -0
  16. package/dist/components/UiField.styles.d.ts +7 -0
  17. package/dist/components/UiField.vue.d.ts +22 -0
  18. package/dist/components/UiLoadingState.styles.d.ts +4 -0
  19. package/dist/components/UiLoadingState.vue.d.ts +10 -0
  20. package/dist/components/UiSegmentedControl.styles.d.ts +23 -0
  21. package/dist/components/UiSegmentedControl.vue.d.ts +14 -0
  22. package/dist/components/UiSelect.styles.d.ts +104 -0
  23. package/dist/components/UiSelect.vue.d.ts +35 -0
  24. package/dist/components/UiSkeleton.styles.d.ts +67 -0
  25. package/dist/components/UiSkeleton.vue.d.ts +12 -0
  26. package/dist/components/index.d.ts +11 -0
  27. package/dist/components.d.ts +1 -0
  28. package/dist/components.js +14 -0
  29. package/dist/css.d.ts +2 -0
  30. package/dist/css.js +1 -0
  31. package/dist/fonts.css +6 -0
  32. package/dist/index.d.ts +3 -0
  33. package/dist/index.js +65 -0
  34. package/dist/styles/index.d.ts +20 -0
  35. package/dist/styles/layouts/cluster.d.ts +27 -0
  36. package/dist/styles/layouts/page.d.ts +2 -0
  37. package/dist/styles/layouts/split.d.ts +22 -0
  38. package/dist/styles/layouts/stack.d.ts +19 -0
  39. package/dist/styles/patterns/actionToolbar.d.ts +1 -0
  40. package/dist/styles/patterns/emptyState.d.ts +2 -0
  41. package/dist/styles/patterns/infoPanel.d.ts +2 -0
  42. package/dist/styles/patterns/metricGrid.d.ts +22 -0
  43. package/dist/styles/patterns/pageHeader.d.ts +3 -0
  44. package/dist/styles/patterns/resultRegion.d.ts +1 -0
  45. package/dist/styles/patterns/selectableListDetail.d.ts +3 -0
  46. package/dist/styles/primitives/feedback.d.ts +4 -0
  47. package/dist/styles/primitives/fields.d.ts +3 -0
  48. package/dist/styles/primitives/surfaces.d.ts +3 -0
  49. package/dist/styles/primitives/typography.d.ts +6 -0
  50. package/dist/styles/recipes/badge.recipe.d.ts +32 -0
  51. package/dist/styles/recipes/button.recipe.d.ts +51 -0
  52. package/dist/styles/recipes/card.recipe.d.ts +59 -0
  53. package/dist/styles/recipes/dropdown.recipe.d.ts +25 -0
  54. package/dist/styles/recipes/input.recipe.d.ts +32 -0
  55. package/dist/styles.d.ts +1 -0
  56. package/dist/styles.js +429 -0
  57. package/dist/tokens/colors.d.ts +360 -0
  58. package/dist/tokens/core-colors.d.ts +238 -0
  59. package/dist/tokens/desktop-colors.d.ts +132 -0
  60. package/dist/tokens/index.d.ts +7 -0
  61. package/dist/tokens/motion.d.ts +10 -0
  62. package/dist/tokens/radius.d.ts +5 -0
  63. package/dist/tokens/spacing.d.ts +8 -0
  64. package/dist/tokens/typography.d.ts +10 -0
  65. package/dist/tokens-core.d.ts +5 -0
  66. package/dist/tokens-core.js +69 -0
  67. package/dist/tokens-desktop.d.ts +1 -0
  68. package/dist/tokens-desktop.js +30 -0
  69. package/dist/tokens.d.ts +1 -0
  70. package/dist/tokens.js +16 -0
  71. package/dist/ui.css +2026 -0
  72. package/package.json +3 -2
  73. package/src/components/PageSurface.styles.ts +3 -0
  74. package/src/components/PageSurface.vue +9 -0
  75. package/src/components/ResultPanel.styles.ts +108 -0
  76. package/src/components/ResultPanel.test.ts +22 -0
  77. package/src/components/ResultPanel.vue +70 -0
  78. package/src/components/SectionCard.styles.ts +65 -0
  79. package/src/components/SectionCard.test.ts +22 -0
  80. package/src/components/SectionCard.vue +51 -0
  81. package/src/components/StatusBadge.styles.ts +49 -0
  82. package/src/components/StatusBadge.test.ts +18 -0
  83. package/src/components/StatusBadge.vue +18 -0
  84. package/src/components/UiButton.styles.ts +29 -0
  85. package/src/components/UiButton.test.ts +21 -0
  86. package/src/components/UiButton.vue +46 -0
  87. package/src/components/UiCheckbox.styles.ts +118 -0
  88. package/src/components/UiCheckbox.test.ts +18 -0
  89. package/src/components/UiCheckbox.vue +72 -0
  90. package/src/components/UiField.styles.ts +35 -0
  91. package/src/components/UiField.test.ts +22 -0
  92. package/src/components/UiField.vue +36 -0
  93. package/src/components/UiLoadingState.styles.ts +36 -0
  94. package/src/components/UiLoadingState.vue +34 -0
  95. package/src/components/UiSegmentedControl.styles.ts +49 -0
  96. package/src/components/UiSegmentedControl.vue +30 -0
  97. package/src/components/UiSelect.styles.ts +214 -0
  98. package/src/components/UiSelect.test.ts +49 -0
  99. package/src/components/UiSelect.vue +256 -0
  100. package/src/components/UiSkeleton.styles.ts +93 -0
  101. package/src/components/UiSkeleton.vue +48 -0
  102. package/src/components/index.ts +11 -0
  103. package/src/components.ts +1 -0
  104. package/src/css/base.css +62 -0
  105. package/src/css/fonts.css +6 -0
  106. package/src/css/index.css +2 -0
  107. package/src/css/storybook.css +15 -0
  108. package/src/env.d.ts +1 -0
  109. package/src/index.ts +3 -0
  110. package/src/stories/feedback/ResultPanel.stories.ts +76 -0
  111. package/src/stories/feedback/StatusBadge.stories.ts +50 -0
  112. package/src/stories/feedback/UiLoadingState.stories.ts +52 -0
  113. package/src/stories/feedback/UiSkeleton.stories.ts +85 -0
  114. package/src/stories/forms/UiCheckbox.stories.ts +104 -0
  115. package/src/stories/forms/UiField.stories.ts +87 -0
  116. package/src/stories/forms/UiSelect.stories.ts +134 -0
  117. package/src/stories/layout/PageSurface.stories.ts +53 -0
  118. package/src/stories/layout/SectionCard.stories.ts +85 -0
  119. package/src/stories/primitives/UiButton.stories.ts +145 -0
  120. package/src/stories/primitives/UiSegmentedControl.stories.ts +67 -0
  121. package/src/stories/support/StoryThemeFrame.vue +101 -0
  122. package/src/stories/support/sources.ts +374 -0
  123. package/src/stories/support/storyStyles.ts +150 -0
  124. package/src/styles/README.md +23 -0
  125. package/src/styles/index.ts +20 -0
  126. package/src/styles/layouts/cluster.ts +27 -0
  127. package/src/styles/layouts/page.ts +22 -0
  128. package/src/styles/layouts/split.ts +26 -0
  129. package/src/styles/layouts/stack.ts +21 -0
  130. package/src/styles/patterns/actionToolbar.ts +8 -0
  131. package/src/styles/patterns/emptyState.ts +23 -0
  132. package/src/styles/patterns/infoPanel.ts +22 -0
  133. package/src/styles/patterns/metricGrid.ts +19 -0
  134. package/src/styles/patterns/pageHeader.ts +19 -0
  135. package/src/styles/patterns/resultRegion.ts +7 -0
  136. package/src/styles/patterns/selectableListDetail.ts +21 -0
  137. package/src/styles/primitives/feedback.ts +23 -0
  138. package/src/styles/primitives/fields.ts +76 -0
  139. package/src/styles/primitives/surfaces.ts +52 -0
  140. package/src/styles/primitives/typography.ts +42 -0
  141. package/src/styles/recipes/badge.recipe.ts +54 -0
  142. package/src/styles/recipes/button.recipe.ts +115 -0
  143. package/src/styles/recipes/card.recipe.ts +64 -0
  144. package/src/styles/recipes/dropdown.recipe.ts +40 -0
  145. package/src/styles/recipes/input.recipe.ts +59 -0
  146. package/src/styles.ts +1 -0
  147. package/src/test/setup.ts +1 -0
  148. package/src/tokens/colors.ts +16 -0
  149. package/src/tokens/core-colors.ts +53 -0
  150. package/src/tokens/desktop-colors.ts +37 -0
  151. package/src/tokens/index.ts +8 -0
  152. package/src/tokens/motion.ts +6 -0
  153. package/src/tokens/radius.ts +3 -0
  154. package/src/tokens/spacing.ts +4 -0
  155. package/src/tokens/typography.ts +6 -0
  156. package/src/tokens-core.ts +5 -0
  157. package/src/tokens-desktop.ts +1 -0
  158. package/src/tokens.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aggc/ui",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Shared Vue UI primitives, patterns, and tokens for AGGC desktop and web.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -50,7 +50,8 @@
50
50
  "*.css"
51
51
  ],
52
52
  "files": [
53
- "dist"
53
+ "dist",
54
+ "src"
54
55
  ],
55
56
  "main": "./dist/index.js",
56
57
  "module": "./dist/index.js",
@@ -0,0 +1,3 @@
1
+ import { pageRootClass } from "../styles";
2
+
3
+ export const pageSurfaceClass = pageRootClass;
@@ -0,0 +1,9 @@
1
+ <script setup lang="ts">
2
+ import { pageSurfaceClass } from "./PageSurface.styles";
3
+ </script>
4
+
5
+ <template>
6
+ <div :class="pageSurfaceClass">
7
+ <slot />
8
+ </div>
9
+ </template>
@@ -0,0 +1,108 @@
1
+ import { cva, css, cx } from "@styled/css";
2
+
3
+ import { surfacePanelClass } from "../styles";
4
+
5
+ export const resultPanelClass = cva({
6
+ base: {
7
+ borderWidth: "1px",
8
+ padding: "5",
9
+ },
10
+ variants: {
11
+ ok: {
12
+ true: {
13
+ borderColor: "result.okBorder",
14
+ bg: "result.okBg",
15
+ },
16
+ false: {
17
+ borderColor: "result.failBorder",
18
+ bg: "result.failBg",
19
+ },
20
+ },
21
+ },
22
+ });
23
+
24
+ export const resultPanelRootClass = (ok: boolean) => cx(surfacePanelClass, resultPanelClass({ ok }));
25
+
26
+ export const resultPanelHeaderClass = css({
27
+ display: "flex",
28
+ justifyContent: "space-between",
29
+ gap: "4",
30
+ alignItems: { base: "flex-start", md: "center" },
31
+ flexDirection: { base: "column", md: "row" },
32
+ mb: "3",
33
+ });
34
+
35
+ export const resultPanelSummaryClass = css({
36
+ display: "flex",
37
+ alignItems: "center",
38
+ gap: "3",
39
+ });
40
+
41
+ export const resultPanelIconClass = cva({
42
+ base: {
43
+ width: "10",
44
+ height: "10",
45
+ borderRadius: "full",
46
+ display: "flex",
47
+ alignItems: "center",
48
+ justifyContent: "center",
49
+ },
50
+ variants: {
51
+ ok: {
52
+ true: {
53
+ bg: "bg.accentSoft",
54
+ color: "text.accent",
55
+ },
56
+ false: {
57
+ bg: "badge.warningBg",
58
+ color: "badge.warningText",
59
+ },
60
+ },
61
+ },
62
+ });
63
+
64
+ export const resultPanelTitleClass = css({
65
+ fontSize: "lg",
66
+ fontWeight: "700",
67
+ color: "text.primary",
68
+ });
69
+
70
+ export const resultPanelSectionClass = cva({
71
+ base: {},
72
+ variants: {
73
+ spaced: {
74
+ true: { mb: "3" },
75
+ false: { mb: "0" },
76
+ },
77
+ },
78
+ });
79
+
80
+ export const resultPanelSectionTitleClass = cva({
81
+ base: {
82
+ fontSize: "sm",
83
+ fontWeight: "700",
84
+ mb: "2",
85
+ },
86
+ variants: {
87
+ tone: {
88
+ detail: { color: "result.detailText" },
89
+ warning: { color: "result.warningLabel" },
90
+ error: { color: "result.errorLabel" },
91
+ },
92
+ },
93
+ });
94
+
95
+ export const resultPanelListClass = cva({
96
+ base: {
97
+ display: "grid",
98
+ gap: "1.5",
99
+ pl: "4",
100
+ },
101
+ variants: {
102
+ tone: {
103
+ detail: { color: "result.bodyText" },
104
+ warning: { color: "result.warningBody" },
105
+ error: { color: "result.errorBody" },
106
+ },
107
+ },
108
+ });
@@ -0,0 +1,22 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { describe, expect, it } from "vitest";
3
+ import ResultPanel from "./ResultPanel.vue";
4
+
5
+ describe("ResultPanel", () => {
6
+ it("renders configurable copy and polite live region by default", () => {
7
+ const wrapper = mount(ResultPanel, {
8
+ props: {
9
+ ok: false,
10
+ summary: "Validation failed",
11
+ failLabel: "Failed",
12
+ warningsLabel: "Heads up",
13
+ warnings: ["One warning"],
14
+ },
15
+ });
16
+
17
+ expect(wrapper.attributes("role")).toBe("status");
18
+ expect(wrapper.attributes("aria-live")).toBe("polite");
19
+ expect(wrapper.text()).toContain("Failed");
20
+ expect(wrapper.text()).toContain("Heads up");
21
+ });
22
+ });
@@ -0,0 +1,70 @@
1
+ <script setup lang="ts">
2
+ import { CheckCircle2, TriangleAlert } from "lucide-vue-next";
3
+
4
+ import StatusBadge from "./StatusBadge.vue";
5
+ import {
6
+ resultPanelHeaderClass,
7
+ resultPanelIconClass,
8
+ resultPanelListClass,
9
+ resultPanelRootClass,
10
+ resultPanelSectionClass,
11
+ resultPanelSectionTitleClass,
12
+ resultPanelSummaryClass,
13
+ resultPanelTitleClass,
14
+ } from "./ResultPanel.styles";
15
+
16
+ const props = defineProps<{
17
+ ok: boolean;
18
+ summary: string;
19
+ warnings?: string[];
20
+ errors?: string[];
21
+ messages?: string[];
22
+ live?: "polite" | "assertive" | "off";
23
+ passLabel?: string;
24
+ failLabel?: string;
25
+ detailsLabel?: string;
26
+ warningsLabel?: string;
27
+ errorsLabel?: string;
28
+ }>();
29
+ </script>
30
+
31
+ <template>
32
+ <div
33
+ :class="resultPanelRootClass(ok)"
34
+ :aria-live="props.live ?? 'polite'"
35
+ :role="(props.live ?? 'polite') === 'off' ? undefined : 'status'"
36
+ >
37
+ <div :class="resultPanelHeaderClass">
38
+ <div :class="resultPanelSummaryClass">
39
+ <div :class="resultPanelIconClass({ ok })">
40
+ <component :is="ok ? CheckCircle2 : TriangleAlert" :size="18" />
41
+ </div>
42
+ <h3 :class="resultPanelTitleClass">{{ props.summary }}</h3>
43
+ </div>
44
+ <StatusBadge :tone="ok ? 'success' : 'warning'">
45
+ {{ ok ? props.passLabel ?? "Pass" : props.failLabel ?? "Fail" }}
46
+ </StatusBadge>
47
+ </div>
48
+
49
+ <div v-if="messages?.length" :class="resultPanelSectionClass({ spaced: true })">
50
+ <p :class="resultPanelSectionTitleClass({ tone: 'detail' })">{{ props.detailsLabel ?? "Details" }}</p>
51
+ <ul :class="resultPanelListClass({ tone: 'detail' })">
52
+ <li v-for="message in messages" :key="message">{{ message }}</li>
53
+ </ul>
54
+ </div>
55
+
56
+ <div v-if="warnings?.length" :class="resultPanelSectionClass({ spaced: !!errors?.length })">
57
+ <p :class="resultPanelSectionTitleClass({ tone: 'warning' })">{{ props.warningsLabel ?? "Warnings" }}</p>
58
+ <ul :class="resultPanelListClass({ tone: 'warning' })">
59
+ <li v-for="warning in warnings" :key="warning">{{ warning }}</li>
60
+ </ul>
61
+ </div>
62
+
63
+ <div v-if="errors?.length">
64
+ <p :class="resultPanelSectionTitleClass({ tone: 'error' })">{{ props.errorsLabel ?? "Errors" }}</p>
65
+ <ul :class="resultPanelListClass({ tone: 'error' })">
66
+ <li v-for="error in errors" :key="error">{{ error }}</li>
67
+ </ul>
68
+ </div>
69
+ </div>
70
+ </template>
@@ -0,0 +1,65 @@
1
+ import { css } from "@styled/css";
2
+ import {
3
+ pageHeaderActionsClass,
4
+ pageHeaderContentClass,
5
+ pageHeaderRootClass,
6
+ sectionTitleClass,
7
+ surfacePanelClass,
8
+ eyebrowClass,
9
+ } from "../styles";
10
+
11
+ export const sectionCardClass = css({
12
+ padding: "6",
13
+ });
14
+
15
+ export const sectionCardScrollClass = css({
16
+ height: "100%",
17
+ minHeight: "0",
18
+ display: "grid",
19
+ gridTemplateRows: "auto minmax(0, 1fr)",
20
+ });
21
+
22
+ export const sectionCardSurfaceClass = surfacePanelClass;
23
+
24
+ export const sectionCardHeaderClass = pageHeaderRootClass;
25
+
26
+ export const sectionCardHeaderWithGapClass = css({
27
+ position: "relative",
28
+ zIndex: "1",
29
+ mb: "5",
30
+ });
31
+
32
+ export const sectionCardHeaderCollapsedGapClass = css({
33
+ position: "relative",
34
+ zIndex: "1",
35
+ mb: "0",
36
+ });
37
+
38
+ export const sectionCardHeaderContentClass = pageHeaderContentClass;
39
+
40
+ export const sectionCardEyebrowClass = eyebrowClass;
41
+
42
+ export const sectionCardTitleClass = sectionTitleClass;
43
+
44
+ export const sectionCardDescriptionClass = css({
45
+ color: "text.secondary",
46
+ lineHeight: "1.65",
47
+ fontSize: "sm",
48
+ maxWidth: "2xl",
49
+ });
50
+
51
+ export const sectionCardActionsClass = pageHeaderActionsClass;
52
+
53
+ export const sectionCardBodyScrollClass = css({
54
+ position: "relative",
55
+ zIndex: "1",
56
+ minHeight: "0",
57
+ overflowY: "auto",
58
+ paddingRight: "1",
59
+ overscrollBehavior: "contain",
60
+ });
61
+
62
+ export const sectionCardBodyStaticClass = css({
63
+ position: "relative",
64
+ zIndex: "1",
65
+ });
@@ -0,0 +1,22 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { describe, expect, it } from "vitest";
3
+ import SectionCard from "./SectionCard.vue";
4
+
5
+ describe("SectionCard", () => {
6
+ it("renders a configurable eyebrow and actions slot", () => {
7
+ const wrapper = mount(SectionCard, {
8
+ props: {
9
+ title: "Release",
10
+ eyebrow: "Lifecycle",
11
+ },
12
+ slots: {
13
+ actions: "Actions",
14
+ default: "Body",
15
+ },
16
+ });
17
+
18
+ expect(wrapper.text()).toContain("Lifecycle");
19
+ expect(wrapper.text()).toContain("Actions");
20
+ expect(wrapper.text()).toContain("Body");
21
+ });
22
+ });
@@ -0,0 +1,51 @@
1
+ <script setup lang="ts">
2
+ import { cx } from "@styled/css";
3
+ import {
4
+ sectionCardActionsClass,
5
+ sectionCardBodyScrollClass,
6
+ sectionCardBodyStaticClass,
7
+ sectionCardClass,
8
+ sectionCardDescriptionClass,
9
+ sectionCardEyebrowClass,
10
+ sectionCardHeaderClass,
11
+ sectionCardHeaderCollapsedGapClass,
12
+ sectionCardHeaderContentClass,
13
+ sectionCardHeaderWithGapClass,
14
+ sectionCardScrollClass,
15
+ sectionCardSurfaceClass,
16
+ sectionCardTitleClass,
17
+ } from "./SectionCard.styles";
18
+
19
+ const props = withDefaults(defineProps<{
20
+ title: string;
21
+ eyebrow?: string;
22
+ description?: string;
23
+ scrollBody?: boolean;
24
+ collapseBodyGap?: boolean;
25
+ }>(), {
26
+ eyebrow: "Workspace surface",
27
+ description: "",
28
+ scrollBody: false,
29
+ collapseBodyGap: false,
30
+ });
31
+ </script>
32
+
33
+ <template>
34
+ <section :class="cx(sectionCardSurfaceClass, sectionCardClass, props.scrollBody && sectionCardScrollClass)">
35
+ <div :class="cx(sectionCardHeaderClass, props.collapseBodyGap ? sectionCardHeaderCollapsedGapClass : sectionCardHeaderWithGapClass)">
36
+ <div :class="sectionCardHeaderContentClass">
37
+ <p v-if="props.eyebrow" :class="sectionCardEyebrowClass">{{ props.eyebrow }}</p>
38
+ <h2 :class="sectionCardTitleClass">{{ props.title }}</h2>
39
+ <p v-if="props.description" :class="sectionCardDescriptionClass">
40
+ {{ props.description }}
41
+ </p>
42
+ </div>
43
+ <div v-if="$slots.actions" :class="sectionCardActionsClass">
44
+ <slot name="actions" />
45
+ </div>
46
+ </div>
47
+ <div :class="props.scrollBody ? sectionCardBodyScrollClass : sectionCardBodyStaticClass">
48
+ <slot />
49
+ </div>
50
+ </section>
51
+ </template>
@@ -0,0 +1,49 @@
1
+ import { cva } from "@styled/css";
2
+
3
+ export const statusBadgeClass = cva({
4
+ base: {
5
+ display: "inline-flex",
6
+ alignItems: "center",
7
+ justifyContent: "center",
8
+ borderRadius: "full",
9
+ borderWidth: "1px",
10
+ px: "3",
11
+ py: "1.5",
12
+ fontSize: "xs",
13
+ fontWeight: "700",
14
+ textTransform: "uppercase",
15
+ letterSpacing: "0.1em",
16
+ backdropFilter: "blur(16px) saturate(140%)",
17
+ boxShadow: "inset 0 1px 0 rgba(255,255,255,0.16)",
18
+ _dark: {
19
+ boxShadow: "inset 0 1px 0 rgba(255,255,255,0.04)",
20
+ },
21
+ },
22
+ variants: {
23
+ tone: {
24
+ success: {
25
+ bg: "badge.successBg",
26
+ color: "badge.successText",
27
+ borderColor: "badge.successBorder",
28
+ },
29
+ warning: {
30
+ bg: "badge.warningBg",
31
+ color: "badge.warningText",
32
+ borderColor: "badge.warningBorder",
33
+ },
34
+ info: {
35
+ bg: "badge.infoBg",
36
+ color: "badge.infoText",
37
+ borderColor: "badge.infoBorder",
38
+ },
39
+ neutral: {
40
+ bg: "badge.neutralBg",
41
+ color: "badge.neutralText",
42
+ borderColor: "badge.neutralBorder",
43
+ },
44
+ },
45
+ },
46
+ defaultVariants: {
47
+ tone: "neutral",
48
+ },
49
+ });
@@ -0,0 +1,18 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { describe, expect, it } from "vitest";
3
+ import StatusBadge from "./StatusBadge.vue";
4
+
5
+ describe("StatusBadge", () => {
6
+ it("renders slot content", () => {
7
+ const wrapper = mount(StatusBadge, {
8
+ props: {
9
+ tone: "success",
10
+ },
11
+ slots: {
12
+ default: "Ready",
13
+ },
14
+ });
15
+
16
+ expect(wrapper.text()).toContain("Ready");
17
+ });
18
+ });
@@ -0,0 +1,18 @@
1
+ <script setup lang="ts">
2
+ import { statusBadgeClass } from "./StatusBadge.styles";
3
+
4
+ const props = withDefaults(
5
+ defineProps<{
6
+ tone?: "success" | "warning" | "info" | "neutral";
7
+ }>(),
8
+ {
9
+ tone: "neutral",
10
+ }
11
+ );
12
+ </script>
13
+
14
+ <template>
15
+ <span :class="statusBadgeClass({ tone: props.tone })">
16
+ <slot />
17
+ </span>
18
+ </template>
@@ -0,0 +1,29 @@
1
+ import { css } from "@styled/css";
2
+ import { buttonRecipe } from "../styles";
3
+
4
+ // buttonRecipe is the source of truth — UiButton uses it directly.
5
+ export const uiButtonClass = buttonRecipe;
6
+
7
+ export const uiButtonContentClass = css({
8
+ display: "inline-flex",
9
+ alignItems: "center",
10
+ justifyContent: "center",
11
+ gap: "2",
12
+ minWidth: "0",
13
+ });
14
+
15
+ export const uiButtonHiddenContentClass = css({
16
+ visibility: "hidden",
17
+ });
18
+
19
+ export const uiButtonBusyContentClass = css({
20
+ position: "absolute",
21
+ inset: "0",
22
+ display: "inline-flex",
23
+ alignItems: "center",
24
+ justifyContent: "center",
25
+ gap: "2",
26
+ px: "4",
27
+ py: "3",
28
+ pointerEvents: "none",
29
+ });
@@ -0,0 +1,21 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { describe, expect, it } from "vitest";
3
+ import UiButton from "./UiButton.vue";
4
+
5
+ describe("UiButton", () => {
6
+ it("renders loading state accessibly", () => {
7
+ const wrapper = mount(UiButton, {
8
+ props: {
9
+ loading: true,
10
+ loadingLabel: "Saving",
11
+ },
12
+ slots: {
13
+ default: "Save",
14
+ },
15
+ });
16
+
17
+ expect(wrapper.attributes("aria-busy")).toBe("true");
18
+ expect(wrapper.text()).toContain("Saving");
19
+ expect(wrapper.attributes("disabled")).toBeDefined();
20
+ });
21
+ });
@@ -0,0 +1,46 @@
1
+ <script setup lang="ts">
2
+ import { LoaderCircle } from "lucide-vue-next";
3
+ import {
4
+ uiButtonBusyContentClass,
5
+ uiButtonClass,
6
+ uiButtonContentClass,
7
+ uiButtonHiddenContentClass,
8
+ } from "./UiButton.styles";
9
+
10
+ withDefaults(
11
+ defineProps<{
12
+ variant?: "solid" | "outline" | "subtle";
13
+ loading?: boolean;
14
+ loadingLabel?: string;
15
+ disabled?: boolean;
16
+ type?: "button" | "submit";
17
+ ariaLabel?: string;
18
+ }>(),
19
+ {
20
+ variant: "solid",
21
+ loading: false,
22
+ loadingLabel: "Working...",
23
+ disabled: false,
24
+ type: "button",
25
+ ariaLabel: undefined,
26
+ }
27
+ );
28
+ </script>
29
+
30
+ <template>
31
+ <button
32
+ :type="type"
33
+ :disabled="disabled || loading"
34
+ :aria-busy="loading || undefined"
35
+ :aria-label="ariaLabel"
36
+ :class="uiButtonClass({ variant, disabled, loading })"
37
+ >
38
+ <span :class="[uiButtonContentClass, loading ? uiButtonHiddenContentClass : undefined]">
39
+ <slot />
40
+ </span>
41
+ <span v-if="loading" :class="uiButtonBusyContentClass">
42
+ <LoaderCircle :size="14" class="aggc-spin" aria-hidden="true" />
43
+ <span>{{ loadingLabel }}</span>
44
+ </span>
45
+ </button>
46
+ </template>
@@ -0,0 +1,118 @@
1
+ import { cva, css } from "@styled/css";
2
+
3
+ export const uiCheckboxRootClass = cva({
4
+ base: {
5
+ width: "100%",
6
+ display: "flex",
7
+ alignItems: "flex-start",
8
+ gap: "3",
9
+ textAlign: "left",
10
+ borderRadius: "2xl",
11
+ borderWidth: "1px",
12
+ px: "4",
13
+ py: "3.5",
14
+ color: "text.primary",
15
+ transition: "all 160ms ease",
16
+ },
17
+ variants: {
18
+ checked: {
19
+ true: {
20
+ borderColor: "border.accent",
21
+ bg: "bg.selected",
22
+ boxShadow: "0 18px 40px -30px rgba(49,94,255,0.56)",
23
+ },
24
+ false: {
25
+ borderColor: "border.default",
26
+ bg: "bg.input",
27
+ boxShadow: "0 14px 32px -30px rgba(15,23,42,0.42)",
28
+ },
29
+ },
30
+ disabled: {
31
+ true: {
32
+ cursor: "not-allowed",
33
+ opacity: 0.5,
34
+ },
35
+ false: {
36
+ cursor: "pointer",
37
+ },
38
+ },
39
+ },
40
+ compoundVariants: [
41
+ {
42
+ checked: true,
43
+ disabled: false,
44
+ css: {
45
+ _hover: {
46
+ borderColor: "border.accent",
47
+ transform: "translateY(-1px)",
48
+ },
49
+ },
50
+ },
51
+ {
52
+ checked: false,
53
+ disabled: false,
54
+ css: {
55
+ _hover: {
56
+ borderColor: "border.strong",
57
+ transform: "translateY(-1px)",
58
+ },
59
+ },
60
+ },
61
+ ],
62
+ defaultVariants: {
63
+ checked: false,
64
+ disabled: false,
65
+ },
66
+ });
67
+
68
+ export const uiCheckboxIndicatorClass = cva({
69
+ base: {
70
+ mt: "0.5",
71
+ flexShrink: "0",
72
+ width: "22px",
73
+ height: "22px",
74
+ borderRadius: "md",
75
+ borderWidth: "1px",
76
+ display: "flex",
77
+ alignItems: "center",
78
+ justifyContent: "center",
79
+ transition: "all 160ms ease",
80
+ },
81
+ variants: {
82
+ checked: {
83
+ true: {
84
+ borderColor: "border.accent",
85
+ bg: "bg.accentStrong",
86
+ color: "text.inverse",
87
+ boxShadow: "0 12px 24px -16px rgba(49,94,255,0.7)",
88
+ },
89
+ false: {
90
+ borderColor: "border.default",
91
+ bg: "bg.buttonOutline",
92
+ color: "transparent",
93
+ boxShadow: "inset 0 1px 0 rgba(255,255,255,0.32)",
94
+ },
95
+ },
96
+ },
97
+ defaultVariants: {
98
+ checked: false,
99
+ },
100
+ });
101
+
102
+ export const uiCheckboxContentClass = css({
103
+ display: "grid",
104
+ gap: "1",
105
+ minWidth: "0",
106
+ });
107
+
108
+ export const uiCheckboxLabelClass = css({
109
+ fontSize: "sm",
110
+ fontWeight: "700",
111
+ lineHeight: "1.45",
112
+ });
113
+
114
+ export const uiCheckboxDescriptionClass = css({
115
+ color: "text.secondary",
116
+ fontSize: "sm",
117
+ lineHeight: "1.55",
118
+ });