@fiscozen/card 0.1.6 → 1.0.0-beta.2
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/dist/card.js +116 -71
- package/dist/card.umd.cjs +1 -1
- package/dist/src/FzCard.vue.d.ts +20 -4
- package/dist/src/types.d.ts +46 -6
- package/package.json +3 -3
- package/src/FzCard.vue +168 -79
- package/src/__test__/FzCard.test.ts +518 -135
- package/src/__test__/__snapshots__/FzCard.test.ts.snap +9 -6
- package/src/types.ts +38 -1
- package/tsconfig.tsbuildinfo +1 -1
package/src/FzCard.vue
CHANGED
|
@@ -1,81 +1,82 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<section :class="[sectionStaticClass, backgroundColor, textColor]">
|
|
3
|
-
<header
|
|
4
|
-
v-if="existHeader"
|
|
5
|
-
:class="[headerContainerComputedClass, borderColor]"
|
|
6
|
-
@click="toggleOpen"
|
|
7
|
-
>
|
|
8
|
-
<div :class="headerStaticClass">
|
|
9
|
-
<div class="flex flex-row gap-12 items-center">
|
|
10
|
-
<h2
|
|
11
|
-
v-if="title"
|
|
12
|
-
class="text-core-black font-medium text-2xl m-0 p-0 break-words"
|
|
13
|
-
:title="title"
|
|
14
|
-
>
|
|
15
|
-
{{ title }}
|
|
16
|
-
</h2>
|
|
17
|
-
<slot name="header"></slot>
|
|
18
|
-
</div>
|
|
19
|
-
<FzIconButton
|
|
20
|
-
v-if="collapsible"
|
|
21
|
-
:iconName="isOpen ? 'chevron-up' : 'chevron-down'"
|
|
22
|
-
variant="invisible"
|
|
23
|
-
/>
|
|
24
|
-
</div>
|
|
25
|
-
<slot name="header-content"></slot>
|
|
26
|
-
</header>
|
|
27
|
-
<article
|
|
28
|
-
v-if="isAlive"
|
|
29
|
-
:class="['p-20', contentClass]"
|
|
30
|
-
v-show="showContent"
|
|
31
|
-
>
|
|
32
|
-
<slot></slot>
|
|
33
|
-
</article>
|
|
34
|
-
<footer
|
|
35
|
-
v-if="(slots.footer || atLeastOneButton) && isAlive"
|
|
36
|
-
:class="[footerStaticClass, borderColor]"
|
|
37
|
-
v-show="showContent"
|
|
38
|
-
>
|
|
39
|
-
<slot name="footer">
|
|
40
|
-
<FzIconButton
|
|
41
|
-
v-if="tertiaryAction"
|
|
42
|
-
@click="emit('fztertiary:click')"
|
|
43
|
-
:iconName="tertiaryAction.icon"
|
|
44
|
-
variant="invisible"
|
|
45
|
-
/>
|
|
46
|
-
<FzButton
|
|
47
|
-
v-if="secondaryAction"
|
|
48
|
-
@click="emit('fzsecondary:click')"
|
|
49
|
-
:label="secondaryAction.label"
|
|
50
|
-
variant="secondary"
|
|
51
|
-
/>
|
|
52
|
-
<FzButton
|
|
53
|
-
v-if="primaryAction"
|
|
54
|
-
@click="emit('fzprimary:click')"
|
|
55
|
-
:label="primaryAction.label"
|
|
56
|
-
variant="primary"
|
|
57
|
-
/>
|
|
58
|
-
</slot>
|
|
59
|
-
</footer>
|
|
60
|
-
</section>
|
|
61
|
-
</template>
|
|
62
|
-
|
|
63
1
|
<script setup lang="ts">
|
|
64
|
-
|
|
2
|
+
/**
|
|
3
|
+
* FzCard Component
|
|
4
|
+
*
|
|
5
|
+
* A flexible container used to group related content and actions. Cards can include
|
|
6
|
+
* elements like text, images or tables. It also has a set of buttons related to the
|
|
7
|
+
* content, but other can be added inside the main area.
|
|
8
|
+
*
|
|
9
|
+
* Supports multiple color variants, collapsible functionality, and customizable actions.
|
|
10
|
+
* The card can be divided into header, main content area, and footer sections.
|
|
11
|
+
*
|
|
12
|
+
* @component
|
|
13
|
+
* @example
|
|
14
|
+
* <FzCard title="Card Title" color="default">
|
|
15
|
+
* <p>Card content goes here</p>
|
|
16
|
+
* </FzCard>
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* <FzCard
|
|
20
|
+
* title="Collapsible Card"
|
|
21
|
+
* collapsible
|
|
22
|
+
* :primaryAction="{ label: 'Save' }"
|
|
23
|
+
* >
|
|
24
|
+
* <p>This card can be collapsed</p>
|
|
25
|
+
* </FzCard>
|
|
26
|
+
*/
|
|
27
|
+
import { computed, onMounted, ref, watch } from "vue";
|
|
65
28
|
import { FzCardEvents, FzCardProps, FzCardSlots } from "./types";
|
|
66
29
|
import { FzButton, FzIconButton } from "@fiscozen/button";
|
|
67
30
|
|
|
68
|
-
const props = defineProps<FzCardProps>()
|
|
31
|
+
const props = withDefaults(defineProps<FzCardProps>(), {
|
|
32
|
+
environment: 'frontoffice'
|
|
33
|
+
});
|
|
69
34
|
const emit = defineEmits<FzCardEvents>();
|
|
70
35
|
const slots = defineSlots<FzCardSlots>();
|
|
71
36
|
const isOpen = ref(props.defaultExpanded ?? false);
|
|
72
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Deprecation warning for 'aliceblue' color prop value.
|
|
40
|
+
*
|
|
41
|
+
* Watches for 'aliceblue' color usage and logs warning once on mount or when color changes.
|
|
42
|
+
* Using watch with immediate:true ensures the warning only fires once per component instance.
|
|
43
|
+
*/
|
|
44
|
+
watch(
|
|
45
|
+
() => props.color === "aliceblue",
|
|
46
|
+
(isAliceblue) => {
|
|
47
|
+
if (isAliceblue) {
|
|
48
|
+
console.warn(
|
|
49
|
+
"[FzCard] The color prop value 'aliceblue' is deprecated and will be removed in a future version. " +
|
|
50
|
+
"Please use 'blue' instead. The component will automatically map 'aliceblue' to 'blue' for now."
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{ immediate: true }
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Normalizes deprecated color values to their replacements.
|
|
59
|
+
*
|
|
60
|
+
* Maps deprecated colors to their replacements:
|
|
61
|
+
* - 'aliceblue' → 'blue'
|
|
62
|
+
*
|
|
63
|
+
* This ensures backward compatibility while encouraging migration to new values.
|
|
64
|
+
* Deprecation warnings are handled separately via watch hooks.
|
|
65
|
+
*/
|
|
66
|
+
const normalizedColor = computed(() => {
|
|
67
|
+
if (props.color === "aliceblue") {
|
|
68
|
+
return "blue";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return props.color;
|
|
72
|
+
});
|
|
73
|
+
|
|
73
74
|
const sectionStaticClass =
|
|
74
75
|
"border-1 border-solid border-grey-100 rounded flex flex-col";
|
|
75
76
|
const headerStaticClass =
|
|
76
|
-
"
|
|
77
|
+
"border-solid pt-16 px-16 flex flex-row justify-between";
|
|
77
78
|
const footerStaticClass =
|
|
78
|
-
"
|
|
79
|
+
"border-solid pt-0 px-16 pb-16 flex justify-end gap-12 items-center";
|
|
79
80
|
|
|
80
81
|
const showContent = computed(() => isOpen.value || !props.collapsible);
|
|
81
82
|
const isAlive = computed(() => props.alwaysAlive || showContent.value);
|
|
@@ -83,49 +84,62 @@ const existHeader = computed(
|
|
|
83
84
|
() => props.title || slots["header"] || slots["header-content"],
|
|
84
85
|
);
|
|
85
86
|
const headerContainerComputedClass = computed(() => [
|
|
86
|
-
showContent.value ? "border-b-1" : "border-b-0",
|
|
87
87
|
props.collapsible ? "cursor-pointer" : "",
|
|
88
88
|
]);
|
|
89
89
|
|
|
90
|
+
const headerTitleClass = computed(() => {
|
|
91
|
+
return props.environment === 'backoffice' ? 'py-2' : 'py-8';
|
|
92
|
+
});
|
|
93
|
+
|
|
90
94
|
const backgroundColor = computed(() => {
|
|
91
|
-
switch (
|
|
95
|
+
switch (normalizedColor.value) {
|
|
92
96
|
case "blue":
|
|
93
|
-
return "bg-blue-500";
|
|
94
|
-
case "aliceblue":
|
|
95
97
|
return "bg-background-alice-blue";
|
|
96
98
|
case "orange":
|
|
97
99
|
return "bg-background-seashell";
|
|
98
100
|
case "purple":
|
|
99
101
|
return "bg-background-pale-purple";
|
|
102
|
+
case "grey":
|
|
103
|
+
return "bg-background-white-smoke";
|
|
100
104
|
default:
|
|
101
105
|
return "bg-core-white";
|
|
102
106
|
}
|
|
103
107
|
});
|
|
104
108
|
|
|
105
109
|
const textColor = computed(() => {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return "text-core-black";
|
|
111
|
-
}
|
|
110
|
+
// Note: Based on Figma designs, all color variants use black text
|
|
111
|
+
// The blue-500 background variant was previously using white text but
|
|
112
|
+
// the new alice-blue background uses black text
|
|
113
|
+
return "text-core-black";
|
|
112
114
|
});
|
|
113
115
|
|
|
114
116
|
const borderColor = computed(() => {
|
|
115
|
-
switch (
|
|
117
|
+
switch (normalizedColor.value) {
|
|
116
118
|
case "blue":
|
|
117
|
-
return "border-blue-500";
|
|
118
|
-
case "aliceblue":
|
|
119
119
|
return "border-background-alice-blue";
|
|
120
120
|
case "orange":
|
|
121
121
|
return "border-orange-200";
|
|
122
122
|
case "purple":
|
|
123
123
|
return "border-purple-200";
|
|
124
|
+
case "grey":
|
|
125
|
+
return "border-grey-200";
|
|
124
126
|
default:
|
|
125
127
|
return "border-grey-100";
|
|
126
128
|
}
|
|
127
129
|
});
|
|
128
130
|
|
|
131
|
+
const borderWidth = computed(() => {
|
|
132
|
+
switch (normalizedColor.value) {
|
|
133
|
+
case "blue":
|
|
134
|
+
case "orange":
|
|
135
|
+
case "purple":
|
|
136
|
+
case "grey":
|
|
137
|
+
return "border-0";
|
|
138
|
+
default:
|
|
139
|
+
return "border-1";
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
129
143
|
const atLeastOneButton = computed(
|
|
130
144
|
() =>
|
|
131
145
|
props.primaryAction !== undefined ||
|
|
@@ -155,3 +169,78 @@ defineExpose({
|
|
|
155
169
|
toggleOpen,
|
|
156
170
|
});
|
|
157
171
|
</script>
|
|
172
|
+
|
|
173
|
+
<template>
|
|
174
|
+
<section :class="[sectionStaticClass, backgroundColor, textColor, borderColor, {'pb-16': !showContent}]">
|
|
175
|
+
<header
|
|
176
|
+
v-if="existHeader"
|
|
177
|
+
:class="[headerContainerComputedClass]"
|
|
178
|
+
@click="toggleOpen"
|
|
179
|
+
>
|
|
180
|
+
<div :class="headerStaticClass">
|
|
181
|
+
<div :class="['flex flex-row gap-12 items-start', headerTitleClass]">
|
|
182
|
+
<h2
|
|
183
|
+
v-if="title"
|
|
184
|
+
class="text-core-black font-medium text-xl m-0 p-0 break-words overflow-wrap-anywhere min-w-0 flex-shrink"
|
|
185
|
+
:title="title"
|
|
186
|
+
>
|
|
187
|
+
{{ title }}
|
|
188
|
+
</h2>
|
|
189
|
+
<slot name="header"></slot>
|
|
190
|
+
</div>
|
|
191
|
+
<div class="flex flex-row gap-8 items-start">
|
|
192
|
+
<FzIconButton
|
|
193
|
+
v-if="hasInfoIcon"
|
|
194
|
+
iconName="circle-question"
|
|
195
|
+
variant="invisible"
|
|
196
|
+
:environment="environment"
|
|
197
|
+
@click.stop="emit('fzcard:click-info')"
|
|
198
|
+
/>
|
|
199
|
+
<FzIconButton
|
|
200
|
+
v-if="collapsible"
|
|
201
|
+
:iconName="isOpen ? 'chevron-up' : 'chevron-down'"
|
|
202
|
+
variant="invisible"
|
|
203
|
+
:environment="environment"
|
|
204
|
+
/>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
<slot name="header-content"></slot>
|
|
208
|
+
</header>
|
|
209
|
+
<article
|
|
210
|
+
v-if="isAlive"
|
|
211
|
+
:class="['p-16', contentClass]"
|
|
212
|
+
v-show="showContent"
|
|
213
|
+
>
|
|
214
|
+
<slot></slot>
|
|
215
|
+
</article>
|
|
216
|
+
<footer
|
|
217
|
+
v-if="(slots.footer || atLeastOneButton) && isAlive"
|
|
218
|
+
:class="[footerStaticClass]"
|
|
219
|
+
v-show="showContent"
|
|
220
|
+
>
|
|
221
|
+
<slot name="footer">
|
|
222
|
+
<FzIconButton
|
|
223
|
+
v-if="tertiaryAction"
|
|
224
|
+
@click="emit('fztertiary:click')"
|
|
225
|
+
:iconName="tertiaryAction.icon"
|
|
226
|
+
variant="invisible"
|
|
227
|
+
:environment="environment"
|
|
228
|
+
/>
|
|
229
|
+
<FzButton
|
|
230
|
+
v-if="secondaryAction"
|
|
231
|
+
@click="emit('fzsecondary:click')"
|
|
232
|
+
:label="secondaryAction.label"
|
|
233
|
+
variant="secondary"
|
|
234
|
+
:environment="environment"
|
|
235
|
+
/>
|
|
236
|
+
<FzButton
|
|
237
|
+
v-if="primaryAction"
|
|
238
|
+
@click="emit('fzprimary:click')"
|
|
239
|
+
:label="primaryAction.label"
|
|
240
|
+
variant="primary"
|
|
241
|
+
:environment="environment"
|
|
242
|
+
/>
|
|
243
|
+
</slot>
|
|
244
|
+
</footer>
|
|
245
|
+
</section>
|
|
246
|
+
</template>
|