main
vue 254 lines 5.07 KB
Raw
1 <template>
2 <n-card
3 class="card-entity"
4 content-class="p-0!"
5 :class="[
6 `card-size-${size}`,
7 `card-status-${status}`,
8 { embedded, highlighted, clickable, hoverable, disabled },
9 cardEntityClass
10 ]"
11 >
12 <n-spin
13 :show="loading"
14 :description="loadingDescription"
15 content-class="h-full grow"
16 class="flex h-full flex-col"
17 >
18 <div class="card-entity-wrapper flex h-full flex-col" :class="cardEntityWrapperClass">
19 <div class="main-box flex h-full flex-col" :class="mainBoxClass">
20 <div v-if="$slots.header" class="header-box" :class="headerBoxClass">
21 <slot name="header" />
22 </div>
23
24 <div
25 v-if="!$slots.header && ($slots.headerMain || ($slots.headerExtra && !hideHeaderExtra))"
26 class="header-box flex flex-wrap items-center justify-between"
27 :class="headerBoxClass"
28 >
29 <div>
30 <slot name="headerMain" />
31 </div>
32 <div v-if="$slots.headerExtra && !hideHeaderExtra">
33 <slot name="headerExtra" />
34 </div>
35 </div>
36
37 <div v-if="$slots.default" class="content-box">
38 <slot name="default" />
39 </div>
40 </div>
41
42 <div v-if="$slots.mainExtra && !hideMainExtra" class="extra-box">
43 <slot name="mainExtra" />
44 </div>
45
46 <div v-if="$slots.footer && !hideFooter" class="footer-box" :class="footerBoxClass">
47 <slot name="footer" />
48 </div>
49
50 <div
51 v-if="showFooterMainLayout"
52 class="footer-box flex flex-wrap items-start justify-between"
53 :class="footerBoxClass"
54 >
55 <div>
56 <slot name="footerMain" />
57 </div>
58 <div v-if="$slots.footerExtra && !hideFooterExtra">
59 <slot name="footerExtra" />
60 </div>
61 </div>
62 </div>
63 </n-spin>
64 </n-card>
65 </template>
66
67 <script setup lang="ts">
68 import { NCard, NSpin } from "naive-ui"
69 import { computed, useSlots } from "vue"
70
71 const {
72 size,
73 status,
74 embedded,
75 highlighted,
76 clickable,
77 hoverable,
78 disabled,
79 loading,
80 loadingDescription,
81 hideFooter,
82 hideFooterExtra,
83 hideHeaderExtra,
84 hideMainExtra,
85 mainBoxClass,
86 headerBoxClass,
87 cardEntityClass,
88 cardEntityWrapperClass,
89 footerBoxClass
90 } = defineProps<{
91 size?: "medium" | "small" | "large"
92 status?: "success" | "warning" | "error"
93 embedded?: boolean
94 highlighted?: boolean
95 clickable?: boolean
96 hoverable?: boolean
97 disabled?: boolean
98 loading?: boolean
99 loadingDescription?: string
100 hideFooter?: boolean
101 hideFooterExtra?: boolean
102 hideHeaderExtra?: boolean
103 hideMainExtra?: boolean
104 mainBoxClass?: string
105 headerBoxClass?: string
106 cardEntityClass?: string
107 cardEntityWrapperClass?: string
108 footerBoxClass?: string
109 }>()
110
111 const slots = useSlots()
112
113 const showFooterMainLayout = computed(
114 () => (!slots.footer || hideFooter) && (!!slots.footerMain || (!!slots.footerExtra && !hideFooterExtra))
115 )
116 </script>
117
118 <style lang="scss" scoped>
119 .card-entity {
120 transition: all 0.2s var(--bezier-ease);
121 overflow: hidden;
122
123 .card-entity-wrapper {
124 .main-box {
125 .header-box {
126 font-family: var(--font-family-mono);
127 font-size: 13px;
128 color: var(--fg-secondary-color);
129
130 :deep(.n-button) {
131 font-family: var(--font-family);
132 }
133 }
134 .content-box {
135 word-break: break-word;
136 }
137 }
138
139 .extra-box {
140 border-top: 1px solid var(--border-color);
141 }
142
143 .footer-box {
144 border-top: 1px solid var(--border-color);
145 font-size: 13px;
146 background-color: var(--bg-secondary-color);
147 }
148
149 .main-box,
150 .extra-box,
151 .footer-box {
152 gap: calc(var(--spacing) * 3);
153 padding: calc(var(--spacing) * 4);
154 }
155
156 .main-box {
157 .header-box {
158 gap: calc(var(--spacing) * 3);
159 }
160 }
161 }
162
163 &.card-size-small {
164 .card-entity-wrapper {
165 .main-box,
166 .extra-box,
167 .footer-box {
168 gap: calc(var(--spacing) * 2);
169 padding: calc(var(--spacing) * 3);
170 }
171
172 .main-box {
173 .header-box {
174 gap: calc(var(--spacing) * 2);
175 }
176 }
177 }
178 }
179
180 &.card-size-large {
181 .card-entity-wrapper {
182 .main-box,
183 .extra-box,
184 .footer-box {
185 gap: calc(var(--spacing) * 6);
186 padding: calc(var(--spacing) * 6);
187 }
188
189 .main-box {
190 .header-box {
191 gap: calc(var(--spacing) * 6);
192 }
193 }
194 }
195 }
196
197 &.clickable {
198 cursor: pointer;
199 }
200
201 &.embedded {
202 background-color: var(--bg-secondary-color);
203 border: 1px solid var(--border-color);
204
205 .footer-box {
206 background-color: var(--bg-body-color);
207 }
208 }
209
210 &.card-status-success {
211 background-color: rgba(var(--success-color-rgb) / 0.05);
212 border-color: rgba(var(--success-color-rgb) / 0.3);
213 }
214
215 &.card-status-warning {
216 background-color: rgba(var(--warning-color-rgb) / 0.05);
217 border-color: rgba(var(--warning-color-rgb) / 0.3);
218 }
219
220 &.card-status-error {
221 background-color: rgba(var(--error-color-rgb) / 0.05);
222 border-color: rgba(var(--error-color-rgb) / 0.3);
223 }
224
225 &.hoverable {
226 &:not(.disabled) {
227 &:hover {
228 border-color: rgba(var(--primary-color-rgb) / 0.4);
229 }
230 }
231 }
232
233 &.highlighted {
234 background-color: rgba(var(--primary-color-rgb) / 0.05);
235 border-color: rgba(var(--primary-color-rgb) / 0.3);
236
237 &:not(.disabled) {
238 &.hoverable {
239 &:hover {
240 box-shadow: 0px 0px 0px 1px var(--primary-color);
241 }
242 }
243 }
244 }
245
246 &.disabled {
247 cursor: not-allowed;
248
249 .card-entity-wrapper {
250 opacity: 70%;
251 }
252 }
253 }
254 </style>