@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.
- package/README.md +19 -0
- package/dist/chunks/UiSkeleton.vue_vue_type_script_setup_true_lang-Lghyrtms.js +1201 -0
- package/dist/chunks/pageHeader-CcJrPX_8.js +522 -0
- package/dist/components/PageSurface.styles.d.ts +1 -0
- package/dist/components/PageSurface.vue.d.ts +13 -0
- package/dist/components/ResultPanel.styles.d.ts +64 -0
- package/dist/components/ResultPanel.vue.d.ts +16 -0
- package/dist/components/SectionCard.styles.d.ts +13 -0
- package/dist/components/SectionCard.vue.d.ts +27 -0
- package/dist/components/StatusBadge.styles.d.ts +24 -0
- package/dist/components/StatusBadge.vue.d.ts +18 -0
- package/dist/components/UiButton.styles.d.ts +54 -0
- package/dist/components/UiButton.vue.d.ts +28 -0
- package/dist/components/UiCheckbox.styles.d.ts +42 -0
- package/dist/components/UiCheckbox.vue.d.ts +37 -0
- package/dist/components/UiField.styles.d.ts +7 -0
- package/dist/components/UiField.vue.d.ts +22 -0
- package/dist/components/UiLoadingState.styles.d.ts +4 -0
- package/dist/components/UiLoadingState.vue.d.ts +10 -0
- package/dist/components/UiSegmentedControl.styles.d.ts +23 -0
- package/dist/components/UiSegmentedControl.vue.d.ts +14 -0
- package/dist/components/UiSelect.styles.d.ts +104 -0
- package/dist/components/UiSelect.vue.d.ts +35 -0
- package/dist/components/UiSkeleton.styles.d.ts +67 -0
- package/dist/components/UiSkeleton.vue.d.ts +12 -0
- package/dist/components/index.d.ts +11 -0
- package/dist/components.d.ts +1 -0
- package/dist/components.js +14 -0
- package/dist/css.d.ts +2 -0
- package/dist/css.js +1 -0
- package/dist/fonts.css +6 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +65 -0
- package/dist/styles/index.d.ts +20 -0
- package/dist/styles/layouts/cluster.d.ts +27 -0
- package/dist/styles/layouts/page.d.ts +2 -0
- package/dist/styles/layouts/split.d.ts +22 -0
- package/dist/styles/layouts/stack.d.ts +19 -0
- package/dist/styles/patterns/actionToolbar.d.ts +1 -0
- package/dist/styles/patterns/emptyState.d.ts +2 -0
- package/dist/styles/patterns/infoPanel.d.ts +2 -0
- package/dist/styles/patterns/metricGrid.d.ts +22 -0
- package/dist/styles/patterns/pageHeader.d.ts +3 -0
- package/dist/styles/patterns/resultRegion.d.ts +1 -0
- package/dist/styles/patterns/selectableListDetail.d.ts +3 -0
- package/dist/styles/primitives/feedback.d.ts +4 -0
- package/dist/styles/primitives/fields.d.ts +3 -0
- package/dist/styles/primitives/surfaces.d.ts +3 -0
- package/dist/styles/primitives/typography.d.ts +6 -0
- package/dist/styles/recipes/badge.recipe.d.ts +32 -0
- package/dist/styles/recipes/button.recipe.d.ts +51 -0
- package/dist/styles/recipes/card.recipe.d.ts +59 -0
- package/dist/styles/recipes/dropdown.recipe.d.ts +25 -0
- package/dist/styles/recipes/input.recipe.d.ts +32 -0
- package/dist/styles.d.ts +1 -0
- package/dist/styles.js +429 -0
- package/dist/tokens/colors.d.ts +360 -0
- package/dist/tokens/core-colors.d.ts +238 -0
- package/dist/tokens/desktop-colors.d.ts +132 -0
- package/dist/tokens/index.d.ts +7 -0
- package/dist/tokens/motion.d.ts +10 -0
- package/dist/tokens/radius.d.ts +5 -0
- package/dist/tokens/spacing.d.ts +8 -0
- package/dist/tokens/typography.d.ts +10 -0
- package/dist/tokens-core.d.ts +5 -0
- package/dist/tokens-core.js +69 -0
- package/dist/tokens-desktop.d.ts +1 -0
- package/dist/tokens-desktop.js +30 -0
- package/dist/tokens.d.ts +1 -0
- package/dist/tokens.js +16 -0
- package/dist/ui.css +2026 -0
- package/package.json +3 -2
- package/src/components/PageSurface.styles.ts +3 -0
- package/src/components/PageSurface.vue +9 -0
- package/src/components/ResultPanel.styles.ts +108 -0
- package/src/components/ResultPanel.test.ts +22 -0
- package/src/components/ResultPanel.vue +70 -0
- package/src/components/SectionCard.styles.ts +65 -0
- package/src/components/SectionCard.test.ts +22 -0
- package/src/components/SectionCard.vue +51 -0
- package/src/components/StatusBadge.styles.ts +49 -0
- package/src/components/StatusBadge.test.ts +18 -0
- package/src/components/StatusBadge.vue +18 -0
- package/src/components/UiButton.styles.ts +29 -0
- package/src/components/UiButton.test.ts +21 -0
- package/src/components/UiButton.vue +46 -0
- package/src/components/UiCheckbox.styles.ts +118 -0
- package/src/components/UiCheckbox.test.ts +18 -0
- package/src/components/UiCheckbox.vue +72 -0
- package/src/components/UiField.styles.ts +35 -0
- package/src/components/UiField.test.ts +22 -0
- package/src/components/UiField.vue +36 -0
- package/src/components/UiLoadingState.styles.ts +36 -0
- package/src/components/UiLoadingState.vue +34 -0
- package/src/components/UiSegmentedControl.styles.ts +49 -0
- package/src/components/UiSegmentedControl.vue +30 -0
- package/src/components/UiSelect.styles.ts +214 -0
- package/src/components/UiSelect.test.ts +49 -0
- package/src/components/UiSelect.vue +256 -0
- package/src/components/UiSkeleton.styles.ts +93 -0
- package/src/components/UiSkeleton.vue +48 -0
- package/src/components/index.ts +11 -0
- package/src/components.ts +1 -0
- package/src/css/base.css +62 -0
- package/src/css/fonts.css +6 -0
- package/src/css/index.css +2 -0
- package/src/css/storybook.css +15 -0
- package/src/env.d.ts +1 -0
- package/src/index.ts +3 -0
- package/src/stories/feedback/ResultPanel.stories.ts +76 -0
- package/src/stories/feedback/StatusBadge.stories.ts +50 -0
- package/src/stories/feedback/UiLoadingState.stories.ts +52 -0
- package/src/stories/feedback/UiSkeleton.stories.ts +85 -0
- package/src/stories/forms/UiCheckbox.stories.ts +104 -0
- package/src/stories/forms/UiField.stories.ts +87 -0
- package/src/stories/forms/UiSelect.stories.ts +134 -0
- package/src/stories/layout/PageSurface.stories.ts +53 -0
- package/src/stories/layout/SectionCard.stories.ts +85 -0
- package/src/stories/primitives/UiButton.stories.ts +145 -0
- package/src/stories/primitives/UiSegmentedControl.stories.ts +67 -0
- package/src/stories/support/StoryThemeFrame.vue +101 -0
- package/src/stories/support/sources.ts +374 -0
- package/src/stories/support/storyStyles.ts +150 -0
- package/src/styles/README.md +23 -0
- package/src/styles/index.ts +20 -0
- package/src/styles/layouts/cluster.ts +27 -0
- package/src/styles/layouts/page.ts +22 -0
- package/src/styles/layouts/split.ts +26 -0
- package/src/styles/layouts/stack.ts +21 -0
- package/src/styles/patterns/actionToolbar.ts +8 -0
- package/src/styles/patterns/emptyState.ts +23 -0
- package/src/styles/patterns/infoPanel.ts +22 -0
- package/src/styles/patterns/metricGrid.ts +19 -0
- package/src/styles/patterns/pageHeader.ts +19 -0
- package/src/styles/patterns/resultRegion.ts +7 -0
- package/src/styles/patterns/selectableListDetail.ts +21 -0
- package/src/styles/primitives/feedback.ts +23 -0
- package/src/styles/primitives/fields.ts +76 -0
- package/src/styles/primitives/surfaces.ts +52 -0
- package/src/styles/primitives/typography.ts +42 -0
- package/src/styles/recipes/badge.recipe.ts +54 -0
- package/src/styles/recipes/button.recipe.ts +115 -0
- package/src/styles/recipes/card.recipe.ts +64 -0
- package/src/styles/recipes/dropdown.recipe.ts +40 -0
- package/src/styles/recipes/input.recipe.ts +59 -0
- package/src/styles.ts +1 -0
- package/src/test/setup.ts +1 -0
- package/src/tokens/colors.ts +16 -0
- package/src/tokens/core-colors.ts +53 -0
- package/src/tokens/desktop-colors.ts +37 -0
- package/src/tokens/index.ts +8 -0
- package/src/tokens/motion.ts +6 -0
- package/src/tokens/radius.ts +3 -0
- package/src/tokens/spacing.ts +4 -0
- package/src/tokens/typography.ts +6 -0
- package/src/tokens-core.ts +5 -0
- package/src/tokens-desktop.ts +1 -0
- package/src/tokens.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aggc/ui",
|
|
3
|
-
"version": "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,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
|
+
});
|