@meistrari/tela-build 1.47.3 → 1.49.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/components/tela/avatar/label/avatar-label.mdx +93 -0
- package/components/tela/avatar/label/avatar-label.stories.ts +75 -0
- package/components/tela/avatar/label/avatar-label.vue +28 -0
- package/components/tela/button/button.vue +6 -6
- package/components/tela/card/card.mdx +10 -8
- package/components/tela/card/card.stories.ts +19 -19
- package/components/tela/card/card.vue +6 -4
- package/components/tela/chart/chart-bar.vue +2 -2
- package/components/tela/collapsible-group/collapsible-group-item.vue +47 -0
- package/components/tela/collapsible-group/collapsible-group.mdx +174 -0
- package/components/tela/collapsible-group/collapsible-group.stories.ts +134 -0
- package/components/tela/collapsible-group/collapsible-group.vue +5 -0
- package/components/tela/create-card/create-card.mdx +84 -0
- package/components/tela/create-card/create-card.stories.ts +133 -0
- package/components/tela/create-card/create-card.vue +108 -0
- package/components/tela/details/details-content-columns.vue +5 -0
- package/components/tela/details/details-content.vue +10 -0
- package/components/tela/details/details-footer.vue +5 -0
- package/components/tela/details/details-review-confirm.vue +42 -0
- package/components/tela/details/details-summary-label.vue +16 -0
- package/components/tela/details/details-summary.vue +5 -0
- package/components/tela/details/details-title.vue +17 -0
- package/components/tela/details/details.mdx +475 -0
- package/components/tela/details/details.vue +5 -0
- package/components/tela/details/hero/hero-aside.vue +5 -0
- package/components/tela/details/hero/hero-identity.vue +8 -0
- package/components/tela/details/hero/hero-main.vue +5 -0
- package/components/tela/details/hero/hero-metric-group.vue +13 -0
- package/components/tela/details/hero/hero-metric.vue +16 -0
- package/components/tela/details/hero/hero-metrics-card.vue +7 -0
- package/components/tela/details/hero/hero-stat.vue +16 -0
- package/components/tela/details/hero/hero-stats.vue +5 -0
- package/components/tela/details/hero/hero.vue +9 -0
- package/components/tela/details/left/left-header.vue +5 -0
- package/components/tela/details/left/left.vue +20 -0
- package/components/tela/details/right/right.vue +13 -0
- package/components/tela/details/use-details-scroll.ts +91 -0
- package/components/tela/dot-separator.vue +3 -0
- package/components/tela/filter/filter-trigger.vue +1 -1
- package/components/tela/header/header.vue +1 -1
- package/components/tela/header/leading/summary/summary-status.vue +1 -1
- package/components/tela/home/home-body.vue +5 -0
- package/components/tela/home/home-content.vue +5 -0
- package/components/tela/home/home-section.vue +5 -0
- package/components/tela/home/home-title.vue +5 -0
- package/components/tela/home/home-toolbar.vue +10 -0
- package/components/tela/home/home.mdx +285 -0
- package/components/tela/home/home.vue +5 -0
- package/components/tela/home/metrics/metrics-card.vue +23 -0
- package/components/tela/home/metrics/metrics-group.vue +5 -0
- package/components/tela/home/metrics/metrics-stat.vue +23 -0
- package/components/tela/home/metrics/metrics.vue +18 -0
- package/components/tela/icon/custom.vue +9 -6
- package/components/tela/input/input.vue +1 -1
- package/components/tela/preview/preview-content.vue +2 -2
- package/components/tela/preview/preview-floating-bar.vue +1 -1
- package/components/tela/progress-bar/progress-bar.mdx +117 -0
- package/components/tela/progress-bar/progress-bar.stories.ts +148 -0
- package/components/tela/progress-bar/progress-bar.vue +88 -0
- package/components/tela/segment-bar/segment-bar.mdx +57 -0
- package/components/tela/segment-bar/segment-bar.stories.ts +115 -0
- package/components/tela/segment-bar/segment-bar.vue +88 -0
- package/components/tela/sidebar/sidebar-footer.vue +1 -1
- package/components/tela/sidebar/sidebar-item.vue +2 -1
- package/components/tela/sidebar/sidebar.mdx +2 -2
- package/components/tela/status/status.mdx +23 -1
- package/components/tela/status/status.stories.ts +40 -0
- package/components/tela/status/status.vue +22 -3
- package/components/tela/table/table-head.vue +1 -1
- package/components/tela/table/table-header.vue +1 -1
- package/components/tela/table/table-row.vue +1 -1
- package/components/tela/tabs/tabs-indicator.vue +1 -1
- package/components/tela/tabs/tabs-trigger.vue +1 -1
- package/package.json +1 -1
- package/utils/design-tokens.ts +1 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
|
|
2
|
+
import * as AvatarLabelStories from './avatar-label.stories.ts';
|
|
3
|
+
|
|
4
|
+
<Meta of={AvatarLabelStories} />
|
|
5
|
+
|
|
6
|
+
# TelaAvatarLabel
|
|
7
|
+
|
|
8
|
+
An inline avatar + text chip. Pairs a `TelaAvatar` with a text label so any "entity reference" (created-by, assigned-to, owner, reviewer, member, etc.) renders consistently across summary rows, list cells, and metadata strips. The component is named after its **shape**, not a role — pick the role at the call site via the `label`.
|
|
9
|
+
|
|
10
|
+
## Examples
|
|
11
|
+
|
|
12
|
+
### Basic Usage
|
|
13
|
+
|
|
14
|
+
```vue
|
|
15
|
+
<TelaAvatarLabel label="Gustavo Rodrigues" />
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### With Avatar Image
|
|
19
|
+
|
|
20
|
+
```vue
|
|
21
|
+
<TelaAvatarLabel
|
|
22
|
+
label="Jane Doe"
|
|
23
|
+
avatar-src="https://i.pravatar.cc/150?img=5"
|
|
24
|
+
/>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Larger Avatar
|
|
28
|
+
|
|
29
|
+
```vue
|
|
30
|
+
<TelaAvatarLabel
|
|
31
|
+
label="Project Phoenix"
|
|
32
|
+
avatar-size="sm"
|
|
33
|
+
/>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Inside a Summary Row
|
|
37
|
+
|
|
38
|
+
A common usage — combining text labels, a `TelaDotSeparator`, and an avatar label:
|
|
39
|
+
|
|
40
|
+
```vue
|
|
41
|
+
<TelaDetailsSummary>
|
|
42
|
+
<TelaDetailsSummaryLabel>
|
|
43
|
+
Processed 23 minutes ago
|
|
44
|
+
</TelaDetailsSummaryLabel>
|
|
45
|
+
<TelaDotSeparator />
|
|
46
|
+
<TelaAvatarLabel label="Gustavo Rodrigues" />
|
|
47
|
+
</TelaDetailsSummary>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Custom Alt Text
|
|
51
|
+
|
|
52
|
+
By default `avatarAlt` falls back to `label`. Override when the alt text needs to differ (e.g. for screen reader context):
|
|
53
|
+
|
|
54
|
+
```vue
|
|
55
|
+
<TelaAvatarLabel
|
|
56
|
+
label="Gustavo Rodrigues"
|
|
57
|
+
avatar-alt="Avatar of Gustavo Rodrigues, case reviewer"
|
|
58
|
+
/>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Props
|
|
62
|
+
|
|
63
|
+
<ArgTypes />
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
type AvatarLabelProps = {
|
|
67
|
+
label: string
|
|
68
|
+
avatarSize?: '2xs' | 'xs' | 'sm' | 'md' | 'lg' // default: '2xs'
|
|
69
|
+
avatarSrc?: string
|
|
70
|
+
avatarAlt?: string // falls back to `label`
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Slots
|
|
75
|
+
|
|
76
|
+
This component does not expose slots — `label` is the single source of truth for the text. If you need rich content next to the avatar, compose `TelaAvatar` directly instead.
|
|
77
|
+
|
|
78
|
+
## Layout
|
|
79
|
+
|
|
80
|
+
Renders as `flex items-center gap-4px` — a small inline chip designed to sit next to other inline elements (text, separators, badges) inside a summary row.
|
|
81
|
+
|
|
82
|
+
## Use Cases
|
|
83
|
+
|
|
84
|
+
- **Summary rows** in details headers (created-by, assigned-to, last edited by).
|
|
85
|
+
- **List cells** showing the owner / responsible person for a row.
|
|
86
|
+
- **Activity feeds** to attribute an action to someone.
|
|
87
|
+
- **Comment headers** above a comment body.
|
|
88
|
+
- **Notification rows** indicating who triggered an event.
|
|
89
|
+
|
|
90
|
+
## Related
|
|
91
|
+
|
|
92
|
+
- `TelaAvatar` — underlying avatar primitive.
|
|
93
|
+
- `TelaDotSeparator` — pairs naturally inside a summary row between labels.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3'
|
|
2
|
+
import TelaAvatarLabel from './avatar-label.vue'
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'Core/AvatarLabel',
|
|
6
|
+
component: TelaAvatarLabel,
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: 'centered',
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component: 'An inline avatar + text chip. Pairs a `TelaAvatar` with a label, useful for representing any entity reference (created-by, assigned-to, owner, reviewer, etc.) in summary rows, lists, or metadata.',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
argTypes: {
|
|
17
|
+
label: {
|
|
18
|
+
control: 'text',
|
|
19
|
+
description: 'Text rendered next to the avatar.',
|
|
20
|
+
},
|
|
21
|
+
avatarSize: {
|
|
22
|
+
control: 'select',
|
|
23
|
+
options: ['2xs', 'xs', 'sm', 'md', 'lg'],
|
|
24
|
+
description: 'Size variant for the avatar. Defaults to `2xs` to fit inline in summary rows.',
|
|
25
|
+
},
|
|
26
|
+
avatarSrc: {
|
|
27
|
+
control: 'text',
|
|
28
|
+
description: 'Image URL for the avatar. When omitted, the avatar falls back to its initials/placeholder behavior.',
|
|
29
|
+
},
|
|
30
|
+
avatarAlt: {
|
|
31
|
+
control: 'text',
|
|
32
|
+
description: 'Alt text for the avatar image. Falls back to `label` when not provided.',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
args: {
|
|
36
|
+
label: 'Gustavo Rodrigues',
|
|
37
|
+
avatarSize: '2xs',
|
|
38
|
+
},
|
|
39
|
+
} satisfies Meta<typeof TelaAvatarLabel>
|
|
40
|
+
|
|
41
|
+
export default meta
|
|
42
|
+
|
|
43
|
+
type Story = StoryObj<typeof meta>
|
|
44
|
+
|
|
45
|
+
export const Default: Story = {}
|
|
46
|
+
|
|
47
|
+
export const WithImage: Story = {
|
|
48
|
+
args: {
|
|
49
|
+
label: 'Jane Doe',
|
|
50
|
+
avatarSrc: 'https://i.pravatar.cc/150?img=5',
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const Larger: Story = {
|
|
55
|
+
args: {
|
|
56
|
+
label: 'Project Phoenix',
|
|
57
|
+
avatarSize: 'sm',
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const InSummaryRow: Story = {
|
|
62
|
+
render: args => ({
|
|
63
|
+
components: { TelaAvatarLabel },
|
|
64
|
+
setup() {
|
|
65
|
+
return { args }
|
|
66
|
+
},
|
|
67
|
+
template: `
|
|
68
|
+
<div class="flex items-center gap-8px">
|
|
69
|
+
<p class="text-body-12-regular text-secondary">Created 23 minutes ago</p>
|
|
70
|
+
<div aria-hidden="true" class="size-3px rounded-full bg-icon-subtle" />
|
|
71
|
+
<TelaAvatarLabel v-bind="args" />
|
|
72
|
+
</div>
|
|
73
|
+
`,
|
|
74
|
+
}),
|
|
75
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
type AvatarSize = '2xs' | 'xs' | 'sm' | 'md' | 'lg'
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
label: string
|
|
6
|
+
avatarSize?: AvatarSize
|
|
7
|
+
avatarSrc?: string
|
|
8
|
+
avatarAlt?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
withDefaults(defineProps<Props>(), {
|
|
12
|
+
avatarSize: '2xs',
|
|
13
|
+
})
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<div flex items-center gap-4px>
|
|
18
|
+
<TelaAvatar
|
|
19
|
+
:size="avatarSize"
|
|
20
|
+
:src="avatarSrc"
|
|
21
|
+
:alt="avatarAlt ?? label"
|
|
22
|
+
rounded-full
|
|
23
|
+
/>
|
|
24
|
+
<p body-12-regular text-secondary>
|
|
25
|
+
{{ label }}
|
|
26
|
+
</p>
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
@@ -33,16 +33,16 @@ const sizeStyle = computed(() => (({
|
|
|
33
33
|
|
|
34
34
|
const variantStyle = computed(() => (({
|
|
35
35
|
'primary': fold`
|
|
36
|
-
bg-
|
|
37
|
-
hover:bg-
|
|
38
|
-
active:bg-
|
|
36
|
+
bg-neutral-900 text-white
|
|
37
|
+
hover:bg-neutral-800
|
|
38
|
+
active:bg-neutral-700
|
|
39
39
|
focus-visible:ring-0.5px focus-visible:ring-cyan-600
|
|
40
40
|
`,
|
|
41
41
|
'secondary': fold`
|
|
42
|
-
bg-white text-
|
|
42
|
+
bg-white text-neutral-900 border border-0.5px
|
|
43
43
|
[box-shadow:0_1px_6px_0_rgba(103,127,148,0.05)]
|
|
44
|
-
hover:bg-subtle hover:border-
|
|
45
|
-
active:bg-muted active:border-
|
|
44
|
+
hover:bg-subtle hover:border-strong
|
|
45
|
+
active:bg-muted active:border-neutral-400/60
|
|
46
46
|
focus-visible:ring-0.5px focus-visible:ring-cyan-600
|
|
47
47
|
`,
|
|
48
48
|
'ghost': fold`
|
|
@@ -14,16 +14,18 @@ A surface container component used to group related content with consistent visu
|
|
|
14
14
|
|
|
15
15
|
Always use the `size` prop to control card padding — it maps directly to the standardized values. Never apply padding manually to `<TelaCard>` or its inner elements.
|
|
16
16
|
|
|
17
|
+
Only `xs` and `sm` are available. Both share `rounded-12px`.
|
|
18
|
+
|
|
17
19
|
| `size` | Applied classes | Use for |
|
|
18
20
|
|--------|------------------|---------|
|
|
19
|
-
| `
|
|
20
|
-
| `sm` | `p-24px
|
|
21
|
+
| `xs` | `p-20px` | Compact cards, dense inner containers |
|
|
22
|
+
| `sm` *(default)* | `p-24px` | Standard cards |
|
|
21
23
|
|
|
22
24
|
```vue
|
|
23
|
-
<!-- Correct — use
|
|
24
|
-
<TelaCard size="
|
|
25
|
+
<!-- Correct — use xs or sm -->
|
|
26
|
+
<TelaCard size="xs">...</TelaCard>
|
|
25
27
|
<TelaCard size="sm">...</TelaCard>
|
|
26
|
-
<TelaCard>...</TelaCard> <!--
|
|
28
|
+
<TelaCard>...</TelaCard> <!-- sm is the default -->
|
|
27
29
|
|
|
28
30
|
<!-- Incorrect — never apply padding manually -->
|
|
29
31
|
<TelaCard class="p-20px">...</TelaCard>
|
|
@@ -43,10 +45,10 @@ Always use the `size` prop to control card padding — it maps directly to the s
|
|
|
43
45
|
</TelaCard>
|
|
44
46
|
```
|
|
45
47
|
|
|
46
|
-
###
|
|
48
|
+
### Compact / Inner Card
|
|
47
49
|
|
|
48
50
|
```vue
|
|
49
|
-
<TelaCard size="
|
|
51
|
+
<TelaCard size="xs">
|
|
50
52
|
<span class="body-12-medium text-secondary">Compact content</span>
|
|
51
53
|
</TelaCard>
|
|
52
54
|
```
|
|
@@ -85,7 +87,7 @@ Cards in grids must use `h-full` for consistent heights.
|
|
|
85
87
|
|
|
86
88
|
| Prop | Type | Default | Description |
|
|
87
89
|
|------|------|---------|-------------|
|
|
88
|
-
| `size` | `'
|
|
90
|
+
| `size` | `'xs' \| 'sm'` | `'sm'` | `xs` = `p-20px`, `sm` = `p-24px`. Both use `rounded-12px` |
|
|
89
91
|
|
|
90
92
|
## Slots
|
|
91
93
|
|
|
@@ -8,15 +8,15 @@ const meta: Meta<typeof Card> = {
|
|
|
8
8
|
layout: 'centered',
|
|
9
9
|
docs: {
|
|
10
10
|
description: {
|
|
11
|
-
component: 'A surface container that groups related content with consistent visual boundaries. Use the `size` prop to control padding — `
|
|
11
|
+
component: 'A surface container that groups related content with consistent visual boundaries. Use the `size` prop to control padding — `sm` for standard cards, `xs` for compact or inner containers.',
|
|
12
12
|
},
|
|
13
13
|
},
|
|
14
14
|
},
|
|
15
15
|
argTypes: {
|
|
16
16
|
size: {
|
|
17
17
|
control: 'select',
|
|
18
|
-
options: ['
|
|
19
|
-
description: '`
|
|
18
|
+
options: ['xs', 'sm'],
|
|
19
|
+
description: '`sm` applies standard card padding. `xs` applies compact/inner container padding.',
|
|
20
20
|
},
|
|
21
21
|
},
|
|
22
22
|
}
|
|
@@ -41,7 +41,7 @@ export const Default: Story = {
|
|
|
41
41
|
`,
|
|
42
42
|
}),
|
|
43
43
|
args: {
|
|
44
|
-
size: '
|
|
44
|
+
size: 'sm',
|
|
45
45
|
},
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -49,10 +49,10 @@ export const Standard: Story = {
|
|
|
49
49
|
render: () => ({
|
|
50
50
|
components: { Card },
|
|
51
51
|
template: `
|
|
52
|
-
<Card size="
|
|
52
|
+
<Card size="sm">
|
|
53
53
|
<div style="display: flex; flex-direction: column; gap: 8px; min-width: 280px;">
|
|
54
54
|
<p class="heading-h4-semibold text-contrast">Standard Card</p>
|
|
55
|
-
<p class="body-14-regular text-secondary">Used for
|
|
55
|
+
<p class="body-14-regular text-secondary">Used for standard content surfaces.</p>
|
|
56
56
|
</div>
|
|
57
57
|
</Card>
|
|
58
58
|
`,
|
|
@@ -60,7 +60,7 @@ export const Standard: Story = {
|
|
|
60
60
|
parameters: {
|
|
61
61
|
docs: {
|
|
62
62
|
description: {
|
|
63
|
-
story: 'Default size (`
|
|
63
|
+
story: 'Default size (`sm`). Use for primary content surfaces.',
|
|
64
64
|
},
|
|
65
65
|
},
|
|
66
66
|
},
|
|
@@ -70,10 +70,10 @@ export const Minor: Story = {
|
|
|
70
70
|
render: () => ({
|
|
71
71
|
components: { Card },
|
|
72
72
|
template: `
|
|
73
|
-
<Card size="
|
|
73
|
+
<Card size="xs">
|
|
74
74
|
<div style="display: flex; flex-direction: column; gap: 8px; min-width: 240px;">
|
|
75
|
-
<p class="heading-h5-semibold text-contrast">
|
|
76
|
-
<p class="body-12-regular text-secondary">Used for
|
|
75
|
+
<p class="heading-h5-semibold text-contrast">Compact Card</p>
|
|
76
|
+
<p class="body-12-regular text-secondary">Used for compact cards and inner containers.</p>
|
|
77
77
|
</div>
|
|
78
78
|
</Card>
|
|
79
79
|
`,
|
|
@@ -81,7 +81,7 @@ export const Minor: Story = {
|
|
|
81
81
|
parameters: {
|
|
82
82
|
docs: {
|
|
83
83
|
description: {
|
|
84
|
-
story: '
|
|
84
|
+
story: 'Compact size (`xs`). Use for compact cards or nested inner containers.',
|
|
85
85
|
},
|
|
86
86
|
},
|
|
87
87
|
},
|
|
@@ -92,18 +92,18 @@ export const SizeComparison: Story = {
|
|
|
92
92
|
components: { Card },
|
|
93
93
|
template: `
|
|
94
94
|
<div style="display: flex; gap: 16px; align-items: flex-start;">
|
|
95
|
-
<Card size="
|
|
95
|
+
<Card size="sm">
|
|
96
96
|
<div style="display: flex; flex-direction: column; gap: 6px; min-width: 200px;">
|
|
97
|
-
<p class="body-12-medium text-secondary">size="
|
|
97
|
+
<p class="body-12-medium text-secondary">size="sm"</p>
|
|
98
98
|
<p class="heading-h4-semibold text-contrast">Standard</p>
|
|
99
|
-
<p class="body-14-regular text-tertiary">p-
|
|
99
|
+
<p class="body-14-regular text-tertiary">p-24px padding</p>
|
|
100
100
|
</div>
|
|
101
101
|
</Card>
|
|
102
|
-
<Card size="
|
|
102
|
+
<Card size="xs">
|
|
103
103
|
<div style="display: flex; flex-direction: column; gap: 6px; min-width: 200px;">
|
|
104
|
-
<p class="body-12-medium text-secondary">size="
|
|
105
|
-
<p class="heading-h4-semibold text-contrast">
|
|
106
|
-
<p class="body-14-regular text-tertiary">p-
|
|
104
|
+
<p class="body-12-medium text-secondary">size="xs"</p>
|
|
105
|
+
<p class="heading-h4-semibold text-contrast">Compact</p>
|
|
106
|
+
<p class="body-14-regular text-tertiary">p-20px padding</p>
|
|
107
107
|
</div>
|
|
108
108
|
</Card>
|
|
109
109
|
</div>
|
|
@@ -136,7 +136,7 @@ export const Grid: Story = {
|
|
|
136
136
|
layout: 'padded',
|
|
137
137
|
docs: {
|
|
138
138
|
description: {
|
|
139
|
-
story: 'Cards used in a grid layout. No padding override needed — `size="
|
|
139
|
+
story: 'Cards used in a grid layout. No padding override needed — `size="sm"` is the default.',
|
|
140
140
|
},
|
|
141
141
|
},
|
|
142
142
|
},
|
|
@@ -2,20 +2,22 @@
|
|
|
2
2
|
import type { HTMLAttributes } from 'vue'
|
|
3
3
|
|
|
4
4
|
const props = withDefaults(defineProps<{
|
|
5
|
-
size?: 'sm' | 'md'
|
|
5
|
+
size?: 'xs' | 'sm' | 'md'
|
|
6
6
|
class?: HTMLAttributes['class']
|
|
7
7
|
/** @deprecated Use `class` instead */
|
|
8
8
|
contentPadding?: HTMLAttributes['class']
|
|
9
9
|
/** @deprecated Use `class` instead */
|
|
10
10
|
borderRadius?: HTMLAttributes['class']
|
|
11
11
|
}>(), {
|
|
12
|
-
size: '
|
|
12
|
+
size: 'sm',
|
|
13
13
|
})
|
|
14
14
|
|
|
15
15
|
const sizeStyles = computed(() => (({
|
|
16
|
+
xs: { padding: 'p-20px' },
|
|
16
17
|
sm: { padding: 'p-24px' },
|
|
18
|
+
/** @deprecated: Use 'xs' or 'sm' instead */
|
|
17
19
|
md: { padding: 'p-32px' },
|
|
18
|
-
}) as Record<string, { padding: string }>)[props.size ?? '
|
|
20
|
+
}) as Record<string, { padding: string }>)[props.size ?? 'sm'] ?? { padding: '' })
|
|
19
21
|
|
|
20
22
|
const paddingClass = computed(() => props.contentPadding ?? sizeStyles.value.padding)
|
|
21
23
|
const rootEl = ref<HTMLElement | null>(null)
|
|
@@ -26,7 +28,7 @@ defineExpose({
|
|
|
26
28
|
</script>
|
|
27
29
|
|
|
28
30
|
<template>
|
|
29
|
-
<div ref="rootEl" :class="cn('rounded-
|
|
31
|
+
<div ref="rootEl" :class="cn('rounded-12px bg border-0.5px border', paddingClass, props.class)">
|
|
30
32
|
<slot />
|
|
31
33
|
</div>
|
|
32
34
|
</template>
|
|
@@ -8,7 +8,7 @@ const props = withDefaults(defineProps<{
|
|
|
8
8
|
}>(), {
|
|
9
9
|
height: '12px',
|
|
10
10
|
gap: '2px',
|
|
11
|
-
duration:
|
|
11
|
+
duration: 400,
|
|
12
12
|
})
|
|
13
13
|
|
|
14
14
|
const animatedFilled = ref(0)
|
|
@@ -52,7 +52,7 @@ watch(() => props.filled, (newValue) => {
|
|
|
52
52
|
:style="{ height }"
|
|
53
53
|
w-1px
|
|
54
54
|
rounded-1px
|
|
55
|
-
:bg="bar.isFilled ? '
|
|
55
|
+
:bg="bar.isFilled ? 'green-500' : 'neutral-200'"
|
|
56
56
|
/>
|
|
57
57
|
</div>
|
|
58
58
|
</template>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const props = withDefaults(defineProps<{
|
|
3
|
+
title: string
|
|
4
|
+
itemsLength?: number
|
|
5
|
+
singularLabel?: string
|
|
6
|
+
pluralLabel?: string
|
|
7
|
+
}>(), {
|
|
8
|
+
singularLabel: 'item',
|
|
9
|
+
pluralLabel: 'items',
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const open = defineModel<boolean>('open', { default: false })
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<TelaCollapsible v-model:open="open" class="first:rounded-t-inherit last:rounded-b-inherit">
|
|
17
|
+
<TelaCollapsibleTrigger
|
|
18
|
+
class="group data-[state=closed]:rounded-b-inherit focus-visible:ring-1 focus-visible:ring-neutral-400"
|
|
19
|
+
outline-none w-full first:rounded-t-inherit
|
|
20
|
+
>
|
|
21
|
+
<div flex items-center justify-between py-20px pl-16px pr-20px>
|
|
22
|
+
<div flex items-center gap-16px>
|
|
23
|
+
<TelaIcon
|
|
24
|
+
name="i-ph-caret-down"
|
|
25
|
+
size="24px"
|
|
26
|
+
color="icon-tertiary"
|
|
27
|
+
class="transition-all ease-out group-data-[state=open]:rotate-180"
|
|
28
|
+
/>
|
|
29
|
+
<h5 heading-h4-semibold text-primary>
|
|
30
|
+
{{ title }}
|
|
31
|
+
</h5>
|
|
32
|
+
</div>
|
|
33
|
+
<div flex items-center gap-14px>
|
|
34
|
+
<slot name="trigger-trailing" />
|
|
35
|
+
<p v-if="props.itemsLength" body-14-regular text-secondary>
|
|
36
|
+
{{ props.itemsLength }} {{ props.itemsLength === 1 ? props.singularLabel : props.pluralLabel }}
|
|
37
|
+
</p>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</TelaCollapsibleTrigger>
|
|
41
|
+
<TelaCollapsibleContent>
|
|
42
|
+
<div pl-56px pr-36px pb-28px>
|
|
43
|
+
<slot />
|
|
44
|
+
</div>
|
|
45
|
+
</TelaCollapsibleContent>
|
|
46
|
+
</TelaCollapsible>
|
|
47
|
+
</template>
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
|
|
2
|
+
import * as CollapsibleGroupStories from './collapsible-group.stories.ts';
|
|
3
|
+
|
|
4
|
+
<Meta of={CollapsibleGroupStories} />
|
|
5
|
+
|
|
6
|
+
# TelaCollapsibleGroup
|
|
7
|
+
|
|
8
|
+
A grouped collapsible component for stacking multiple expandable items in a single bordered container with dividers between rows. Each `TelaCollapsibleGroupItem` supports a title, an optional item count with customizable singular/plural labels, an open/close model, and a slot for arbitrary content. Useful for accordion-style sections, document outlines, and grouped detail panels.
|
|
9
|
+
|
|
10
|
+
## Examples
|
|
11
|
+
|
|
12
|
+
### Basic Usage
|
|
13
|
+
|
|
14
|
+
```vue
|
|
15
|
+
<TelaCollapsibleGroup>
|
|
16
|
+
<TelaCollapsibleGroupItem title="Item 1" :items-length="4">
|
|
17
|
+
<p>Lorem ipsum dolor sit amet.</p>
|
|
18
|
+
</TelaCollapsibleGroupItem>
|
|
19
|
+
<TelaCollapsibleGroupItem title="Item 2" :items-length="2">
|
|
20
|
+
<p>Sed do eiusmod tempor incididunt.</p>
|
|
21
|
+
</TelaCollapsibleGroupItem>
|
|
22
|
+
<TelaCollapsibleGroupItem title="Item 3" :items-length="1">
|
|
23
|
+
<p>Ut enim ad minim veniam.</p>
|
|
24
|
+
</TelaCollapsibleGroupItem>
|
|
25
|
+
</TelaCollapsibleGroup>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Without Item Count
|
|
29
|
+
|
|
30
|
+
Omit `itemsLength` to hide the count badge entirely.
|
|
31
|
+
|
|
32
|
+
```vue
|
|
33
|
+
<TelaCollapsibleGroup>
|
|
34
|
+
<TelaCollapsibleGroupItem title="Overview">
|
|
35
|
+
<p>Just a title and content.</p>
|
|
36
|
+
</TelaCollapsibleGroupItem>
|
|
37
|
+
<TelaCollapsibleGroupItem title="Details">
|
|
38
|
+
<p>No count is shown.</p>
|
|
39
|
+
</TelaCollapsibleGroupItem>
|
|
40
|
+
</TelaCollapsibleGroup>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Custom Singular / Plural Labels
|
|
44
|
+
|
|
45
|
+
The count text defaults to `item` / `items`. Pass `singularLabel` and `pluralLabel` to localize or customize.
|
|
46
|
+
|
|
47
|
+
```vue
|
|
48
|
+
<TelaCollapsibleGroup>
|
|
49
|
+
<TelaCollapsibleGroupItem
|
|
50
|
+
title="Documents"
|
|
51
|
+
:items-length="1"
|
|
52
|
+
singular-label="file"
|
|
53
|
+
plural-label="files"
|
|
54
|
+
>
|
|
55
|
+
<p>A single file.</p>
|
|
56
|
+
</TelaCollapsibleGroupItem>
|
|
57
|
+
<TelaCollapsibleGroupItem
|
|
58
|
+
title="Reports"
|
|
59
|
+
:items-length="12"
|
|
60
|
+
singular-label="file"
|
|
61
|
+
plural-label="files"
|
|
62
|
+
>
|
|
63
|
+
<p>Twelve files inside.</p>
|
|
64
|
+
</TelaCollapsibleGroupItem>
|
|
65
|
+
</TelaCollapsibleGroup>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Controlled Open State
|
|
69
|
+
|
|
70
|
+
Use `v-model:open` to control whether an item is expanded.
|
|
71
|
+
|
|
72
|
+
```vue
|
|
73
|
+
<script setup>
|
|
74
|
+
const isOpen = ref(true)
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<template>
|
|
78
|
+
<TelaCollapsibleGroup>
|
|
79
|
+
<TelaCollapsibleGroupItem
|
|
80
|
+
v-model:open="isOpen"
|
|
81
|
+
title="Controlled Item"
|
|
82
|
+
:items-length="3"
|
|
83
|
+
>
|
|
84
|
+
<p>Open state is bound to a parent ref.</p>
|
|
85
|
+
</TelaCollapsibleGroupItem>
|
|
86
|
+
</TelaCollapsibleGroup>
|
|
87
|
+
</template>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Custom Trailing Slot
|
|
91
|
+
|
|
92
|
+
The `trigger-trailing` slot lets you place arbitrary content (badges, actions) next to the item count.
|
|
93
|
+
|
|
94
|
+
```vue
|
|
95
|
+
<TelaCollapsibleGroup>
|
|
96
|
+
<TelaCollapsibleGroupItem title="With Action" :items-length="2">
|
|
97
|
+
<template #trigger-trailing>
|
|
98
|
+
<TelaBadge variant="warning">Pending</TelaBadge>
|
|
99
|
+
</template>
|
|
100
|
+
<p>Item content goes here.</p>
|
|
101
|
+
</TelaCollapsibleGroupItem>
|
|
102
|
+
</TelaCollapsibleGroup>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Props
|
|
106
|
+
|
|
107
|
+
### TelaCollapsibleGroup
|
|
108
|
+
|
|
109
|
+
Wrapper component. Accepts no props — renders a bordered, divided container around its default slot.
|
|
110
|
+
|
|
111
|
+
### TelaCollapsibleGroupItem
|
|
112
|
+
|
|
113
|
+
<ArgTypes />
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
type CollapsibleGroupItemProps = {
|
|
117
|
+
title: string
|
|
118
|
+
itemsLength?: number
|
|
119
|
+
singularLabel?: string // default: 'item'
|
|
120
|
+
pluralLabel?: string // default: 'items'
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
type CollapsibleGroupItemModels = {
|
|
124
|
+
open?: boolean // v-model:open
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Slots
|
|
129
|
+
|
|
130
|
+
### TelaCollapsibleGroup
|
|
131
|
+
|
|
132
|
+
- `default` — One or more `TelaCollapsibleGroupItem` components.
|
|
133
|
+
|
|
134
|
+
### TelaCollapsibleGroupItem
|
|
135
|
+
|
|
136
|
+
- `default` — Content revealed when the item is expanded.
|
|
137
|
+
- `trigger-trailing` — Custom content rendered to the left of the item count in the header row.
|
|
138
|
+
|
|
139
|
+
## Components
|
|
140
|
+
|
|
141
|
+
The collapsible group system consists of two components:
|
|
142
|
+
|
|
143
|
+
- `TelaCollapsibleGroup` — Bordered container with dividers between items.
|
|
144
|
+
- `TelaCollapsibleGroupItem` — Individual expandable row with title, optional count, and content.
|
|
145
|
+
|
|
146
|
+
## Features
|
|
147
|
+
|
|
148
|
+
- **Stacked Layout**: Multiple items share a single bordered container with dividers.
|
|
149
|
+
- **Item Count**: Optional numeric badge with customizable singular/plural labels.
|
|
150
|
+
- **Controlled or Uncontrolled**: Use `v-model:open` or let each item manage its own state.
|
|
151
|
+
- **Custom Trailing Content**: Slot for badges or actions next to the count.
|
|
152
|
+
- **Smooth Animation**: Caret icon rotates and content animates on toggle.
|
|
153
|
+
- **Accessible**: Built on `TelaCollapsible` (reka-ui Collapsible primitives).
|
|
154
|
+
|
|
155
|
+
## Use Cases
|
|
156
|
+
|
|
157
|
+
- **Document Sections**: Group chapters, attachments, or structured details.
|
|
158
|
+
- **Accordions**: Stack related expandable items with consistent styling.
|
|
159
|
+
- **File / Folder Lists**: Show grouped items with counts.
|
|
160
|
+
- **Detail Panels**: Organize record metadata into expandable groups.
|
|
161
|
+
|
|
162
|
+
## Best Practices
|
|
163
|
+
|
|
164
|
+
1. **Consistent Counts**: When using `itemsLength`, apply it to all items in the group for visual rhythm.
|
|
165
|
+
2. **Localize Labels**: Always pass `singularLabel` / `pluralLabel` when copy needs to match the surrounding locale.
|
|
166
|
+
3. **Avoid Deep Nesting**: Don't nest `TelaCollapsibleGroup` inside another item — flatten when possible.
|
|
167
|
+
4. **Concise Titles**: Keep titles short so the row stays scannable.
|
|
168
|
+
|
|
169
|
+
## Accessibility
|
|
170
|
+
|
|
171
|
+
- Built on `TelaCollapsible` / reka-ui Collapsible primitives.
|
|
172
|
+
- Proper ARIA attributes (`aria-expanded`, `aria-controls`).
|
|
173
|
+
- Keyboard navigation (Space/Enter to toggle).
|
|
174
|
+
- Focus management and screen reader support.
|