srcdev-nuxt-components 9.1.43 → 9.1.44
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/.claude/skills/components/display-avatar.md +187 -0
- package/.claude/skills/components/display-chip.md +213 -0
- package/.claude/skills/index.md +2 -0
- package/app/components/01.atoms/display-avatar/DisplayAvatar.vue +130 -0
- package/app/components/{display-avatar → 01.atoms/display-avatar}/stories/DisplayAvatar.stories.ts +12 -0
- package/app/components/01.atoms/display-avatar/tests/DisplayAvatar.spec.ts +208 -0
- package/app/components/01.atoms/display-avatar/tests/__snapshots__/DisplayAvatar.spec.ts.snap +11 -0
- package/app/components/02.molecules/display-chip/DisplayChip.vue +189 -0
- package/app/components/{display-chip → 02.molecules/display-chip}/stories/DisplayChip.stories.ts +37 -53
- package/app/components/02.molecules/display-chip/tests/DisplayChip.spec.ts +191 -0
- package/app/components/02.molecules/display-chip/tests/__snapshots__/DisplayChip.spec.ts.snap +12 -0
- package/app/pages/ui/display-chip.vue +201 -66
- package/package.json +1 -1
- package/app/components/display-avatar/DisplayAvatar.vue +0 -148
- package/app/components/display-chip/DisplayChip.vue +0 -187
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# DisplayAvatar Component
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`DisplayAvatar` renders a circular avatar — either an image (via `NuxtImg`) or a text fallback showing initials derived from the `alt` prop. Optionally wraps in a `DisplayChip` to show a status indicator badge.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Props reference
|
|
10
|
+
|
|
11
|
+
> **Hyphenation rule**: Vue's ESLint config enforces `vue/attribute-hyphenation`. Always write camelCase prop names hyphenated in templates: `:style-class-passthrough`.
|
|
12
|
+
|
|
13
|
+
| Prop (template form) | Type | Default | Notes |
|
|
14
|
+
| -------------------------- | ----------------------------------------- | -------- | ------------------------------------------------------------------ |
|
|
15
|
+
| `as` | `string \| object` | `"span"` | Root element tag. Ignored when `chip` is set. |
|
|
16
|
+
| `src` | `string` | — | Image URL. Renders `NuxtImg` when set; fallback text otherwise. |
|
|
17
|
+
| `alt` | `string` | — | Alt text for the image; also used to derive initials. |
|
|
18
|
+
| `text` | `string` | — | Override the auto-derived initials with an explicit string. |
|
|
19
|
+
| `size` | `"xs" \| "s" \| "md" \| "lg" \| "xl"` | `"md"` | Controls width, height, and font-size. |
|
|
20
|
+
| `chip` | `boolean \| DisplayChipConfig` | — | Add a status chip. `true` uses defaults; pass a config object to customise. |
|
|
21
|
+
| `:style-class-passthrough` | `string \| string[]` | `[]` | Extra CSS classes on the root element. |
|
|
22
|
+
|
|
23
|
+
### Size dimensions
|
|
24
|
+
|
|
25
|
+
| Size | Diameter | Font size |
|
|
26
|
+
| ---- | -------- | --------- |
|
|
27
|
+
| `xs` | 24px | 0.75rem |
|
|
28
|
+
| `s` | 32px | 0.875rem |
|
|
29
|
+
| `md` | 40px | 1rem |
|
|
30
|
+
| `lg` | 48px | 1.125rem |
|
|
31
|
+
| `xl` | 56px | 1.25rem |
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Slots
|
|
36
|
+
|
|
37
|
+
| Slot | Purpose |
|
|
38
|
+
| --------- | -------------------------------------------------------------------- |
|
|
39
|
+
| `default` | Replaces the auto image/fallback content entirely. |
|
|
40
|
+
| `icon` | Appended inside the avatar (e.g. an icon overlay over the image). |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Fallback text logic
|
|
45
|
+
|
|
46
|
+
When `src` is not set, a `<span>` renders the fallback value:
|
|
47
|
+
|
|
48
|
+
1. `text` prop — used as-is if provided.
|
|
49
|
+
2. `alt` initials — first character of each word, capped at two characters.
|
|
50
|
+
3. Empty string — if neither is set.
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
alt="John Doe" → "JD"
|
|
54
|
+
alt="Alice" → "A"
|
|
55
|
+
alt="Alice Bob C" → "AB"
|
|
56
|
+
text="?" → "?"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Usage examples
|
|
62
|
+
|
|
63
|
+
### Image avatar
|
|
64
|
+
|
|
65
|
+
```vue
|
|
66
|
+
<DisplayAvatar
|
|
67
|
+
src="/images/profile.jpg"
|
|
68
|
+
alt="Jane Smith"
|
|
69
|
+
size="lg"
|
|
70
|
+
/>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Initials fallback
|
|
74
|
+
|
|
75
|
+
```vue
|
|
76
|
+
<DisplayAvatar alt="Jane Smith" size="md" />
|
|
77
|
+
<!-- renders: "JS" -->
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Custom text fallback
|
|
81
|
+
|
|
82
|
+
```vue
|
|
83
|
+
<DisplayAvatar text="?" size="xs" />
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Custom root element
|
|
87
|
+
|
|
88
|
+
```vue
|
|
89
|
+
<DisplayAvatar as="div" alt="Jane Smith" />
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### With a status chip (default config)
|
|
93
|
+
|
|
94
|
+
```vue
|
|
95
|
+
<DisplayAvatar
|
|
96
|
+
src="/images/profile.jpg"
|
|
97
|
+
alt="Jane Smith"
|
|
98
|
+
:chip="true"
|
|
99
|
+
/>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Default chip config: `{ size: "12px", maskWidth: "4px", offset: "0px", angle: "90deg" }`.
|
|
103
|
+
|
|
104
|
+
### With a custom chip
|
|
105
|
+
|
|
106
|
+
```vue
|
|
107
|
+
<DisplayAvatar
|
|
108
|
+
src="/images/profile.jpg"
|
|
109
|
+
alt="Jane Smith"
|
|
110
|
+
:chip="{
|
|
111
|
+
size: '16px',
|
|
112
|
+
maskWidth: '2px',
|
|
113
|
+
offset: '4px',
|
|
114
|
+
angle: '45deg'
|
|
115
|
+
}"
|
|
116
|
+
/>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Full `DisplayChipConfig` shape (pass directly as the `chip` value):
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
interface DisplayChipConfig {
|
|
123
|
+
size: string // chip diameter, e.g. "12px"
|
|
124
|
+
maskWidth: string // cutout ring width, e.g. "4px"
|
|
125
|
+
offset: string // distance from avatar edge, e.g. "0px"
|
|
126
|
+
angle: string // position around avatar (0–360deg), e.g. "45deg"
|
|
127
|
+
icon?: string // Iconify icon name
|
|
128
|
+
label?: string // short text (max 3 characters)
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Default slot override
|
|
133
|
+
|
|
134
|
+
```vue
|
|
135
|
+
<DisplayAvatar size="xl">
|
|
136
|
+
<template #default>
|
|
137
|
+
<img src="/images/profile.jpg" alt="Jane Smith" class="avatar-image" />
|
|
138
|
+
</template>
|
|
139
|
+
</DisplayAvatar>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Icon slot
|
|
143
|
+
|
|
144
|
+
```vue
|
|
145
|
+
<DisplayAvatar alt="Jane Smith">
|
|
146
|
+
<template #icon>
|
|
147
|
+
<Icon name="bi:check-circle-fill" class="avatar-icon" />
|
|
148
|
+
</template>
|
|
149
|
+
</DisplayAvatar>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Local style override scaffold
|
|
155
|
+
|
|
156
|
+
```vue
|
|
157
|
+
<DisplayAvatar
|
|
158
|
+
alt="Jane Smith"
|
|
159
|
+
:style-class-passthrough="['profile-avatar']"
|
|
160
|
+
/>
|
|
161
|
+
|
|
162
|
+
<style>
|
|
163
|
+
/* ─── DisplayAvatar local overrides ────────────────────────────────
|
|
164
|
+
Scope by your wrapper class, then nest .display-avatar directly.
|
|
165
|
+
No :deep() needed (component styles are unscoped).
|
|
166
|
+
Delete this block if no overrides are needed.
|
|
167
|
+
─────────────────────────────────────────────────────────────────── */
|
|
168
|
+
.my-page-section {
|
|
169
|
+
.display-avatar {
|
|
170
|
+
&.profile-avatar {
|
|
171
|
+
/* custom overrides */
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
</style>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
See [component-local-style-override.md](../component-local-style-override.md) for the full pattern.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Notes
|
|
183
|
+
|
|
184
|
+
- Auto-imported in Nuxt — no manual import needed.
|
|
185
|
+
- When `chip` is set, the root element becomes `DisplayChip` and the `as` prop is ignored.
|
|
186
|
+
- `class` and `style` are **not** declared as explicit props — they fall through to the root element automatically via Vue's attribute inheritance (`inheritAttrs: true`). Do not re-add them as props; doing so pulls them out of `$attrs` and breaks automatic inheritance.
|
|
187
|
+
- `NuxtImg` is used for the image, so `@nuxt/image` must be installed in the consuming app.
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# DisplayChip Component
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`DisplayChip` renders a small status indicator dot (or icon/label badge) that is absolutely positioned on a parent element using CSS trigonometric functions. It works by applying a radial-gradient mask to the parent's content, creating a clean cutout behind the chip. Supports circle and square parent shapes.
|
|
6
|
+
|
|
7
|
+
Used directly for standalone chip overlays, and internally by `DisplayAvatar` when its `chip` prop is set.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Props reference
|
|
12
|
+
|
|
13
|
+
> **Hyphenation rule**: Vue's ESLint config enforces `vue/attribute-hyphenation`. Always write camelCase prop names hyphenated in templates: `:style-class-passthrough`.
|
|
14
|
+
|
|
15
|
+
| Prop (template form) | Type | Default | Notes |
|
|
16
|
+
| -------------------------- | -------------------------- | ---------- | -------------------------------------------------- |
|
|
17
|
+
| `tag` | `"div" \| "span"` | `"span"` | Root element tag. |
|
|
18
|
+
| `shape` | `"circle" \| "square"` | `"circle"` | Affects position maths — must match the parent shape. |
|
|
19
|
+
| `:config` | `DisplayChipConfig` | see below | Controls chip geometry and optional content. |
|
|
20
|
+
| `:style-class-passthrough` | `string \| string[]` | `[]` | Extra CSS classes — use for status colour variants. |
|
|
21
|
+
|
|
22
|
+
### DisplayChipConfig
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
interface DisplayChipConfig {
|
|
26
|
+
size: string // chip dot diameter, e.g. "12px"
|
|
27
|
+
maskWidth: string // cutout ring width around the chip, e.g. "4px"
|
|
28
|
+
offset: string // extra distance from the parent edge, e.g. "0px"
|
|
29
|
+
angle: string // position around the parent (0–360deg), e.g. "45deg"
|
|
30
|
+
icon?: string // Iconify icon name rendered inside the chip
|
|
31
|
+
label?: string // short text rendered inside the chip (max 3 characters)
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Default config: `{ size: "12px", maskWidth: "4px", offset: "0px", angle: "90deg" }`.
|
|
36
|
+
|
|
37
|
+
### Angle reference
|
|
38
|
+
|
|
39
|
+
| Angle | Position |
|
|
40
|
+
| -------- | ------------ |
|
|
41
|
+
| `0deg` | Top |
|
|
42
|
+
| `45deg` | Top-right |
|
|
43
|
+
| `90deg` | Right |
|
|
44
|
+
| `135deg` | Bottom-right |
|
|
45
|
+
| `180deg` | Bottom |
|
|
46
|
+
| `225deg` | Bottom-left |
|
|
47
|
+
| `270deg` | Left |
|
|
48
|
+
| `315deg` | Top-left |
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Status colours
|
|
53
|
+
|
|
54
|
+
Apply status via `styleClassPassthrough` — the component has built-in colour variants:
|
|
55
|
+
|
|
56
|
+
| Class | Colour |
|
|
57
|
+
| ---------- | ----------------------- |
|
|
58
|
+
| (none) | `slategrey` (offline) |
|
|
59
|
+
| `online` | `rgb(0, 255, 135)` |
|
|
60
|
+
| `idle` | `rgb(255, 185, 51)` |
|
|
61
|
+
| `dnd` | `rgb(255, 40, 80)` |
|
|
62
|
+
|
|
63
|
+
```vue
|
|
64
|
+
<DisplayChip :style-class-passthrough="['online']">...</DisplayChip>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Label constraints
|
|
70
|
+
|
|
71
|
+
- Max 3 characters. Longer values are silently truncated with a `console.warn`.
|
|
72
|
+
- Font-size scales automatically with chip size via `--_font-size-adjust`:
|
|
73
|
+
- 1 char → `0.7 × size`
|
|
74
|
+
- 2 chars → `0.6 × size`
|
|
75
|
+
- 3 chars → `0.5 × size`
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Slots
|
|
80
|
+
|
|
81
|
+
| Slot | Purpose |
|
|
82
|
+
| --------- | ------------------------------------------ |
|
|
83
|
+
| `default` | The host element the chip is positioned on. Must be a single block element. |
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Usage examples
|
|
88
|
+
|
|
89
|
+
### Simple status dot on a circular avatar
|
|
90
|
+
|
|
91
|
+
```vue
|
|
92
|
+
<DisplayChip
|
|
93
|
+
shape="circle"
|
|
94
|
+
:config="{ size: '12px', maskWidth: '4px', offset: '0px', angle: '45deg' }"
|
|
95
|
+
:style-class-passthrough="['online']"
|
|
96
|
+
>
|
|
97
|
+
<div class="avatar">SRC</div>
|
|
98
|
+
</DisplayChip>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Status dot on a square card thumbnail
|
|
102
|
+
|
|
103
|
+
```vue
|
|
104
|
+
<DisplayChip
|
|
105
|
+
shape="square"
|
|
106
|
+
:config="{ size: '10px', maskWidth: '3px', offset: '2px', angle: '135deg' }"
|
|
107
|
+
:style-class-passthrough="['idle']"
|
|
108
|
+
>
|
|
109
|
+
<img src="/thumbnail.jpg" alt="Card thumbnail" />
|
|
110
|
+
</DisplayChip>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### With an icon inside the chip
|
|
114
|
+
|
|
115
|
+
```vue
|
|
116
|
+
<DisplayChip
|
|
117
|
+
:config="{ size: '16px', maskWidth: '4px', offset: '0px', angle: '45deg', icon: 'bi:check-circle-fill' }"
|
|
118
|
+
:style-class-passthrough="['online']"
|
|
119
|
+
>
|
|
120
|
+
<div class="avatar">SRC</div>
|
|
121
|
+
</DisplayChip>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### With a label inside the chip
|
|
125
|
+
|
|
126
|
+
```vue
|
|
127
|
+
<!-- 1–3 characters only; longer values are truncated with a warning -->
|
|
128
|
+
<DisplayChip
|
|
129
|
+
:config="{ size: '16px', maskWidth: '4px', offset: '0px', angle: '45deg', label: '+2' }"
|
|
130
|
+
:style-class-passthrough="['dnd']"
|
|
131
|
+
>
|
|
132
|
+
<div class="avatar">SRC</div>
|
|
133
|
+
</DisplayChip>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Reactive config (QA panel / form pattern)
|
|
137
|
+
|
|
138
|
+
```vue
|
|
139
|
+
<script setup lang="ts">
|
|
140
|
+
import type { DisplayChipConfig } from 'srcdev-nuxt-components/types/components'
|
|
141
|
+
|
|
142
|
+
const size = ref(12)
|
|
143
|
+
const angle = ref(45)
|
|
144
|
+
|
|
145
|
+
const chipConfig = computed((): DisplayChipConfig => ({
|
|
146
|
+
size: `${size.value}px`,
|
|
147
|
+
maskWidth: '4px',
|
|
148
|
+
offset: '0px',
|
|
149
|
+
angle: `${angle.value}deg`,
|
|
150
|
+
}))
|
|
151
|
+
</script>
|
|
152
|
+
|
|
153
|
+
<template>
|
|
154
|
+
<DisplayChip shape="circle" :config="chipConfig" :style-class-passthrough="['online']">
|
|
155
|
+
<div class="avatar">SRC</div>
|
|
156
|
+
</DisplayChip>
|
|
157
|
+
</template>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Via DisplayAvatar (recommended for avatar use cases)
|
|
161
|
+
|
|
162
|
+
Prefer `DisplayAvatar` with its `chip` prop over wiring `DisplayChip` directly:
|
|
163
|
+
|
|
164
|
+
```vue
|
|
165
|
+
<DisplayAvatar
|
|
166
|
+
src="/images/profile.jpg"
|
|
167
|
+
alt="Jane Smith"
|
|
168
|
+
:chip="{ size: '12px', maskWidth: '4px', offset: '0px', angle: '45deg' }"
|
|
169
|
+
:style-class-passthrough="['online']"
|
|
170
|
+
/>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
See [display-avatar.md](./display-avatar.md) for the full API.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Local style override scaffold
|
|
178
|
+
|
|
179
|
+
```vue
|
|
180
|
+
<DisplayChip
|
|
181
|
+
:config="chipConfig"
|
|
182
|
+
:style-class-passthrough="['my-chip']"
|
|
183
|
+
>
|
|
184
|
+
<div class="avatar">SRC</div>
|
|
185
|
+
</DisplayChip>
|
|
186
|
+
|
|
187
|
+
<style>
|
|
188
|
+
/* ─── DisplayChip local overrides ──────────────────────────────────
|
|
189
|
+
Scope by your wrapper class, then nest .display-chip-core directly.
|
|
190
|
+
No :deep() needed (component styles are unscoped).
|
|
191
|
+
Delete this block if no overrides are needed.
|
|
192
|
+
─────────────────────────────────────────────────────────────────── */
|
|
193
|
+
.my-page-section {
|
|
194
|
+
.display-chip-core {
|
|
195
|
+
&.my-chip {
|
|
196
|
+
/* override colour vars, e.g. */
|
|
197
|
+
--color-online: hotpink;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
</style>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Notes
|
|
207
|
+
|
|
208
|
+
- Auto-imported in Nuxt — no manual import needed.
|
|
209
|
+
- `shape` must match the actual shape of the slot content — the position maths differs between `circle` (radius-based) and `square` (clamped corner-aware).
|
|
210
|
+
- `config` values are geometric inputs to CSS `calc(cos())` / `calc(sin())` expressions. Pass them as strings with units (`"12px"`, `"45deg"`), not plain numbers.
|
|
211
|
+
- The chip dot is rendered via `::after` pseudo-element; icon and label sit above it at `z-index: 2`.
|
|
212
|
+
- The mask cutout is applied to all direct children of `.display-chip-core` except `.chip-icon` and `.chip-label` — ensure the host element is a direct child.
|
|
213
|
+
- `DisplayChipConfig` and `DisplayChipProps` are both exported from the layer types. Use `DisplayChipConfig` when passing geometry values (the `config` prop). Use `DisplayChipProps` only if you need to pass the full component prop set (e.g. when building a wrapper component).
|
package/.claude/skills/index.md
CHANGED
|
@@ -78,6 +78,8 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
78
78
|
├── capture-qr-code.md — CaptureQrCode: live camera scanner, error state, visibility/route/KeepAlive lifecycle, media stream cleanup
|
|
79
79
|
├── decode-qr-code.md — DecodeQrCode: file picker + drag-and-drop image decoder, shared results list, CSS override points
|
|
80
80
|
├── auto-grid.md — AutoGrid: auto-fit responsive grid, $slots iteration, --auto-grid-min-col-size/gap tokens, semantic tag + aria
|
|
81
|
+
├── display-avatar.md — DisplayAvatar: circular avatar with image/initials fallback, size variants, chip badge, icon slot, styleClassPassthrough
|
|
82
|
+
├── display-chip.md — DisplayChip: status indicator chip overlay, CSS trig positioning, circle/square shapes, status colours, icon/label content
|
|
81
83
|
├── carousel-flip.md — CarouselFlip: FLIP-animated carousel, carouselDataIds slot API, buttonLayout variants (sides/controls-flanking/controls-grouped-right/overlay), CSS tokens
|
|
82
84
|
└── samaritan-prompt-mixed.md — SamaritanPromptMixed: animated text prompt, typewriter/word-pulse effects, MessageConfig API, aria-live accessibility, CSS tokens
|
|
83
85
|
```
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="props.chip ? DisplayChip : as"
|
|
4
|
+
v-bind="
|
|
5
|
+
props.chip
|
|
6
|
+
? typeof props.chip === 'object'
|
|
7
|
+
? { tag: chipTag, config: props.chip }
|
|
8
|
+
: { tag: chipTag, config: chipDefaultConfig }
|
|
9
|
+
: {}
|
|
10
|
+
"
|
|
11
|
+
class="display-avatar"
|
|
12
|
+
:class="[size, elementClasses]"
|
|
13
|
+
:style-class-passthrough="elementClasses"
|
|
14
|
+
>
|
|
15
|
+
<slot name="default">
|
|
16
|
+
<NuxtImg v-if="src" :src :alt="alt || 'Avatar'" width="100%" height="100%" class="avatar-image" />
|
|
17
|
+
<span v-else>{{ fallback }}</span>
|
|
18
|
+
</slot>
|
|
19
|
+
<slot name="icon"></slot>
|
|
20
|
+
</component>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script setup lang="ts">
|
|
24
|
+
import DisplayChip from "../../02.molecules/display-chip/DisplayChip.vue";
|
|
25
|
+
import type { DisplayChipConfig } from "~/types/components";
|
|
26
|
+
|
|
27
|
+
interface Props {
|
|
28
|
+
as?: string | object;
|
|
29
|
+
src?: string;
|
|
30
|
+
alt?: string;
|
|
31
|
+
text?: string;
|
|
32
|
+
size?: "xs" | "s" | "md" | "lg" | "xl" | string;
|
|
33
|
+
chip?: boolean | DisplayChipConfig;
|
|
34
|
+
styleClassPassthrough?: string | string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
38
|
+
as: "span",
|
|
39
|
+
src: undefined,
|
|
40
|
+
alt: undefined,
|
|
41
|
+
text: undefined,
|
|
42
|
+
size: "md",
|
|
43
|
+
chip: undefined,
|
|
44
|
+
styleClassPassthrough: () => [],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const { elementClasses, resetElementClasses, updateElementClasses } = useStyleClassPassthrough(
|
|
48
|
+
props.styleClassPassthrough
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
if (props.chip && typeof props.chip === "object" && !("styleClassPassthrough" in props.chip)) {
|
|
52
|
+
updateElementClasses(["display-avatar", props.size]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const fallback = computed(
|
|
56
|
+
() =>
|
|
57
|
+
props.text ||
|
|
58
|
+
(props.alt || "")
|
|
59
|
+
.split(" ")
|
|
60
|
+
.map((word) => word.charAt(0))
|
|
61
|
+
.join("")
|
|
62
|
+
.substring(0, 2)
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const chipDefaultConfig: DisplayChipConfig = {
|
|
66
|
+
size: "12px",
|
|
67
|
+
maskWidth: "4px",
|
|
68
|
+
offset: "0px",
|
|
69
|
+
angle: "90deg",
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const chipTag = computed((): "div" | "span" => (props.as === "div" || props.as === "span" ? props.as : "span"));
|
|
73
|
+
|
|
74
|
+
watch(
|
|
75
|
+
() => props.styleClassPassthrough,
|
|
76
|
+
() => {
|
|
77
|
+
resetElementClasses(props.styleClassPassthrough);
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
<style lang="css">
|
|
83
|
+
@layer components {
|
|
84
|
+
.display-avatar {
|
|
85
|
+
display: flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
justify-content: center;
|
|
88
|
+
border-radius: 50%;
|
|
89
|
+
color: var(--slate-03);
|
|
90
|
+
|
|
91
|
+
isolation: isolate;
|
|
92
|
+
|
|
93
|
+
&.xs {
|
|
94
|
+
width: 24px;
|
|
95
|
+
height: 24px;
|
|
96
|
+
font-size: 0.75rem;
|
|
97
|
+
}
|
|
98
|
+
&.s {
|
|
99
|
+
width: 32px;
|
|
100
|
+
height: 32px;
|
|
101
|
+
font-size: 0.875rem;
|
|
102
|
+
}
|
|
103
|
+
&.md {
|
|
104
|
+
width: 40px;
|
|
105
|
+
height: 40px;
|
|
106
|
+
font-size: 1rem;
|
|
107
|
+
}
|
|
108
|
+
&.lg {
|
|
109
|
+
width: 48px;
|
|
110
|
+
height: 48px;
|
|
111
|
+
font-size: 1.125rem;
|
|
112
|
+
}
|
|
113
|
+
&.xl {
|
|
114
|
+
width: 56px;
|
|
115
|
+
height: 56px;
|
|
116
|
+
font-size: 1.25rem;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.avatar-image {
|
|
120
|
+
width: 100%;
|
|
121
|
+
border-radius: 50%;
|
|
122
|
+
object-fit: cover;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.avatar-icon {
|
|
126
|
+
font-size: 24px;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
</style>
|
package/app/components/{display-avatar → 01.atoms/display-avatar}/stories/DisplayAvatar.stories.ts
RENAMED
|
@@ -5,6 +5,14 @@ export default {
|
|
|
5
5
|
title: "Components/UI/DisplayAvatar",
|
|
6
6
|
component: StorybookComponent,
|
|
7
7
|
argTypes: {
|
|
8
|
+
size: {
|
|
9
|
+
control: { type: "inline-radio" },
|
|
10
|
+
options: ["xs", "s", "md", "lg", "xl"],
|
|
11
|
+
description: "Avatar size",
|
|
12
|
+
table: {
|
|
13
|
+
category: "Avatar",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
8
16
|
src: {
|
|
9
17
|
control: { type: "text" },
|
|
10
18
|
description: "Avatar image source URL",
|
|
@@ -61,6 +69,7 @@ export default {
|
|
|
61
69
|
},
|
|
62
70
|
},
|
|
63
71
|
args: {
|
|
72
|
+
size: "md",
|
|
64
73
|
src: "https://github.com/srcdev.png",
|
|
65
74
|
alt: "SrcDev Avatar",
|
|
66
75
|
chipSize: 12,
|
|
@@ -77,7 +86,9 @@ const Template: StoryFn<typeof StorybookComponent> = (args) => ({
|
|
|
77
86
|
return { args };
|
|
78
87
|
},
|
|
79
88
|
template: `
|
|
89
|
+
<div style="display: flex; align-items: center; justify-content: center; height: 100vh;">
|
|
80
90
|
<StorybookComponent
|
|
91
|
+
:size="args.size"
|
|
81
92
|
:src="args.src"
|
|
82
93
|
:alt="args.alt"
|
|
83
94
|
:chip="{
|
|
@@ -88,6 +99,7 @@ const Template: StoryFn<typeof StorybookComponent> = (args) => ({
|
|
|
88
99
|
}"
|
|
89
100
|
:style-class-passthrough="args.styleClassPassthrough"
|
|
90
101
|
/>
|
|
102
|
+
</div>
|
|
91
103
|
`,
|
|
92
104
|
});
|
|
93
105
|
|