@appscode/design-system 2.17.86 → 2.17.91-alpha.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.
- package/package.json +1 -1
- package/vue-components/index.ts +18 -0
- package/vue-components/styles/base/utilities/_grid.scss +22 -0
- package/vue-components/styles/base/utilities/_typography.scss +30 -0
- package/vue-components/styles/components/_badge-tags.scss +10 -8
- package/vue-components/styles/components/charts/_recoverable-window.scss +15 -0
- package/vue-components/v3/button/SegmentedControl.vue +64 -0
- package/vue-components/v3/cards/ConfirmNameCard.vue +201 -0
- package/vue-components/v3/cards/DetailCard.vue +58 -0
- package/vue-components/v3/cards/RadioCard.vue +162 -0
- package/vue-components/v3/cards/StatCard.vue +50 -0
- package/vue-components/v3/cards/SummaryCard.vue +176 -0
- package/vue-components/v3/cards/TableCard.vue +61 -0
- package/vue-components/v3/charts/RecoverableWindow.vue +240 -0
- package/vue-components/v3/charts/RecoverableWindowTimeline.vue +953 -0
- package/vue-components/v3/charts/ScheduledMarker.vue +18 -0
- package/vue-components/v3/charts/TimelineTrack.vue +405 -0
- package/vue-components/v3/charts/timeline.ts +142 -0
- package/vue-components/v3/charts/types.ts +51 -0
- package/vue-components/v3/navbar/ThemeMode.vue +23 -2
package/package.json
CHANGED
package/vue-components/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ import AcBreadcrumb from "./v3/breadcrumbs/Breadcrumb.vue";
|
|
|
16
16
|
import AcButton from "./v3/button/Button.vue";
|
|
17
17
|
import AcButtons from "./v3/button/Buttons.vue";
|
|
18
18
|
import AcDownloadBtn from "./v3/button/DownloadBtn.vue";
|
|
19
|
+
import AcSegmentedControl from "./v3/button/SegmentedControl.vue";
|
|
19
20
|
|
|
20
21
|
import AcCard from "./v3/cards/Card.vue";
|
|
21
22
|
import AcCardContent from "./v3/cards/CardContent.vue";
|
|
@@ -23,6 +24,8 @@ import AcCardHeader from "./v3/cards/CardHeader.vue";
|
|
|
23
24
|
import AcCards from "../design/cards/Cards.vue";
|
|
24
25
|
import AcCluster from "./v3/cards/Cluster.vue";
|
|
25
26
|
import AcCounter from "./v3/cards/Counter.vue";
|
|
27
|
+
import AcDetailCard from "./v3/cards/DetailCard.vue";
|
|
28
|
+
import AcTableCard from "./v3/cards/TableCard.vue";
|
|
26
29
|
import AcFeatureCard from "./v3/cards/FeatureCard.vue";
|
|
27
30
|
import AcFeatureCards from "./v3/cards/FeatureCards.vue";
|
|
28
31
|
import AcInfoCard from "./v3/cards/InfoCard.vue";
|
|
@@ -31,9 +34,14 @@ import AcOrgCard from "./v3/cards/OrgCard.vue";
|
|
|
31
34
|
import AcOverviewCard from "./v3/cards/OverviewCard.vue";
|
|
32
35
|
import AcOverviewCards from "./v3/cards/OverviewCards.vue";
|
|
33
36
|
import AcPayment from "./v3/cards/Payment.vue";
|
|
37
|
+
import AcRadioCard from "./v3/cards/RadioCard.vue";
|
|
34
38
|
import AcSessionCard from "./v3/cards/SessionCard.vue";
|
|
39
|
+
import AcStatCard from "./v3/cards/StatCard.vue";
|
|
35
40
|
import AcVendor from "./v3/cards/Vendor.vue";
|
|
36
41
|
|
|
42
|
+
import AcRecoverableWindow from "./v3/charts/RecoverableWindow.vue";
|
|
43
|
+
import AcTimelineTrack from "./v3/charts/TimelineTrack.vue";
|
|
44
|
+
|
|
37
45
|
import AcCodeBlock from "./v3/code-preview/CodeBlock.vue";
|
|
38
46
|
import AcCodeGroup from "./v3/code-preview/CodeGroup.vue";
|
|
39
47
|
|
|
@@ -192,6 +200,9 @@ import AcTags from "./v3/tag/Tags.vue";
|
|
|
192
200
|
|
|
193
201
|
import AcSimpleSelect from "./v3/form-fields/SimpleSelect.vue";
|
|
194
202
|
|
|
203
|
+
export * from "./v3/charts/types";
|
|
204
|
+
export { defaultRangeOptions } from "./v3/charts/timeline";
|
|
205
|
+
|
|
195
206
|
export {
|
|
196
207
|
AcAccordion,
|
|
197
208
|
AcAlert,
|
|
@@ -204,13 +215,19 @@ export {
|
|
|
204
215
|
AcBreadcrumb,
|
|
205
216
|
AcButton,
|
|
206
217
|
AcButtons,
|
|
218
|
+
AcRadioCard,
|
|
219
|
+
AcRecoverableWindow,
|
|
220
|
+
AcTimelineTrack,
|
|
207
221
|
AcDownloadBtn,
|
|
222
|
+
AcSegmentedControl,
|
|
208
223
|
AcCard,
|
|
209
224
|
AcCardContent,
|
|
210
225
|
AcCardHeader,
|
|
211
226
|
AcCards,
|
|
212
227
|
AcCluster,
|
|
213
228
|
AcCounter,
|
|
229
|
+
AcDetailCard,
|
|
230
|
+
AcTableCard,
|
|
214
231
|
AcFeatureCard,
|
|
215
232
|
AcFeatureCards,
|
|
216
233
|
AcInfoCard,
|
|
@@ -220,6 +237,7 @@ export {
|
|
|
220
237
|
AcOverviewCards,
|
|
221
238
|
AcPayment,
|
|
222
239
|
AcSessionCard,
|
|
240
|
+
AcStatCard,
|
|
223
241
|
AcVendor,
|
|
224
242
|
AcCodeBlock,
|
|
225
243
|
AcCodeGroup,
|
|
@@ -27,3 +27,25 @@
|
|
|
27
27
|
gap: #{$i}px;
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
// Responsive grid utilities (mobile-first, Bulma breakpoints)
|
|
32
|
+
// e.g. "grid grid-cols-1 grid-cols-2-tablet grid-cols-4-desktop"
|
|
33
|
+
$grid-breakpoints: (
|
|
34
|
+
"tablet": 769px,
|
|
35
|
+
"desktop": 1024px,
|
|
36
|
+
"widescreen": 1216px,
|
|
37
|
+
"fullhd": 1408px,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
@each $bp, $min in $grid-breakpoints {
|
|
41
|
+
@media screen and (min-width: $min) {
|
|
42
|
+
@for $i from 1 through 12 {
|
|
43
|
+
.grid-cols-#{$i}-#{$bp} {
|
|
44
|
+
grid-template-columns: repeat(#{$i}, minmax(0, 1fr));
|
|
45
|
+
}
|
|
46
|
+
.col-span-#{$i}-#{$bp} {
|
|
47
|
+
grid-column: span #{$i} / span #{$i};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -158,6 +158,36 @@ strong {
|
|
|
158
158
|
font-weight: $weight-bold;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
// Letter spacing utilities
|
|
162
|
+
$letter-spacings: (
|
|
163
|
+
"tighter": -0.05em,
|
|
164
|
+
"tight": -0.025em,
|
|
165
|
+
"normal": 0,
|
|
166
|
+
"wide": 0.025em,
|
|
167
|
+
"wider": 0.05em,
|
|
168
|
+
"widest": 0.1em,
|
|
169
|
+
);
|
|
170
|
+
@each $key, $value in $letter-spacings {
|
|
171
|
+
.tracking-#{$key} {
|
|
172
|
+
letter-spacing: $value;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Line height utilities
|
|
177
|
+
$line-heights: (
|
|
178
|
+
"none": 1,
|
|
179
|
+
"tight": 1.25,
|
|
180
|
+
"snug": 1.375,
|
|
181
|
+
"normal": 1.5,
|
|
182
|
+
"relaxed": 1.625,
|
|
183
|
+
"loose": 2,
|
|
184
|
+
);
|
|
185
|
+
@each $key, $value in $line-heights {
|
|
186
|
+
.leading-#{$key} {
|
|
187
|
+
line-height: $value;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
161
191
|
.material-icons {
|
|
162
192
|
font-size: 1em;
|
|
163
193
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@mixin badge($text-color, $bg-color, $text-dark, $bg-light) {
|
|
1
|
+
@mixin badge($text-color, $bg-color, $text-dark, $bg-light, $border-light) {
|
|
2
2
|
color: $text-color;
|
|
3
3
|
background-color: $bg-color;
|
|
4
4
|
.circle {
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
&.is-light {
|
|
8
8
|
background-color: $bg-light;
|
|
9
9
|
color: $text-dark;
|
|
10
|
+
border: 1px solid $border-light;
|
|
11
|
+
padding: 3px 7px; // keep same size as borderless tags
|
|
10
12
|
.circle {
|
|
11
13
|
background-color: $bg-color;
|
|
12
14
|
}
|
|
@@ -48,25 +50,25 @@
|
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
&.is-primary {
|
|
51
|
-
@include badge($white-100, $ac-primary, $primary-10, $primary-95);
|
|
53
|
+
@include badge($white-100, $ac-primary, $primary-10, $primary-95, $primary-80);
|
|
52
54
|
}
|
|
53
55
|
&.is-secondary {
|
|
54
|
-
@include badge($white-100, $ac-secondary, $secondary-10, $secondary-95);
|
|
56
|
+
@include badge($white-100, $ac-secondary, $secondary-10, $secondary-95, $secondary-80);
|
|
55
57
|
}
|
|
56
58
|
&.is-info {
|
|
57
|
-
@include badge($white-100, $info, $blue-10, $blue-95);
|
|
59
|
+
@include badge($white-100, $info, $blue-10, $blue-95, $blue-80);
|
|
58
60
|
}
|
|
59
61
|
&.is-success {
|
|
60
|
-
@include badge($white-100, $success, $green-10, $green-95);
|
|
62
|
+
@include badge($white-100, $success, $green-10, $green-95, $green-80);
|
|
61
63
|
}
|
|
62
64
|
&.is-warning {
|
|
63
|
-
@include badge($white-100, $warning, $yellow-10, $yellow-95);
|
|
65
|
+
@include badge($white-100, $warning, $yellow-10, $yellow-95, $yellow-80);
|
|
64
66
|
}
|
|
65
67
|
&.is-danger {
|
|
66
|
-
@include badge($white-100, $danger, $red-10, $red-95);
|
|
68
|
+
@include badge($white-100, $danger, $red-10, $red-95, $red-80);
|
|
67
69
|
}
|
|
68
70
|
&.is-dark {
|
|
69
|
-
@include badge($white-100, $gray, $gray-10, $gray-95);
|
|
71
|
+
@include badge($white-100, $gray, $gray-10, $gray-95, $gray-80);
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
.circle {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
$rw-pitr-start: #059669;
|
|
2
|
+
$rw-pitr-end: #34d399;
|
|
3
|
+
$rw-pitr-legend: #10b981;
|
|
4
|
+
$rw-snapshot: #00994a;
|
|
5
|
+
$rw-base-divider: #166534;
|
|
6
|
+
$rw-failed: #dc2626;
|
|
7
|
+
$rw-scheduled: #9ca3af;
|
|
8
|
+
$rw-running: #6ee7b7;
|
|
9
|
+
$rw-rail: #f3f4f6;
|
|
10
|
+
$rw-rail-border: #e5e7eb;
|
|
11
|
+
$rw-axis-text: #9ca3af;
|
|
12
|
+
$rw-guide: rgba(255, 255, 255, 0.35);
|
|
13
|
+
$rw-dot-success: #10b981;
|
|
14
|
+
$rw-dot-warning: #f97316;
|
|
15
|
+
$rw-dot-danger: #dc2626;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from "vue";
|
|
3
|
+
import AcButton from "./Button.vue";
|
|
4
|
+
|
|
5
|
+
const props = withDefaults(defineProps<{ editable?: boolean }>(), {
|
|
6
|
+
editable: false,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const options = defineModel<string[]>("options", { required: true });
|
|
10
|
+
const model = defineModel<string>();
|
|
11
|
+
|
|
12
|
+
const editingIndex = ref(-1);
|
|
13
|
+
const draft = ref("");
|
|
14
|
+
|
|
15
|
+
const vFocus = {
|
|
16
|
+
mounted: (el: HTMLInputElement) => {
|
|
17
|
+
el.focus();
|
|
18
|
+
el.select();
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function onClick(index: number) {
|
|
23
|
+
if (props.editable && options.value[index] === model.value) {
|
|
24
|
+
draft.value = options.value[index];
|
|
25
|
+
editingIndex.value = index;
|
|
26
|
+
} else {
|
|
27
|
+
model.value = options.value[index];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function save() {
|
|
32
|
+
const index = editingIndex.value;
|
|
33
|
+
const value = draft.value.trim();
|
|
34
|
+
editingIndex.value = -1;
|
|
35
|
+
if (index === -1 || !value || options.value.includes(value)) return;
|
|
36
|
+
options.value = options.value.map((option, i) => (i === index ? value : option));
|
|
37
|
+
model.value = value;
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<div class="buttons has-addons has-gap mb-0">
|
|
43
|
+
<template v-for="(option, index) in options" :key="index">
|
|
44
|
+
<input
|
|
45
|
+
v-if="index === editingIndex"
|
|
46
|
+
v-model="draft"
|
|
47
|
+
v-focus
|
|
48
|
+
class="input height-24 width-48 py-0 has-text-centered"
|
|
49
|
+
@keydown.enter.prevent="save"
|
|
50
|
+
@keydown.esc="editingIndex = -1"
|
|
51
|
+
@blur="save"
|
|
52
|
+
/>
|
|
53
|
+
<AcButton
|
|
54
|
+
v-else
|
|
55
|
+
type="button"
|
|
56
|
+
:title="option"
|
|
57
|
+
:tooltip="editable && option === model ? 'Click to edit' : undefined"
|
|
58
|
+
:aria-pressed="option === model"
|
|
59
|
+
:modifier-classes="option === model ? 'is-tinny has-text-weight-bold' : 'is-tinny has-text-grey'"
|
|
60
|
+
@click="onClick(index)"
|
|
61
|
+
/>
|
|
62
|
+
</template>
|
|
63
|
+
</div>
|
|
64
|
+
</template>
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, watch } from "vue";
|
|
3
|
+
import CheckIcon from "../icons/CheckIcon.vue";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
// the value the user has to type to confirm
|
|
7
|
+
name: string;
|
|
8
|
+
// text around the highlighted name: "{prefix} <name> {suffix}"
|
|
9
|
+
prefix?: string;
|
|
10
|
+
suffix?: string;
|
|
11
|
+
required?: boolean;
|
|
12
|
+
placeholder?: string;
|
|
13
|
+
confirmedText?: string;
|
|
14
|
+
// warning note under the input; hidden when empty
|
|
15
|
+
hint?: string;
|
|
16
|
+
caseSensitive?: boolean;
|
|
17
|
+
// ignore leading/trailing whitespace while comparing
|
|
18
|
+
trim?: boolean;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
id?: string;
|
|
21
|
+
modifierClasses?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
25
|
+
prefix: "Type",
|
|
26
|
+
suffix: "to confirm",
|
|
27
|
+
required: true,
|
|
28
|
+
placeholder: "",
|
|
29
|
+
confirmedText: "Identity Confirmed",
|
|
30
|
+
hint: "",
|
|
31
|
+
caseSensitive: true,
|
|
32
|
+
trim: false,
|
|
33
|
+
disabled: false,
|
|
34
|
+
id: "",
|
|
35
|
+
modifierClasses: "",
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const emit = defineEmits<{
|
|
39
|
+
confirmed: [isConfirmed: boolean];
|
|
40
|
+
}>();
|
|
41
|
+
|
|
42
|
+
// the typed value
|
|
43
|
+
const input = defineModel<string>({ default: "" });
|
|
44
|
+
|
|
45
|
+
const inputId = computed(() => props.id || `confirm-name-${props.name.toLowerCase().replace(/\s+/g, "-")}`);
|
|
46
|
+
|
|
47
|
+
const normalize = (value: string) => {
|
|
48
|
+
const v = props.trim ? value.trim() : value;
|
|
49
|
+
return props.caseSensitive ? v : v.toLowerCase();
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const isConfirmed = computed(() => !!props.name && normalize(input.value) === normalize(props.name));
|
|
53
|
+
|
|
54
|
+
watch(isConfirmed, (value) => emit("confirmed", value), { immediate: true });
|
|
55
|
+
|
|
56
|
+
defineExpose({ isConfirmed });
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<template>
|
|
60
|
+
<div class="confirm-name-card" :class="[modifierClasses, { 'is-confirmed': isConfirmed, 'is-disabled': disabled }]">
|
|
61
|
+
<label :for="inputId" class="cnc-label">
|
|
62
|
+
{{ prefix }} <code class="cnc-name">{{ name }}</code> {{ suffix }}
|
|
63
|
+
<span v-if="required" class="cnc-required">*</span>
|
|
64
|
+
</label>
|
|
65
|
+
|
|
66
|
+
<div class="cnc-field is-flex is-align-items-center is-flex-wrap-wrap gap-16">
|
|
67
|
+
<input
|
|
68
|
+
:id="inputId"
|
|
69
|
+
v-model="input"
|
|
70
|
+
type="text"
|
|
71
|
+
class="cnc-input"
|
|
72
|
+
autocomplete="off"
|
|
73
|
+
autocapitalize="off"
|
|
74
|
+
spellcheck="false"
|
|
75
|
+
:placeholder="placeholder || name"
|
|
76
|
+
:required="required"
|
|
77
|
+
:disabled="disabled"
|
|
78
|
+
:aria-invalid="!!input && !isConfirmed"
|
|
79
|
+
/>
|
|
80
|
+
<span v-if="isConfirmed" class="cnc-badge" role="status">
|
|
81
|
+
<CheckIcon class="cnc-badge-icon" />
|
|
82
|
+
{{ confirmedText }}
|
|
83
|
+
</span>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<p v-if="hint || $slots.hint" class="cnc-hint is-flex is-align-items-center gap-8 mb-0">
|
|
87
|
+
<svg class="cnc-hint-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true">
|
|
88
|
+
<path
|
|
89
|
+
fill="none"
|
|
90
|
+
stroke="currentColor"
|
|
91
|
+
stroke-linecap="round"
|
|
92
|
+
stroke-linejoin="round"
|
|
93
|
+
stroke-width="2"
|
|
94
|
+
d="M12 9v4m0 4h.01M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0"
|
|
95
|
+
/>
|
|
96
|
+
</svg>
|
|
97
|
+
<slot name="hint">{{ hint }}</slot>
|
|
98
|
+
</p>
|
|
99
|
+
</div>
|
|
100
|
+
</template>
|
|
101
|
+
|
|
102
|
+
<style scoped lang="scss">
|
|
103
|
+
.confirm-name-card {
|
|
104
|
+
--cnc-mono: "Inconsolata", monospace;
|
|
105
|
+
|
|
106
|
+
padding: 24px 28px;
|
|
107
|
+
border: 1px solid $red-95;
|
|
108
|
+
border-radius: 12px;
|
|
109
|
+
background-color: $red-97;
|
|
110
|
+
|
|
111
|
+
&.is-disabled {
|
|
112
|
+
opacity: 0.6;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.cnc-label {
|
|
117
|
+
display: block;
|
|
118
|
+
margin-bottom: 16px;
|
|
119
|
+
font-size: 15px;
|
|
120
|
+
font-weight: 600;
|
|
121
|
+
line-height: 1.8;
|
|
122
|
+
color: $color-heading;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.cnc-name {
|
|
126
|
+
padding: 2px 8px;
|
|
127
|
+
border: 1px solid $red-90;
|
|
128
|
+
border-radius: 6px;
|
|
129
|
+
font-family: var(--cnc-mono);
|
|
130
|
+
font-size: 14px;
|
|
131
|
+
font-weight: 700;
|
|
132
|
+
color: $danger;
|
|
133
|
+
background-color: $red-95;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.cnc-required {
|
|
137
|
+
color: $danger;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.cnc-input {
|
|
141
|
+
width: 100%;
|
|
142
|
+
max-width: 384px;
|
|
143
|
+
height: 44px;
|
|
144
|
+
padding: 0 16px;
|
|
145
|
+
border: 1px solid $color-border;
|
|
146
|
+
border-radius: 8px;
|
|
147
|
+
font-family: var(--cnc-mono);
|
|
148
|
+
font-size: 15px;
|
|
149
|
+
color: $color-heading;
|
|
150
|
+
background-color: $white-100;
|
|
151
|
+
outline: none;
|
|
152
|
+
transition: border-color 0.2s;
|
|
153
|
+
|
|
154
|
+
&:focus {
|
|
155
|
+
border-color: $color-border-dark;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
&[aria-invalid="true"] {
|
|
159
|
+
border-color: $red-80;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.is-confirmed & {
|
|
163
|
+
border-color: $green-80;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
&:disabled {
|
|
167
|
+
cursor: not-allowed;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.cnc-badge {
|
|
172
|
+
display: inline-flex;
|
|
173
|
+
align-items: center;
|
|
174
|
+
gap: 6px;
|
|
175
|
+
padding: 6px 12px;
|
|
176
|
+
border: 1px solid $green-80;
|
|
177
|
+
border-radius: 4px;
|
|
178
|
+
font-size: 14px;
|
|
179
|
+
font-weight: 600;
|
|
180
|
+
color: $success;
|
|
181
|
+
background-color: $green-95;
|
|
182
|
+
|
|
183
|
+
.cnc-badge-icon {
|
|
184
|
+
width: 16px;
|
|
185
|
+
height: 16px;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.cnc-hint {
|
|
190
|
+
margin-top: 16px;
|
|
191
|
+
font-size: 13px;
|
|
192
|
+
color: $color-label;
|
|
193
|
+
|
|
194
|
+
.cnc-hint-icon {
|
|
195
|
+
flex-shrink: 0;
|
|
196
|
+
width: 18px;
|
|
197
|
+
height: 18px;
|
|
198
|
+
color: $yellow-40;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
</style>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import AcDivider from "../divider/Divider.vue";
|
|
3
|
+
|
|
4
|
+
interface Field {
|
|
5
|
+
label: string;
|
|
6
|
+
value?: string | number;
|
|
7
|
+
key?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
withDefaults(
|
|
11
|
+
defineProps<{
|
|
12
|
+
title: string;
|
|
13
|
+
fields?: Field[];
|
|
14
|
+
columns?: number;
|
|
15
|
+
modifierClasses?: string;
|
|
16
|
+
}>(),
|
|
17
|
+
{
|
|
18
|
+
fields: () => [],
|
|
19
|
+
columns: 2,
|
|
20
|
+
modifierClasses: "",
|
|
21
|
+
},
|
|
22
|
+
);
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<div class="is-flex is-flex-direction-column p-20 b-1 is-rounded-8 bg-white" :class="modifierClasses">
|
|
27
|
+
<div class="is-flex is-justify-content-space-between is-align-items-center gap-8">
|
|
28
|
+
<div class="is-flex is-align-items-center gap-12">
|
|
29
|
+
<span v-if="$slots.icon" class="icon is-medium is-flex-shrink-0 is-rounded-8 bg-primary-93 has-text-primary">
|
|
30
|
+
<slot name="icon" />
|
|
31
|
+
</span>
|
|
32
|
+
<h5>{{ title }}</h5>
|
|
33
|
+
</div>
|
|
34
|
+
<slot name="header-right" />
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<AcDivider />
|
|
38
|
+
|
|
39
|
+
<div class="is-flex-grow-1">
|
|
40
|
+
<div v-if="fields.length" class="grid gap-16" :class="`grid-cols-${columns}`">
|
|
41
|
+
<div v-for="field in fields" :key="field.label">
|
|
42
|
+
<p class="has-text-grey">{{ field.label }}</p>
|
|
43
|
+
<div class="is-flex is-flex-wrap-wrap is-align-items-center gap-8 has-text-weight-semibold">
|
|
44
|
+
<slot :name="`field-${field.key}`" :field="field">{{ field.value }}</slot>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
<slot />
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<template v-if="$slots.footer">
|
|
52
|
+
<AcDivider />
|
|
53
|
+
<div class="is-flex is-justify-content-space-between is-align-items-center gap-8">
|
|
54
|
+
<slot name="footer" />
|
|
55
|
+
</div>
|
|
56
|
+
</template>
|
|
57
|
+
</div>
|
|
58
|
+
</template>
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
value: string | number;
|
|
6
|
+
label: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
footer?: string;
|
|
10
|
+
status?: string;
|
|
11
|
+
statusTone?: "success" | "warning" | "danger";
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
id?: string;
|
|
14
|
+
modifierClasses?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
18
|
+
name: "radio-card",
|
|
19
|
+
description: "",
|
|
20
|
+
footer: "",
|
|
21
|
+
status: "",
|
|
22
|
+
statusTone: "success",
|
|
23
|
+
disabled: false,
|
|
24
|
+
id: "",
|
|
25
|
+
modifierClasses: "",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const selected = defineModel<string | number>();
|
|
29
|
+
|
|
30
|
+
const inputId = computed(() => props.id || `${props.name}-${props.value}`);
|
|
31
|
+
const isSelected = computed(() => selected.value === props.value);
|
|
32
|
+
|
|
33
|
+
function select() {
|
|
34
|
+
if (props.disabled) return;
|
|
35
|
+
selected.value = props.value;
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<template>
|
|
40
|
+
<div
|
|
41
|
+
class="radio-card ac-radio"
|
|
42
|
+
:class="[modifierClasses, { 'is-selected': isSelected, 'is-disabled': disabled }]"
|
|
43
|
+
@click="select"
|
|
44
|
+
>
|
|
45
|
+
<input :id="inputId" v-model="selected" type="radio" :name="name" :value="value" :disabled="disabled" />
|
|
46
|
+
<label :for="inputId">{{ label }}</label>
|
|
47
|
+
|
|
48
|
+
<p v-if="description || $slots.default" class="radio-card__description">
|
|
49
|
+
<slot>{{ description }}</slot>
|
|
50
|
+
</p>
|
|
51
|
+
|
|
52
|
+
<p v-if="status || $slots.status" class="radio-card__status" :class="`radio-card__status--${statusTone}`">
|
|
53
|
+
<slot name="status">
|
|
54
|
+
<span class="radio-card__dot" />
|
|
55
|
+
{{ status }}
|
|
56
|
+
</slot>
|
|
57
|
+
</p>
|
|
58
|
+
|
|
59
|
+
<p v-else-if="footer || $slots.footer" class="radio-card__footer">
|
|
60
|
+
<slot name="footer">{{ footer }}</slot>
|
|
61
|
+
</p>
|
|
62
|
+
</div>
|
|
63
|
+
</template>
|
|
64
|
+
|
|
65
|
+
<style lang="scss" scoped>
|
|
66
|
+
.radio-card {
|
|
67
|
+
display: flex;
|
|
68
|
+
flex-direction: column;
|
|
69
|
+
padding: 16px;
|
|
70
|
+
border: 1px solid $color-border;
|
|
71
|
+
border-radius: 10px;
|
|
72
|
+
background-color: $white-100;
|
|
73
|
+
cursor: pointer;
|
|
74
|
+
transition: 0.2s ease-in-out;
|
|
75
|
+
|
|
76
|
+
label {
|
|
77
|
+
font-size: 14px;
|
|
78
|
+
font-weight: 600;
|
|
79
|
+
color: $slate-10;
|
|
80
|
+
padding-left: 32px;
|
|
81
|
+
line-height: 20px;
|
|
82
|
+
|
|
83
|
+
&::before {
|
|
84
|
+
width: 20px;
|
|
85
|
+
height: 20px;
|
|
86
|
+
border-color: $color-border-dark;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
&::after {
|
|
90
|
+
top: 5px;
|
|
91
|
+
left: 5px;
|
|
92
|
+
width: 10px;
|
|
93
|
+
height: 10px;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
&:hover:not(.is-disabled) {
|
|
98
|
+
border-color: $ac-primary;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
&.is-selected {
|
|
102
|
+
border-color: $ac-primary;
|
|
103
|
+
background-color: $primary-97;
|
|
104
|
+
box-shadow: inset 0 0 0 1px $ac-primary;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
&.is-disabled {
|
|
108
|
+
cursor: not-allowed;
|
|
109
|
+
opacity: 0.6;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.radio-card__description {
|
|
114
|
+
margin: 6px 0 0;
|
|
115
|
+
padding-left: 32px;
|
|
116
|
+
font-size: 14px;
|
|
117
|
+
line-height: 20px;
|
|
118
|
+
color: $slate-40;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.radio-card__footer,
|
|
122
|
+
.radio-card__status {
|
|
123
|
+
display: flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
gap: 6px;
|
|
126
|
+
margin: 16px 0 0;
|
|
127
|
+
padding-top: 0;
|
|
128
|
+
font-size: 12px;
|
|
129
|
+
line-height: 16px;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.radio-card__footer {
|
|
133
|
+
margin-top: auto;
|
|
134
|
+
padding-top: 16px;
|
|
135
|
+
color: $slate-50;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.radio-card__status {
|
|
139
|
+
margin-top: auto;
|
|
140
|
+
padding-top: 16px;
|
|
141
|
+
|
|
142
|
+
&--success {
|
|
143
|
+
color: $ac-primary;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
&--warning {
|
|
147
|
+
color: $warning;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
&--danger {
|
|
151
|
+
color: $danger;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.radio-card__dot {
|
|
156
|
+
width: 6px;
|
|
157
|
+
height: 6px;
|
|
158
|
+
border-radius: 50%;
|
|
159
|
+
background-color: currentColor;
|
|
160
|
+
flex-shrink: 0;
|
|
161
|
+
}
|
|
162
|
+
</style>
|