@meistrari/tela-build 1.47.2 → 1.48.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/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 +7 -5
- package/components/tela/chart/chart-bar.vue +2 -2
- package/components/tela/combobox/combobox-input.vue +1 -1
- package/components/tela/combobox/combobox-label.vue +1 -1
- package/components/tela/combobox/combobox.vue +29 -29
- 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/expandable-input.vue +4 -5
- package/components/tela/filter/filter-trigger.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/icon-button/icon-button.vue +3 -3
- package/components/tela/input/input.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-toggle/segment-toggle.vue +95 -21
- package/components/tela/select-menu/select-menu-item.vue +2 -2
- package/components/tela/select-menu/select-menu-trigger.vue +3 -3
- package/components/tela/select-menu/select-menu.vue +4 -4
- 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/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-list.vue +1 -1
- package/components/tela/tabs/tabs-trigger.vue +1 -1
- package/package.json +1 -1
|
@@ -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
|
},
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { HTMLAttributes } from 'vue'
|
|
3
3
|
|
|
4
|
-
const props = withDefaults(defineProps<{
|
|
5
|
-
size?: '
|
|
4
|
+
const props = withDefaults(defineProps <{
|
|
5
|
+
size?: 'xs' | 'sm'
|
|
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>
|
|
@@ -22,7 +22,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|
|
22
22
|
<template>
|
|
23
23
|
<ComboboxInput
|
|
24
24
|
v-bind="forwarded"
|
|
25
|
-
:class="cn('flex h-10 w-full bg
|
|
25
|
+
:class="cn('flex h-10 w-full bg border-b-[0.5px] border-border rounded-xl px-3 py-1 body-14-regular text-primary transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-tertiary focus-visible:bg focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50', props.class)"
|
|
26
26
|
>
|
|
27
27
|
<slot />
|
|
28
28
|
</ComboboxInput>
|
|
@@ -15,7 +15,7 @@ const forwarded = useForwardProps(delegatedProps)
|
|
|
15
15
|
<template>
|
|
16
16
|
<ComboboxLabel
|
|
17
17
|
v-bind="forwarded"
|
|
18
|
-
:class="cn('px-2 py-1 body-12-semibold text-
|
|
18
|
+
:class="cn('px-2 py-1 body-12-semibold text-secondary', props.class)"
|
|
19
19
|
>
|
|
20
20
|
<slot />
|
|
21
21
|
</ComboboxLabel>
|
|
@@ -366,7 +366,7 @@ watch(innerValue, (raw) => {
|
|
|
366
366
|
<ComboboxList :class="cn('z-999', compact ? 'w-440px' : 'w-327px', props.contentClass)" :disable-portal="props.disablePortal" align="start">
|
|
367
367
|
<div v-if="hasSearchbar" class="relative">
|
|
368
368
|
<span class="absolute inset-y-0 start-0 flex items-center justify-center px-2.5">
|
|
369
|
-
<TelaIcon name="i-ph-magnifying-glass" :size="compact ? '12px' : '14px'" class="text-
|
|
369
|
+
<TelaIcon name="i-ph-magnifying-glass" :size="compact ? '12px' : '14px'" class="text-tertiary" />
|
|
370
370
|
</span>
|
|
371
371
|
<ComboboxInput
|
|
372
372
|
v-model="search" :class="cn('rounded-b-none', compact ? 'pl-7' : 'pl-8')"
|
|
@@ -374,7 +374,7 @@ watch(innerValue, (raw) => {
|
|
|
374
374
|
/>
|
|
375
375
|
<div v-if="search.length > 0" class="absolute inset-y-0 end-0 flex items-center justify-center px-2.5">
|
|
376
376
|
<button class="flex items-center justify-center" @click="handleDeleteSearchValue">
|
|
377
|
-
<TelaIcon name="i-ph-x-circle" size="16px" class="text-
|
|
377
|
+
<TelaIcon name="i-ph-x-circle" size="16px" class="text-tertiary" />
|
|
378
378
|
</button>
|
|
379
379
|
</div>
|
|
380
380
|
</div>
|
|
@@ -392,17 +392,17 @@ watch(innerValue, (raw) => {
|
|
|
392
392
|
class="flex flex-col items-center justify-center gap-5 px-7"
|
|
393
393
|
>
|
|
394
394
|
<div class="flex flex-col items-center justify-center gap-3">
|
|
395
|
-
<TelaIcon name="i-ph-smiley-sad" size="24px" class="text-
|
|
395
|
+
<TelaIcon name="i-ph-smiley-sad" size="24px" class="text-tertiary" />
|
|
396
396
|
<span class="text-xl font-medium leading-none">
|
|
397
397
|
{{ search.trim() && labelNoResultsWithSearch ? labelNoResultsWithSearch(search) : labelNoResults }}
|
|
398
398
|
</span>
|
|
399
|
-
<span v-if="search.trim()" class="inline-flex items-center gap-1.5 text-md text-
|
|
399
|
+
<span v-if="search.trim()" class="inline-flex items-center gap-1.5 text-md text-secondary">
|
|
400
400
|
{{ labelPress }}
|
|
401
401
|
<div class="flex items-center gap-1">
|
|
402
|
-
<div class="p-2px bg-
|
|
402
|
+
<div class="p-2px bg-lowered rounded">
|
|
403
403
|
<TelaIcon name="i-ph-command" size="16px" />
|
|
404
404
|
</div>
|
|
405
|
-
<div class="p-2px bg-
|
|
405
|
+
<div class="p-2px bg-lowered rounded">
|
|
406
406
|
<TelaIcon name="i-ph-backspace" size="16px" />
|
|
407
407
|
</div>
|
|
408
408
|
</div>
|
|
@@ -441,15 +441,15 @@ watch(innerValue, (raw) => {
|
|
|
441
441
|
<div class="flex flex-col">
|
|
442
442
|
<div class="flex items-center gap-1">
|
|
443
443
|
<span :class="cn('font-medium truncate max-w-140px', compact ? 'text-sm font-580' : 'text-body-14-regular', labelItemClass)">{{ item.label }}</span>
|
|
444
|
-
<TelaBadge v-if="item.isMultiModal" variant="filled" class="py-[2px] bg-
|
|
444
|
+
<TelaBadge v-if="item.isMultiModal" variant="filled" class="py-[2px] bg-muted" text-class="leading-none">
|
|
445
445
|
{{ labelMultimodal }}
|
|
446
446
|
</TelaBadge>
|
|
447
447
|
<span v-if="item.cost !== undefined" class="text-10px font-580 ml-1px">
|
|
448
|
-
<span class="text-
|
|
449
|
-
<span class="text-
|
|
448
|
+
<span class="text-primary">{{ renderCostIndicator(item.cost)?.blackSymbols }}</span>
|
|
449
|
+
<span class="text-neutral-300">{{ renderCostIndicator(item.cost)?.graySymbols }}</span>
|
|
450
450
|
</span>
|
|
451
451
|
</div>
|
|
452
|
-
<span v-if="item.description" :class="cn('font-normal text-sm text-balance leading-none text-
|
|
452
|
+
<span v-if="item.description" :class="cn('font-normal text-sm text-balance leading-none text-secondary', descriptionClass)">
|
|
453
453
|
{{ item.description }}
|
|
454
454
|
</span>
|
|
455
455
|
<slot name="tags" :option="item" />
|
|
@@ -457,18 +457,18 @@ watch(innerValue, (raw) => {
|
|
|
457
457
|
</div>
|
|
458
458
|
<div class="flex gap-3">
|
|
459
459
|
<div v-if="item.maxInputTokens" class="flex flex-col gap-1.5">
|
|
460
|
-
<span class="text-
|
|
460
|
+
<span class="text-tertiary leading-none text-9px uppercase tracking-wider font-semibold">
|
|
461
461
|
{{ labelInputMax }}
|
|
462
462
|
</span>
|
|
463
|
-
<span class="text-
|
|
463
|
+
<span class="text-tertiary leading-none text-11px uppercase font-semibold group-data-[highlighted]:text-neutral-700">
|
|
464
464
|
{{ handleFormatNumber(item.maxInputTokens) }}
|
|
465
465
|
</span>
|
|
466
466
|
</div>
|
|
467
467
|
<div v-if="item.maxOutputTokens" class="flex flex-col gap-1.5">
|
|
468
|
-
<span class="text-
|
|
468
|
+
<span class="text-tertiary leading-none text-9px uppercase tracking-wider font-semibold">
|
|
469
469
|
{{ labelOutputMax }}
|
|
470
470
|
</span>
|
|
471
|
-
<span class="text-
|
|
471
|
+
<span class="text-tertiary leading-none text-11px uppercase font-semibold group-data-[highlighted]:text-neutral-700">
|
|
472
472
|
{{ handleFormatNumber(item.maxOutputTokens) }}
|
|
473
473
|
</span>
|
|
474
474
|
</div>
|
|
@@ -492,17 +492,17 @@ watch(innerValue, (raw) => {
|
|
|
492
492
|
class="flex flex-col items-center justify-center gap-5 px-7"
|
|
493
493
|
>
|
|
494
494
|
<div class="flex flex-col items-center justify-center gap-3">
|
|
495
|
-
<TelaIcon name="i-ph-smiley-sad" size="24px" class="text-
|
|
495
|
+
<TelaIcon name="i-ph-smiley-sad" size="24px" class="text-tertiary" />
|
|
496
496
|
<span class="text-xl font-medium leading-none">
|
|
497
497
|
{{ search.trim() && labelNoResultsWithSearch ? labelNoResultsWithSearch(search) : labelNoResults }}
|
|
498
498
|
</span>
|
|
499
|
-
<span v-if="search.trim()" class="inline-flex gap-1.5 text-md text-
|
|
499
|
+
<span v-if="search.trim()" class="inline-flex gap-1.5 text-md text-secondary">
|
|
500
500
|
{{ labelPress }}
|
|
501
501
|
<div class="flex items-center gap-1">
|
|
502
|
-
<div class="p-2px bg-
|
|
502
|
+
<div class="p-2px bg-lowered rounded">
|
|
503
503
|
<TelaIcon name="i-ph-command" size="16px" />
|
|
504
504
|
</div>
|
|
505
|
-
<div class="p-2px bg-
|
|
505
|
+
<div class="p-2px bg-lowered rounded">
|
|
506
506
|
<TelaIcon name="i-ph-backspace" size="16px" />
|
|
507
507
|
</div>
|
|
508
508
|
</div>
|
|
@@ -548,15 +548,15 @@ watch(innerValue, (raw) => {
|
|
|
548
548
|
<div class="flex flex-col">
|
|
549
549
|
<div class="flex items-center gap-1">
|
|
550
550
|
<span :class="cn('font-medium truncate max-w-140px', compact ? 'text-sm font-580' : 'text-body-14-regular', labelItemClass)">{{ item.label }}</span>
|
|
551
|
-
<TelaBadge v-if="item.isMultiModal" variant="filled" class="py-[2px] bg-
|
|
551
|
+
<TelaBadge v-if="item.isMultiModal" variant="filled" class="py-[2px] bg-muted" text-class="leading-none">
|
|
552
552
|
{{ labelMultimodal }}
|
|
553
553
|
</TelaBadge>
|
|
554
554
|
<span v-if="item.cost !== undefined" class="text-10px font-580 ml-1px">
|
|
555
|
-
<span class="text-
|
|
556
|
-
<span class="text-
|
|
555
|
+
<span class="text-primary">{{ renderCostIndicator(item.cost)?.blackSymbols }}</span>
|
|
556
|
+
<span class="text-neutral-300">{{ renderCostIndicator(item.cost)?.graySymbols }}</span>
|
|
557
557
|
</span>
|
|
558
558
|
</div>
|
|
559
|
-
<span v-if="item.description" :class="cn('font-normal text-sm leading-none text-
|
|
559
|
+
<span v-if="item.description" :class="cn('font-normal text-sm leading-none text-secondary', descriptionClass)">
|
|
560
560
|
{{ item.description }}
|
|
561
561
|
</span>
|
|
562
562
|
<slot name="tags" :option="item" />
|
|
@@ -564,25 +564,25 @@ watch(innerValue, (raw) => {
|
|
|
564
564
|
</div>
|
|
565
565
|
<div v-if="item.maxInputTokens || item.maxOutputTokens" class="flex gap-3">
|
|
566
566
|
<div class="flex flex-col gap-1.5">
|
|
567
|
-
<span class="text-
|
|
567
|
+
<span class="text-tertiary leading-none text-9px uppercase tracking-wider font-semibold">
|
|
568
568
|
{{ labelInputMax }}
|
|
569
569
|
</span>
|
|
570
|
-
<span class="text-
|
|
570
|
+
<span class="text-tertiary leading-none text-11px uppercase font-semibold group-data-[highlighted]:text-neutral-700">
|
|
571
571
|
{{ handleFormatNumber(item.maxInputTokens) }}
|
|
572
572
|
</span>
|
|
573
573
|
</div>
|
|
574
574
|
<div class="flex flex-col gap-1.5">
|
|
575
|
-
<span class="text-
|
|
575
|
+
<span class="text-tertiary leading-none text-9px uppercase tracking-wider font-semibold">
|
|
576
576
|
{{ labelOutputMax }}
|
|
577
577
|
</span>
|
|
578
|
-
<span class="text-
|
|
578
|
+
<span class="text-tertiary leading-none text-11px uppercase font-semibold group-data-[highlighted]:text-neutral-700">
|
|
579
579
|
{{ handleFormatNumber(item.maxOutputTokens) }}
|
|
580
580
|
</span>
|
|
581
581
|
</div>
|
|
582
582
|
</div>
|
|
583
583
|
</div>
|
|
584
584
|
<div class="flex gap-2 items-center">
|
|
585
|
-
<TelaIcon v-if="item.children?.length" name="i-ph-caret-right" size="14px"
|
|
585
|
+
<TelaIcon v-if="item.children?.length" name="i-ph-caret-right" size="14px" color="icon-tertiary" />
|
|
586
586
|
<ComboboxItemIndicator v-else>
|
|
587
587
|
<TelaIcon name="i-ph-check" size="14px" />
|
|
588
588
|
</ComboboxItemIndicator>
|
|
@@ -602,7 +602,7 @@ watch(innerValue, (raw) => {
|
|
|
602
602
|
v-for="child in item.children"
|
|
603
603
|
:key="child.value"
|
|
604
604
|
:class="cn(
|
|
605
|
-
'relative flex cursor-pointer select-none justify-between items-center rounded-lg px-2 py-2 text-sm font-medium text-
|
|
605
|
+
'relative flex cursor-pointer select-none justify-between items-center rounded-lg px-2 py-2 text-sm font-medium text-primary outline-none hover:bg-muted',
|
|
606
606
|
!compact && '!px-1.5',
|
|
607
607
|
)"
|
|
608
608
|
@click.stop="handleChildSelect(child, $event)"
|
|
@@ -617,7 +617,7 @@ watch(innerValue, (raw) => {
|
|
|
617
617
|
</div>
|
|
618
618
|
<div class="flex flex-col">
|
|
619
619
|
<span :class="cn('font-medium truncate max-w-140px', compact ? 'text-sm font-580' : 'text-body-14-regular', labelItemClass)">{{ child.label }}</span>
|
|
620
|
-
<span v-if="child.description" :class="cn('font-normal text-sm leading-none text-
|
|
620
|
+
<span v-if="child.description" :class="cn('font-normal text-sm leading-none text-secondary', descriptionClass)">
|
|
621
621
|
{{ child.description }}
|
|
622
622
|
</span>
|
|
623
623
|
</div>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# TelaCreateCard
|
|
2
|
+
|
|
3
|
+
A fixed-size entry point card for creating something new from files. Click it to open the native file picker, or drag files directly onto it. It emits a `change` event with the native input event so the parent owns the upload logic.
|
|
4
|
+
|
|
5
|
+
## Rules
|
|
6
|
+
|
|
7
|
+
- **Always** handle the `change` event — the component only surfaces files, it does not upload them.
|
|
8
|
+
- The card has a **fixed footprint** (`w-221px h-128px`). Lay it out in a grid or flex row alongside other cards; do not stretch it.
|
|
9
|
+
- Use it as the **first cell** in a list of existing items (the "add new" affordance), not as a standalone hero.
|
|
10
|
+
- Set `multiple={false}` when the flow only accepts a single file.
|
|
11
|
+
- Pass `name` when the input participates in a native `<form>` submission.
|
|
12
|
+
- Use `disabled` while an upload is in flight — it blocks both click and drag-and-drop.
|
|
13
|
+
|
|
14
|
+
## Examples
|
|
15
|
+
|
|
16
|
+
### Default
|
|
17
|
+
|
|
18
|
+
```vue
|
|
19
|
+
<TelaCreateCard @change="handleChange" />
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
function handleChange(event: Event) {
|
|
24
|
+
const input = event.target as HTMLInputElement
|
|
25
|
+
const files = input.files
|
|
26
|
+
// upload files...
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Custom Labels
|
|
31
|
+
|
|
32
|
+
```vue
|
|
33
|
+
<TelaCreateCard
|
|
34
|
+
title="Upload dataset"
|
|
35
|
+
description="Drop a CSV or"
|
|
36
|
+
browse-label="choose a file"
|
|
37
|
+
:multiple="false"
|
|
38
|
+
@change="handleChange"
|
|
39
|
+
/>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Without Browse Label
|
|
43
|
+
|
|
44
|
+
```vue
|
|
45
|
+
<!-- Empty browse-label hides the underlined call-to-action -->
|
|
46
|
+
<TelaCreateCard
|
|
47
|
+
title="New canvas"
|
|
48
|
+
description="Drag files here"
|
|
49
|
+
browse-label=""
|
|
50
|
+
@change="handleChange"
|
|
51
|
+
/>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Disabled (e.g. while uploading)
|
|
55
|
+
|
|
56
|
+
```vue
|
|
57
|
+
<TelaCreateCard :disabled="isUploading" @change="handleChange" />
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### In a Grid
|
|
61
|
+
|
|
62
|
+
```vue
|
|
63
|
+
<div class="grid grid-cols-3 gap-4">
|
|
64
|
+
<TelaCreateCard @change="handleChange" />
|
|
65
|
+
<TelaCard v-for="item in items" :key="item.id" h-full>...</TelaCard>
|
|
66
|
+
</div>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Props
|
|
70
|
+
|
|
71
|
+
| Prop | Type | Default | Description |
|
|
72
|
+
|------|------|---------|-------------|
|
|
73
|
+
| `title` | `string` | `'New analysis'` | Heading shown inside the card |
|
|
74
|
+
| `description` | `string` | `'Drag files or'` | Leading helper text before the browse label |
|
|
75
|
+
| `browseLabel` | `string` | `'browse your computer'` | Underlined call-to-action appended after the description. Empty string hides it |
|
|
76
|
+
| `multiple` | `boolean` | `true` | Whether the file input accepts multiple files |
|
|
77
|
+
| `name` | `string` | — | `name` attribute forwarded to the underlying file input |
|
|
78
|
+
| `disabled` | `boolean` | `false` | Disables clicking and drag-and-drop, and dims the card |
|
|
79
|
+
|
|
80
|
+
## Events
|
|
81
|
+
|
|
82
|
+
| Event | Payload | Description |
|
|
83
|
+
|-------|---------|-------------|
|
|
84
|
+
| `change` | `Event` | Native input change event, fired on file selection and on drop. Read files from `(event.target as HTMLInputElement).files` |
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3'
|
|
2
|
+
import CreateCard from './create-card.vue'
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof CreateCard> = {
|
|
5
|
+
title: 'Core/CreateCard',
|
|
6
|
+
component: CreateCard,
|
|
7
|
+
parameters: {
|
|
8
|
+
layout: 'centered',
|
|
9
|
+
docs: {
|
|
10
|
+
description: {
|
|
11
|
+
component: 'A fixed-size entry point card for creating something new from files. Click to open the file picker, or drag files onto it. Emits `change` with the native input event.',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
argTypes: {
|
|
16
|
+
title: {
|
|
17
|
+
control: 'text',
|
|
18
|
+
description: 'Heading shown inside the card.',
|
|
19
|
+
},
|
|
20
|
+
description: {
|
|
21
|
+
control: 'text',
|
|
22
|
+
description: 'Leading helper text before the browse label.',
|
|
23
|
+
},
|
|
24
|
+
browseLabel: {
|
|
25
|
+
control: 'text',
|
|
26
|
+
description: 'Underlined call-to-action appended after the description.',
|
|
27
|
+
},
|
|
28
|
+
multiple: {
|
|
29
|
+
control: 'boolean',
|
|
30
|
+
description: 'Whether the file input accepts multiple files.',
|
|
31
|
+
},
|
|
32
|
+
name: {
|
|
33
|
+
control: 'text',
|
|
34
|
+
description: 'Name attribute forwarded to the underlying file input.',
|
|
35
|
+
},
|
|
36
|
+
disabled: {
|
|
37
|
+
control: 'boolean',
|
|
38
|
+
description: 'Disables clicking and drag-and-drop.',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
args: {
|
|
42
|
+
onChange: (event: Event) => {
|
|
43
|
+
const input = event.target as HTMLInputElement
|
|
44
|
+
// eslint-disable-next-line no-console
|
|
45
|
+
console.log('change', input.files)
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default meta
|
|
51
|
+
|
|
52
|
+
type Story = StoryObj<typeof meta>
|
|
53
|
+
|
|
54
|
+
export const Default: Story = {
|
|
55
|
+
render: args => ({
|
|
56
|
+
components: { CreateCard },
|
|
57
|
+
setup() {
|
|
58
|
+
return { args }
|
|
59
|
+
},
|
|
60
|
+
template: '<CreateCard v-bind="args" @change="args.onChange" />',
|
|
61
|
+
}),
|
|
62
|
+
args: {
|
|
63
|
+
title: 'New analysis',
|
|
64
|
+
description: 'Drag files or',
|
|
65
|
+
browseLabel: 'browse your computer',
|
|
66
|
+
multiple: true,
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const Disabled: Story = {
|
|
71
|
+
render: args => ({
|
|
72
|
+
components: { CreateCard },
|
|
73
|
+
setup() {
|
|
74
|
+
return { args }
|
|
75
|
+
},
|
|
76
|
+
template: '<CreateCard v-bind="args" @change="args.onChange" />',
|
|
77
|
+
}),
|
|
78
|
+
args: {
|
|
79
|
+
disabled: true,
|
|
80
|
+
},
|
|
81
|
+
parameters: {
|
|
82
|
+
docs: {
|
|
83
|
+
description: {
|
|
84
|
+
story: 'Disabled state — clicking and drag-and-drop are inert and the card is dimmed.',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const CustomLabels: Story = {
|
|
91
|
+
render: args => ({
|
|
92
|
+
components: { CreateCard },
|
|
93
|
+
setup() {
|
|
94
|
+
return { args }
|
|
95
|
+
},
|
|
96
|
+
template: '<CreateCard v-bind="args" @change="args.onChange" />',
|
|
97
|
+
}),
|
|
98
|
+
args: {
|
|
99
|
+
title: 'Upload dataset',
|
|
100
|
+
description: 'Drop a CSV or',
|
|
101
|
+
browseLabel: 'choose a file',
|
|
102
|
+
multiple: false,
|
|
103
|
+
},
|
|
104
|
+
parameters: {
|
|
105
|
+
docs: {
|
|
106
|
+
description: {
|
|
107
|
+
story: 'Custom title, description, and browse label. Set `multiple: false` to accept a single file.',
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const NoBrowseLabel: Story = {
|
|
114
|
+
render: args => ({
|
|
115
|
+
components: { CreateCard },
|
|
116
|
+
setup() {
|
|
117
|
+
return { args }
|
|
118
|
+
},
|
|
119
|
+
template: '<CreateCard v-bind="args" @change="args.onChange" />',
|
|
120
|
+
}),
|
|
121
|
+
args: {
|
|
122
|
+
title: 'New canvas',
|
|
123
|
+
description: 'Drag files here',
|
|
124
|
+
browseLabel: '',
|
|
125
|
+
},
|
|
126
|
+
parameters: {
|
|
127
|
+
docs: {
|
|
128
|
+
description: {
|
|
129
|
+
story: 'Omitting `browseLabel` (empty string) hides the underlined call-to-action.',
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
}
|