@meistrari/tela-build 1.33.0 → 1.34.1
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.mdx +22 -0
- package/components/tela/button/button.vue +2 -2
- package/components/tela/card/card.mdx +31 -1
- package/components/tela/card/card.vue +4 -5
- package/components/tela/checkbox/checkbox.vue +16 -6
- package/components/tela/dropdown-menu/dropdown-menu.mdx +17 -0
- package/components/tela/header/header.mdx +10 -0
- package/components/tela/icon-button/icon-button.mdx +94 -0
- package/components/tela/input/input.mdx +18 -8
- package/components/tela/input/input.stories.ts +1 -6
- package/components/tela/input/{tela-input.vue → input.vue} +0 -3
- package/components/tela/modal/modal.mdx +71 -0
- package/components/tela/select-menu/select-menu.mdx +30 -0
- package/components/tela/sidebar/sidebar-item.vue +2 -4
- package/components/tela/sidebar/sidebar.mdx +32 -0
- package/components/tela/status/status.mdx +5 -0
- package/components/tela/table/table-cell.vue +1 -1
- package/components/tela/table/table.mdx +4 -0
- package/composables/optimistic-async-data/stories/optimistic-async-data-action-demo.vue +0 -1
- package/composables/optimistic-async-data/stories/optimistic-async-data-shared-state-demo.vue +1 -3
- package/lib/doc-generator.ts +50 -3
- package/lib/tela-build-skill.ts +1 -5
- package/package.json +1 -1
|
@@ -7,6 +7,28 @@ import * as ButtonStories from './button.stories.ts';
|
|
|
7
7
|
|
|
8
8
|
A versatile button component with multiple variants, sizes, and states. Supports icons, loading states, and can function as a link when provided with a `to` prop. Built for consistent UI interactions across the application.
|
|
9
9
|
|
|
10
|
+
## Rules
|
|
11
|
+
|
|
12
|
+
- **Never** use `variant="ghost"` — use `secondary` instead.
|
|
13
|
+
- **Never** use `size="sm"` — always `md` (default) or `lg`.
|
|
14
|
+
- Since `md` is the default, omit the `size` prop unless you need `lg`.
|
|
15
|
+
- When a `TelaButton` sits beside a `TelaInput` in the same row, both must use `size="md"` (default). Never mix sizes in the same row — it creates misaligned rows.
|
|
16
|
+
- **Icon API — two separate props, never one.** The icon **name** goes on `icon`; its **position** is toggled with the boolean `leading`. Default position is trailing. `leading` never takes a value.
|
|
17
|
+
|
|
18
|
+
```vue
|
|
19
|
+
<!-- ✅ Correct — icon on the leading side -->
|
|
20
|
+
<TelaButton icon="i-ph-export" leading>Export</TelaButton>
|
|
21
|
+
|
|
22
|
+
<!-- ✅ Correct — icon on the trailing side (default) -->
|
|
23
|
+
<TelaButton icon="i-ph-arrow-right">Next</TelaButton>
|
|
24
|
+
|
|
25
|
+
<!-- ❌ Wrong — `leading` is a boolean, not a slot for the icon name -->
|
|
26
|
+
<TelaButton leading="i-ph-export">Export</TelaButton>
|
|
27
|
+
|
|
28
|
+
<!-- ❌ Wrong — no `icon` prop means no icon renders -->
|
|
29
|
+
<TelaButton leading>Export</TelaButton>
|
|
30
|
+
```
|
|
31
|
+
|
|
10
32
|
## Examples
|
|
11
33
|
|
|
12
34
|
### All Sizes
|
|
@@ -27,8 +27,8 @@ const resolvedVariant = computed(() =>
|
|
|
27
27
|
|
|
28
28
|
const sizeStyle = computed(() => (({
|
|
29
29
|
sm: 'text-12px leading-16px font-580 tracking-normal px-10px py-4px rounded-9px',
|
|
30
|
-
md: 'text-14px leading-18px font-580 -tracking-0.
|
|
31
|
-
lg: 'text-16px leading-20px font-580 -tracking-0.
|
|
30
|
+
md: 'text-14px leading-18px font-580 -tracking-0.15px px-12px py-7px rounded-11px [&>svg]:text-10px',
|
|
31
|
+
lg: 'text-16px leading-20px font-580 -tracking-0.15px px-16px py-10px rounded-13px [&>svg]:text-12px',
|
|
32
32
|
}) as Record<ButtonSize, string>)[resolvedSize.value] ?? '')
|
|
33
33
|
|
|
34
34
|
const variantStyle = computed(() => (({
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
A surface container component used to group related content with consistent visual boundaries.
|
|
4
4
|
|
|
5
|
+
## Rules
|
|
6
|
+
|
|
7
|
+
- **Always** use the `size` prop to control card padding and radius — do not use `content-padding` or `border-radius` props.
|
|
8
|
+
- Attributify spacing/radius props (`p-*`, `px-*`, `py-*`, `rounded-*`) are intentionally ignored on `<TelaCard>` to keep `size` deterministic. If you need a forced override, use `class` with the important modifier (e.g. `class="!p-0"`).
|
|
9
|
+
- Cards in grids must use `h-full` for consistent heights.
|
|
10
|
+
- **Never** wrap `TelaTable` in `TelaCard` — tables are their own surface.
|
|
11
|
+
- Small gaps look like accidents next to large typography. Use at least `gap-4px` when a card contains a large typographic element — prefer `gap-6px` when the large text is the primary content.
|
|
12
|
+
|
|
5
13
|
## Size Prop
|
|
6
14
|
|
|
7
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.
|
|
@@ -45,12 +53,34 @@ Always use the `size` prop to control card padding — it maps directly to the s
|
|
|
45
53
|
|
|
46
54
|
### Card Grid
|
|
47
55
|
|
|
56
|
+
Cards in grids must use `h-full` for consistent heights.
|
|
57
|
+
|
|
48
58
|
```vue
|
|
49
59
|
<div class="grid grid-cols-3 gap-4">
|
|
50
|
-
<TelaCard v-for="item in items" :key="item.id">...</TelaCard>
|
|
60
|
+
<TelaCard v-for="item in items" :key="item.id" h-full>...</TelaCard>
|
|
51
61
|
</div>
|
|
52
62
|
```
|
|
53
63
|
|
|
64
|
+
### Gap with Large Text
|
|
65
|
+
|
|
66
|
+
```vue
|
|
67
|
+
<!-- Wrong — too tight -->
|
|
68
|
+
<TelaCard size="sm">
|
|
69
|
+
<div flex flex-col gap-1>
|
|
70
|
+
<span body-12-medium text-secondary>Total Requests</span>
|
|
71
|
+
<span heading-h2-semibold text-primary>128,430</span>
|
|
72
|
+
</div>
|
|
73
|
+
</TelaCard>
|
|
74
|
+
|
|
75
|
+
<!-- Correct -->
|
|
76
|
+
<TelaCard size="sm" h-full>
|
|
77
|
+
<div flex flex-col gap-4px>
|
|
78
|
+
<span body-12-medium text-secondary>Total Requests</span>
|
|
79
|
+
<h1 heading-h1-semibold text-primary>128,430</h1>
|
|
80
|
+
</div>
|
|
81
|
+
</TelaCard>
|
|
82
|
+
```
|
|
83
|
+
|
|
54
84
|
## Props
|
|
55
85
|
|
|
56
86
|
| Prop | Type | Default | Description |
|
|
@@ -13,12 +13,11 @@ const props = withDefaults(defineProps<{
|
|
|
13
13
|
})
|
|
14
14
|
|
|
15
15
|
const sizeStyles = computed(() => (({
|
|
16
|
-
sm: { padding: 'p-24px'
|
|
17
|
-
md: { padding: 'p-32px
|
|
18
|
-
}) as Record<string, { padding: string
|
|
16
|
+
sm: { padding: 'p-24px' },
|
|
17
|
+
md: { padding: 'p-32px' },
|
|
18
|
+
}) as Record<string, { padding: string }>)[props.size ?? 'md'] ?? { padding: '' })
|
|
19
19
|
|
|
20
20
|
const paddingClass = computed(() => props.contentPadding ?? sizeStyles.value.padding)
|
|
21
|
-
const borderRadiusClass = computed(() => props.borderRadius ?? sizeStyles.value.rounded)
|
|
22
21
|
const rootEl = ref<HTMLElement | null>(null)
|
|
23
22
|
|
|
24
23
|
defineExpose({
|
|
@@ -27,7 +26,7 @@ defineExpose({
|
|
|
27
26
|
</script>
|
|
28
27
|
|
|
29
28
|
<template>
|
|
30
|
-
<div ref="rootEl" :class="cn('bg border-0.5px border', paddingClass,
|
|
29
|
+
<div ref="rootEl" :class="cn('rounded-16px bg border-0.5px border', paddingClass, props.class)">
|
|
31
30
|
<slot />
|
|
32
31
|
</div>
|
|
33
32
|
</template>
|
|
@@ -22,7 +22,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|
|
22
22
|
props.class,
|
|
23
23
|
)"
|
|
24
24
|
>
|
|
25
|
-
<CheckboxIndicator class="grid place-content-center">
|
|
25
|
+
<CheckboxIndicator force-mount class="grid place-content-center">
|
|
26
26
|
<svg
|
|
27
27
|
viewBox="0 0 24 24"
|
|
28
28
|
fill="none"
|
|
@@ -30,14 +30,24 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|
|
30
30
|
stroke-linejoin="round"
|
|
31
31
|
class="shrink-0 stroke-3 stroke-white-1000 w-14px h-11px"
|
|
32
32
|
>
|
|
33
|
-
<
|
|
34
|
-
|
|
33
|
+
<path
|
|
34
|
+
data-show-icon="true"
|
|
35
35
|
d="M5 13 L10 18 L20 6"
|
|
36
|
-
|
|
37
|
-
:animate="{ scale: 1, pathLength: 1 }"
|
|
38
|
-
:transition="{ duration: 0.2, ease: [0.645, 0.045, 0.355, 1] }"
|
|
36
|
+
pathLength="1"
|
|
39
37
|
/>
|
|
40
38
|
</svg>
|
|
41
39
|
</CheckboxIndicator>
|
|
42
40
|
</CheckboxRoot>
|
|
43
41
|
</template>
|
|
42
|
+
|
|
43
|
+
<style scoped>
|
|
44
|
+
[data-show-icon="true"] {
|
|
45
|
+
stroke-dasharray: 1;
|
|
46
|
+
stroke-dashoffset: 1;
|
|
47
|
+
transition: stroke-dashoffset 200ms cubic-bezier(0.645, 0.045, 0.355, 1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
[data-state="checked"] [data-show-icon="true"] {
|
|
51
|
+
stroke-dashoffset: 0;
|
|
52
|
+
}
|
|
53
|
+
</style>
|
|
@@ -7,6 +7,23 @@ import * as DropdownMenuStories from './dropdown-menu.stories.ts';
|
|
|
7
7
|
|
|
8
8
|
A dropdown menu component built on radix-vue. Provides a flexible menu system with support for grouping, icons, tooltips, checkboxes, and search functionality. Items can be organized into groups and can include click handlers, disabled states, and custom styling.
|
|
9
9
|
|
|
10
|
+
## Rules
|
|
11
|
+
|
|
12
|
+
- **Always** include an `icon` on every item. Icons give each action a visual identity and make the menu scannable.
|
|
13
|
+
|
|
14
|
+
### DropdownMenu vs SelectMenu
|
|
15
|
+
|
|
16
|
+
Use `TelaDropdownMenu` when clicking triggers a side effect. Use `TelaSelectMenu` when the action updates a bound value.
|
|
17
|
+
|
|
18
|
+
| Scenario | Component |
|
|
19
|
+
|---|---|
|
|
20
|
+
| Export, duplicate, archive, configure | `TelaDropdownMenu` |
|
|
21
|
+
| Navigation links and actions | `TelaDropdownMenu` |
|
|
22
|
+
| Choose environment, select status, pick a model | `TelaSelectMenu` |
|
|
23
|
+
| Filtering or form field with options | `TelaSelectMenu` |
|
|
24
|
+
|
|
25
|
+
**The test:** If clicking triggers a side effect → `TelaDropdownMenu`. If it updates a bound value → `TelaSelectMenu`.
|
|
26
|
+
|
|
10
27
|
## Examples
|
|
11
28
|
|
|
12
29
|
### Basic Usage
|
|
@@ -7,6 +7,16 @@ import * as HeaderStories from './header.stories.ts';
|
|
|
7
7
|
|
|
8
8
|
A composable header component for fullscreen views. Provides a sticky top bar with three layout zones: leading (left), center, and trailing (right). Built with slot-based sub-components for maximum flexibility.
|
|
9
9
|
|
|
10
|
+
## Rules
|
|
11
|
+
|
|
12
|
+
- **Always** use `TelaHeader` on details pages (single resource, editor, settings) — never build page chrome from scratch.
|
|
13
|
+
- For root pages (index, list, dashboard) use `TelaSidebar` instead.
|
|
14
|
+
|
|
15
|
+
| Page type | Shell |
|
|
16
|
+
|---|---|
|
|
17
|
+
| Details page (single resource, editor, settings) | `TelaHeader` |
|
|
18
|
+
| Root page (index, list, dashboard) | `TelaSidebar` |
|
|
19
|
+
|
|
10
20
|
## Examples
|
|
11
21
|
|
|
12
22
|
### Basic Usage
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Meta } from '@storybook/blocks';
|
|
2
|
+
|
|
3
|
+
<Meta title="Inputs/IconButton" />
|
|
4
|
+
|
|
5
|
+
# TelaIconButton
|
|
6
|
+
|
|
7
|
+
An icon-only button for compact actions (toolbars, modal close buttons, inline controls). Supports multiple sizes, colors, loading, and disabled states, and can render as a link when `to` is provided.
|
|
8
|
+
|
|
9
|
+
## Rules
|
|
10
|
+
|
|
11
|
+
- **Never** use `color="primary"` — always `color="secondary"`.
|
|
12
|
+
- The default is `primary`, so **always set `color` explicitly**.
|
|
13
|
+
|
|
14
|
+
## Examples
|
|
15
|
+
|
|
16
|
+
### Basic Usage
|
|
17
|
+
|
|
18
|
+
```vue
|
|
19
|
+
<TelaIconButton
|
|
20
|
+
icon="i-ph-gear"
|
|
21
|
+
color="secondary"
|
|
22
|
+
/>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### As a Close Button (inside Modal)
|
|
26
|
+
|
|
27
|
+
```vue
|
|
28
|
+
<TelaIconButton
|
|
29
|
+
icon="i-ph-x"
|
|
30
|
+
size="md"
|
|
31
|
+
color="secondary"
|
|
32
|
+
outline-none
|
|
33
|
+
p-8px mt--12px mr--16px
|
|
34
|
+
@click="isOpen = false"
|
|
35
|
+
/>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### As a Link
|
|
39
|
+
|
|
40
|
+
```vue
|
|
41
|
+
<TelaIconButton
|
|
42
|
+
icon="i-ph-arrow-up-right"
|
|
43
|
+
color="secondary"
|
|
44
|
+
to="https://example.com"
|
|
45
|
+
target="_blank"
|
|
46
|
+
/>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Loading State
|
|
50
|
+
|
|
51
|
+
```vue
|
|
52
|
+
<TelaIconButton
|
|
53
|
+
icon="i-ph-gear"
|
|
54
|
+
color="secondary"
|
|
55
|
+
loading
|
|
56
|
+
/>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Props
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
type IconButtonSize = '3xs' | '2xs' | 'xs' | 'sm' | 'md'
|
|
63
|
+
type IconButtonColor = 'primary' | 'secondary'
|
|
64
|
+
|
|
65
|
+
type IconButtonProps = {
|
|
66
|
+
color?: IconButtonColor
|
|
67
|
+
icon: string
|
|
68
|
+
size?: IconButtonSize
|
|
69
|
+
outline?: boolean
|
|
70
|
+
disabled?: boolean
|
|
71
|
+
loading?: boolean
|
|
72
|
+
active?: boolean
|
|
73
|
+
iconClass?: string
|
|
74
|
+
buttonClass?: string
|
|
75
|
+
to?: string
|
|
76
|
+
target?: '_blank' | null
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Events
|
|
81
|
+
|
|
82
|
+
- `longpress` — emitted after a 700ms press-and-hold (ignored when `disabled`).
|
|
83
|
+
|
|
84
|
+
## Features
|
|
85
|
+
|
|
86
|
+
- **Compact**: Icon-only surface for dense layouts.
|
|
87
|
+
- **Link Functionality**: Renders as `NuxtLink` when `to` is provided.
|
|
88
|
+
- **Long-press**: Built-in long-press detection.
|
|
89
|
+
- **States**: `disabled`, `loading`, `active`, `outline`.
|
|
90
|
+
|
|
91
|
+
## Accessibility
|
|
92
|
+
|
|
93
|
+
- Renders as a `<button>` (or `<NuxtLink>` when `to` is set).
|
|
94
|
+
- Always pair with a visible label or tooltip so the action is discoverable by screen readers.
|
|
@@ -7,6 +7,24 @@ import * as InputStories from './input.stories.ts';
|
|
|
7
7
|
|
|
8
8
|
A flexible input component that supports both text input and textarea modes. Includes features like labels, error states, icons, clear buttons, and validation feedback. Supports v-model binding for two-way data binding.
|
|
9
9
|
|
|
10
|
+
## Rules
|
|
11
|
+
|
|
12
|
+
- **Always** use `size="md"` (default). When a `TelaInput` sits next to a `TelaButton` in the same row, both must be `md` — never mix sizes, it creates misaligned rows.
|
|
13
|
+
- Inside a `TelaModal`, the input must be `w-full`.
|
|
14
|
+
|
|
15
|
+
### Search
|
|
16
|
+
|
|
17
|
+
Search fields **never** have a submit button. Use an inline `TelaInput` that filters as the user types.
|
|
18
|
+
|
|
19
|
+
```vue
|
|
20
|
+
<!-- Correct -->
|
|
21
|
+
<TelaInput v-model="query" placeholder="Search..." hide-label />
|
|
22
|
+
|
|
23
|
+
<!-- Wrong — no submit button next to a search input -->
|
|
24
|
+
<TelaInput v-model="query" placeholder="Search..." hide-label />
|
|
25
|
+
<TelaButton>Search</TelaButton>
|
|
26
|
+
```
|
|
27
|
+
|
|
10
28
|
## Examples
|
|
11
29
|
|
|
12
30
|
### With Clear Button
|
|
@@ -127,8 +145,6 @@ const value = ref('')
|
|
|
127
145
|
<ArgTypes />
|
|
128
146
|
|
|
129
147
|
```typescript
|
|
130
|
-
type InputSize = 'sm' | 'md'
|
|
131
|
-
|
|
132
148
|
type InputProps = {
|
|
133
149
|
id?: string
|
|
134
150
|
modelValue?: string | number
|
|
@@ -136,7 +152,6 @@ type InputProps = {
|
|
|
136
152
|
placeholder?: string
|
|
137
153
|
hideLabel?: boolean
|
|
138
154
|
disabled?: boolean
|
|
139
|
-
size?: InputSize
|
|
140
155
|
error?: string | false
|
|
141
156
|
showClearButton?: boolean
|
|
142
157
|
tabindex?: number
|
|
@@ -166,11 +181,6 @@ type InputProps = {
|
|
|
166
181
|
- **Two-way Binding**: Full v-model support
|
|
167
182
|
- **Textarea Mode**: Multi-line input support
|
|
168
183
|
|
|
169
|
-
## Sizes
|
|
170
|
-
|
|
171
|
-
- **sm**: Small input with compact padding
|
|
172
|
-
- **md**: Medium input (default) with comfortable spacing
|
|
173
|
-
|
|
174
184
|
## Accessibility
|
|
175
185
|
|
|
176
186
|
- Proper label-input association with `for` and `id` attributes
|
|
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/vue3'
|
|
|
2
2
|
|
|
3
3
|
import { fn } from '@storybook/test'
|
|
4
4
|
|
|
5
|
-
import Input from './
|
|
5
|
+
import Input from './input.vue'
|
|
6
6
|
|
|
7
7
|
const meta: Meta<typeof Input> = {
|
|
8
8
|
title: 'Core/Input',
|
|
@@ -40,11 +40,6 @@ const meta: Meta<typeof Input> = {
|
|
|
40
40
|
control: 'boolean',
|
|
41
41
|
description: 'Disable the input, preventing user interaction.',
|
|
42
42
|
},
|
|
43
|
-
size: {
|
|
44
|
-
control: 'select',
|
|
45
|
-
options: ['sm', 'md'],
|
|
46
|
-
description: 'Size of the input field. Controls height and padding.',
|
|
47
|
-
},
|
|
48
43
|
error: {
|
|
49
44
|
control: 'text',
|
|
50
45
|
description: 'Error message to display below the input. Set to false to hide error state.',
|
|
@@ -8,7 +8,6 @@ const props = defineProps<{
|
|
|
8
8
|
placeholder?: string
|
|
9
9
|
hideLabel?: boolean
|
|
10
10
|
disabled?: boolean
|
|
11
|
-
size?: 'sm' | 'md'
|
|
12
11
|
error?: string | false
|
|
13
12
|
showClearButton?: boolean
|
|
14
13
|
tabindex?: number
|
|
@@ -31,7 +30,6 @@ const emit = defineEmits<{
|
|
|
31
30
|
|
|
32
31
|
const isTextarea = computed(() => props.type === 'textarea')
|
|
33
32
|
const attributes = computed(() => ({
|
|
34
|
-
size: isTextarea.value ? 'md' : undefined,
|
|
35
33
|
type: isTextarea.value ? undefined : props.type,
|
|
36
34
|
modelValue: props.modelValue,
|
|
37
35
|
value: props.modelValue,
|
|
@@ -128,7 +126,6 @@ defineExpose({
|
|
|
128
126
|
rounded-10px bg-white
|
|
129
127
|
class="focus-within-border-strong focus-within-ring-2 focus-within-ring-gray-100 [box-shadow:0_1px_4px_0_rgba(103,127,148,0.03)]"
|
|
130
128
|
:class="[
|
|
131
|
-
size === 'sm' && 'py-7px !px-12px',
|
|
132
129
|
disabled && '!bg-gray-50 cursor-not-allowed color-gray-600',
|
|
133
130
|
$attrs.class,
|
|
134
131
|
error && '!border-red-900 !text-red-900 !bg-[#FFDBDB66]',
|
|
@@ -6,6 +6,77 @@ import { Meta, ArgTypes } from '@storybook/blocks';
|
|
|
6
6
|
|
|
7
7
|
A modal dialog component that displays content in a centered overlay. Useful for important messages, confirmations, forms, and content that requires user attention.
|
|
8
8
|
|
|
9
|
+
`TelaDialog` is deprecated — **always** use `TelaModal`.
|
|
10
|
+
|
|
11
|
+
## Rules
|
|
12
|
+
|
|
13
|
+
- **Never** pass `title` to `TelaModal` — build the header yourself inside the slot.
|
|
14
|
+
- **Always** set `:compact="true"` and `:hide-dividers="true"`.
|
|
15
|
+
- **Always** set `:is-close-icon="false"` — build the close button manually with `TelaIconButton`.
|
|
16
|
+
- Close button negative margins must equal **half the modal padding** → `mt--{p/2} mr--{p/2}`.
|
|
17
|
+
- Footer layout: `flex justify-end gap-8px`.
|
|
18
|
+
- Every control inside a modal must fill the modal width. See [Width](#width) below.
|
|
19
|
+
|
|
20
|
+
### Recommended Structure
|
|
21
|
+
|
|
22
|
+
```vue
|
|
23
|
+
<TelaModal
|
|
24
|
+
v-model="isOpen"
|
|
25
|
+
modal-width="md"
|
|
26
|
+
:compact="true"
|
|
27
|
+
:hide-dividers="true"
|
|
28
|
+
:is-close-icon="false"
|
|
29
|
+
>
|
|
30
|
+
<div flex="~ col" w-full gap-16px>
|
|
31
|
+
<!-- Header -->
|
|
32
|
+
<div flex="~ row justify-between" items-start>
|
|
33
|
+
<div flex="~ col" gap-4px>
|
|
34
|
+
<h4 heading-h4-semibold text-primary>Dialog Title</h4>
|
|
35
|
+
<p body-14-regular text-secondary>Dialog subtitle or description.</p>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<!-- Close Button — negative margins = half the modal padding -->
|
|
39
|
+
<TelaIconButton
|
|
40
|
+
icon="i-ph-x"
|
|
41
|
+
size="md"
|
|
42
|
+
color="secondary"
|
|
43
|
+
outline-none
|
|
44
|
+
p-8px mt--12px mr--16px
|
|
45
|
+
@click="isOpen = false"
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<!-- Content -->
|
|
50
|
+
<div flex="~ col" gap-8px>
|
|
51
|
+
<!-- ... -->
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<!-- Footer -->
|
|
55
|
+
<div flex gap-8px justify-end>
|
|
56
|
+
<TelaButton variant="secondary" @click="isOpen = false">Cancel</TelaButton>
|
|
57
|
+
<TelaButton>Submit</TelaButton>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</TelaModal>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Width
|
|
64
|
+
|
|
65
|
+
Every control inside a modal must fill the modal width. `w-full` on the root of a compound component (`TelaSelectMenu`, `TelaCombobox`) does not reach the trigger — use `trigger-class="w-full"` instead.
|
|
66
|
+
|
|
67
|
+
```vue
|
|
68
|
+
<!-- Correct -->
|
|
69
|
+
<TelaInput v-model="val" label="Name" w-full />
|
|
70
|
+
<TelaTextarea v-model="val" w-full />
|
|
71
|
+
<TelaToggleGroup v-model="val" :options="opts" w-full />
|
|
72
|
+
<TelaSelectMenu v-model="val" :options="opts" trigger-class="w-full" />
|
|
73
|
+
<TelaCombobox v-model="val" :options="opts" trigger-class="w-full" />
|
|
74
|
+
|
|
75
|
+
<!-- Wrong — w-full on root doesn't reach the trigger -->
|
|
76
|
+
<TelaSelectMenu v-model="val" :options="opts" w-full />
|
|
77
|
+
<TelaCombobox v-model="val" :options="opts" w-full />
|
|
78
|
+
```
|
|
79
|
+
|
|
9
80
|
## Props
|
|
10
81
|
|
|
11
82
|
<ArgTypes />
|
|
@@ -7,6 +7,36 @@ import * as SelectMenuStories from './select-menu.stories.ts';
|
|
|
7
7
|
|
|
8
8
|
A flexible select menu component built on reka-ui. Allows users to select a single option from a dropdown list. Supports icons, descriptions, tooltips, grouping, and custom styling.
|
|
9
9
|
|
|
10
|
+
## Rules
|
|
11
|
+
|
|
12
|
+
- **Never** place icons before text in select options.
|
|
13
|
+
- **Always** set `trigger-class` to constrain the trigger width — never let it overflow or expand the layout.
|
|
14
|
+
|
|
15
|
+
| Context | Value |
|
|
16
|
+
|---|---|
|
|
17
|
+
| Inside modals and forms | `trigger-class="w-full"` |
|
|
18
|
+
| In toolbars | `trigger-class="w-160px"` (or a fixed width) |
|
|
19
|
+
|
|
20
|
+
### SelectMenu vs DropdownMenu
|
|
21
|
+
|
|
22
|
+
Use `TelaSelectMenu` when the action updates a bound value. Use `TelaDropdownMenu` when clicking triggers a side effect.
|
|
23
|
+
|
|
24
|
+
| Scenario | Component |
|
|
25
|
+
|---|---|
|
|
26
|
+
| Choose environment, select status, pick a model | `TelaSelectMenu` |
|
|
27
|
+
| Filtering or form field with options | `TelaSelectMenu` |
|
|
28
|
+
| Export, duplicate, archive, configure | `TelaDropdownMenu` |
|
|
29
|
+
| Navigation links and actions | `TelaDropdownMenu` |
|
|
30
|
+
|
|
31
|
+
**The test:** If clicking updates a bound value → `TelaSelectMenu`. If it triggers a side effect → `TelaDropdownMenu`.
|
|
32
|
+
|
|
33
|
+
### Filters
|
|
34
|
+
|
|
35
|
+
When building a filter panel, always use `TelaSelectMenu` for options with multiple choices — never toggles or checkboxes. Filter panels must include **Apply / Clear** actions and must never auto-apply on change:
|
|
36
|
+
|
|
37
|
+
- Apply: default primary `TelaButton`
|
|
38
|
+
- Clear: `variant="secondary"` `TelaButton`
|
|
39
|
+
|
|
10
40
|
## Examples
|
|
11
41
|
|
|
12
42
|
### Basic Usage
|
|
@@ -43,10 +43,8 @@ const rootAttrs = computed(() => {
|
|
|
43
43
|
/>
|
|
44
44
|
<div
|
|
45
45
|
:class="cn(
|
|
46
|
-
'absolute inset-0 size-full rounded-[14px] z-0 border-[0.5px] border-transparent
|
|
47
|
-
'group-
|
|
48
|
-
'group-data-[active=true]:bg-neutral-200 group-focus-visible:border-strong group-[[data-active=false]:hover]:border-strong group-hover:scale-100 group-hover:opacity-100',
|
|
49
|
-
'group-focus-visible:border-strong group-focus-visible:scale-100 group-focus-visible:opacity-100',
|
|
46
|
+
'absolute inset-0 size-full rounded-[14px] z-0 border-[0.5px] border-transparent',
|
|
47
|
+
isActive ? 'bg-neutral-200 group-focus-visible:border-strong' : 'bg scale-10 opacity-0 duration-150 ease-out origin-center group-hover:border-strong group-hover:scale-100 group-hover:opacity-100 group-focus-visible:border-strong group-focus-visible:scale-100 group-focus-visible:opacity-100',
|
|
50
48
|
)"
|
|
51
49
|
/>
|
|
52
50
|
</div>
|
|
@@ -7,6 +7,38 @@ import * as SidebarStories from './sidebar.stories.ts';
|
|
|
7
7
|
|
|
8
8
|
A composable sidebar navigation system built from focused sub-components. Fixed 80px wide and full-height, designed for icon-based navigation with labels.
|
|
9
9
|
|
|
10
|
+
## Rules
|
|
11
|
+
|
|
12
|
+
- **Always** use `TelaSidebar` on root pages (index, list, dashboard) — never hand-roll a sidebar, `<nav>`, or custom rail.
|
|
13
|
+
- For details pages (single resource, editor, settings) use `TelaHeader` instead — the sidebar is not part of a details surface.
|
|
14
|
+
- **Always position the sidebar as `sticky`** (`sticky top-0 h-screen`). It must stay pinned to the viewport as the main content scrolls — never allow it to scroll away with the page.
|
|
15
|
+
|
|
16
|
+
| Page type | Shell |
|
|
17
|
+
|---|---|
|
|
18
|
+
| Root page (index, list, dashboard) | `TelaSidebar` |
|
|
19
|
+
| Details page (single resource, editor, settings) | `TelaHeader` |
|
|
20
|
+
|
|
21
|
+
### Layout
|
|
22
|
+
|
|
23
|
+
Place the sidebar and main content in a flex row. The sidebar is `sticky`; the `<main>` takes the remaining width with its own `max-width` + `mx-auto`.
|
|
24
|
+
|
|
25
|
+
```vue
|
|
26
|
+
<div flex>
|
|
27
|
+
<TelaSidebar sticky top-0 h-screen>
|
|
28
|
+
...
|
|
29
|
+
</TelaSidebar>
|
|
30
|
+
|
|
31
|
+
<main flex="~ col" w-full max-w-1380px mx-auto px-48px py-32px>
|
|
32
|
+
<!-- page content -->
|
|
33
|
+
</main>
|
|
34
|
+
</div>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Never:**
|
|
38
|
+
- Let the sidebar scroll with the page (missing `sticky`)
|
|
39
|
+
- Use `h-screen` / `h-[calc(...)]` height hacks on the page wrapper — flex + the sticky sidebar handle sizing
|
|
40
|
+
- Replace `<main>` with a plain `<div>` — semantics matter
|
|
41
|
+
|
|
10
42
|
## Examples
|
|
11
43
|
|
|
12
44
|
### Full Sidebar
|
|
@@ -7,6 +7,11 @@ import * as StatusStories from './status.stories.ts';
|
|
|
7
7
|
|
|
8
8
|
A status component that displays different workflow and task statuses with appropriate icons, colors, and labels. Supports multiple status types including pending, running, completed, error, and review states. Features smooth animations and automatic width transitions for a polished user experience.
|
|
9
9
|
|
|
10
|
+
## Rules
|
|
11
|
+
|
|
12
|
+
- **Always** use `<TelaStatus />` for any status indicator (workflow status, event status, health state, etc.). Never build a custom status with icons + color classes.
|
|
13
|
+
- `text-success` and `text-error` are reserved for `<TelaStatus />` — never apply them to regular text content.
|
|
14
|
+
|
|
10
15
|
## Examples
|
|
11
16
|
|
|
12
17
|
### Default Status
|
|
@@ -206,6 +206,10 @@ The Table system consists of these components:
|
|
|
206
206
|
- `TelaTableCaption` - Accessible table description (`<caption>`)
|
|
207
207
|
- `TelaTableEmpty` - Empty state component for when no data is available
|
|
208
208
|
|
|
209
|
+
## Rules
|
|
210
|
+
|
|
211
|
+
- **Never use `items-end` + `text-right` on cells**, except when styling the last column. Right-alignment is reserved for the trailing column (typically numeric totals or actions); applying it to middle columns breaks visual scan order and column alignment.
|
|
212
|
+
|
|
209
213
|
## Features
|
|
210
214
|
|
|
211
215
|
- **Responsive**: Horizontal scrolling on smaller screens
|
package/composables/optimistic-async-data/stories/optimistic-async-data-shared-state-demo.vue
CHANGED
|
@@ -4,7 +4,7 @@ import { useOptimisticAsyncData } from '..'
|
|
|
4
4
|
import { useOptimisticAsyncState } from '../useOptimisticAsyncState'
|
|
5
5
|
import OptimisticAsyncProvider from '../optimistic-async-provider.vue'
|
|
6
6
|
import TelaButton from '../../../components/tela/button/button.vue'
|
|
7
|
-
import TelaInput from '../../../components/tela/input/
|
|
7
|
+
import TelaInput from '../../../components/tela/input/input.vue'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* SHARED STATE DEMO (with OptimisticAsyncProvider)
|
|
@@ -204,13 +204,11 @@ const EditorComponent = defineComponent({
|
|
|
204
204
|
v-model="editName"
|
|
205
205
|
label="Name"
|
|
206
206
|
:placeholder="data.name"
|
|
207
|
-
size="sm"
|
|
208
207
|
/>
|
|
209
208
|
<TelaInput
|
|
210
209
|
v-model="editEmail"
|
|
211
210
|
label="Email"
|
|
212
211
|
:placeholder="data.email"
|
|
213
|
-
size="sm"
|
|
214
212
|
/>
|
|
215
213
|
<TelaButton
|
|
216
214
|
variant="primary"
|
package/lib/doc-generator.ts
CHANGED
|
@@ -235,7 +235,7 @@ async function globAsync(pattern: string, options: any): Promise<string[]> {
|
|
|
235
235
|
export function generateTagName(fileName: string): string {
|
|
236
236
|
// Convert file name to PascalCase tag name
|
|
237
237
|
// Components in tela/ directory should preserve the Tela prefix
|
|
238
|
-
// e.g.,
|
|
238
|
+
// e.g., input -> TelaInput, button -> TelaButton
|
|
239
239
|
|
|
240
240
|
let tagName: string
|
|
241
241
|
|
|
@@ -566,8 +566,55 @@ export function generateDocsToDirectory(componentDocs: ComponentDoc[], typeResol
|
|
|
566
566
|
const body = dedent`
|
|
567
567
|
# Tela Build
|
|
568
568
|
|
|
569
|
-
|
|
570
|
-
Use
|
|
569
|
+
UI library and design engineering principles for building polished interfaces.
|
|
570
|
+
Always consult before making UI changes. Use when building UI components, reviewing frontend code, or polishing visual details — animations, hover states, shadows, borders, typography, micro-interactions,
|
|
571
|
+
spacing, border radius, optical alignment. Triggers on UI polish, "feels off", design details.
|
|
572
|
+
|
|
573
|
+
## Objective
|
|
574
|
+
|
|
575
|
+
**Tela Build skill exists to produce pixel-perfect UI.** Every output — new component, review, or polish pass — must be measured against that bar. "Close enough" is not acceptable. Concentric border radius, optical alignment, intentional gaps, exact tokens, clean negative space, precise typography — all of it must be correct, not approximate. If a detail is off by a pixel or a token is wrong, it is not done.
|
|
576
|
+
|
|
577
|
+
## Instructions
|
|
578
|
+
|
|
579
|
+
- IMPORTANT: Before making ANY UI change or introducing NEW UI, ALWAYS consult this Tela Build components index first.
|
|
580
|
+
- Prefer reusing existing Tela components and patterns. Only create new components when a clear gap exists.
|
|
581
|
+
- When a new component is needed, align with existing naming, props, events, and patterns documented here.
|
|
582
|
+
- Keep this documentation current when components are added or changed (update supporting files under \
|
|
583
|
+
\`components/\`).
|
|
584
|
+
|
|
585
|
+
## Quick Reference
|
|
586
|
+
|
|
587
|
+
| Category | When to use |
|
|
588
|
+
|---|---|
|
|
589
|
+
| Tokens | Choosing colors, text styles, backgrounds, borders, or icon colors |
|
|
590
|
+
| Interfaces | Layout decisions, component rules, spacing, styling patterns, do/don'ts |
|
|
591
|
+
| Surfaces | Border radius, depth strategy, elevation, outlines, hit areas |
|
|
592
|
+
| Typography | Heading tokens, body font sizes, font weights |
|
|
593
|
+
| Animations | Deciding when to animate, easing, duration, springs, and staggered enter animations |
|
|
594
|
+
|
|
595
|
+
## Common Mistakes
|
|
596
|
+
|
|
597
|
+
| Mistake | Fix |
|
|
598
|
+
|---|---|
|
|
599
|
+
| Same border radius on parent and child | Calculate \`outerRadius = innerRadius + padding\` |
|
|
600
|
+
| Icons look off-center | Adjust optically with padding or fix SVG directly |
|
|
601
|
+
| \`transition: all\` on elements | Specify exact properties |
|
|
602
|
+
| First-frame animation stutter | Add \`will-change: transform\` (sparingly) |
|
|
603
|
+
|
|
604
|
+
## Review Output Format
|
|
605
|
+
|
|
606
|
+
Always present changes as a markdown table with Before and After columns. Include every change you made — not just a subset. Never list findings as separate "Before:" / "After:" lines outside of a table. Group changes by principle using a heading above each table, and keep each row focused on a single diff so the reader can scan the whole list quickly.
|
|
607
|
+
|
|
608
|
+
## Review Checklist
|
|
609
|
+
|
|
610
|
+
- [ ] Nested rounded elements use concentric border radius
|
|
611
|
+
- [ ] Before building, scan the Components Index and reuse existing components — never hand-roll UI when a component exists
|
|
612
|
+
- [ ] Double-check negative space, gaps, and padding — squint at the layout and confirm it reads as clean and intentional, not cramped or accidental
|
|
613
|
+
- [ ] Typography follows the rules — semantic \`heading-*\` and \`body-*\` tokens only (no raw \`text-Xpx\`/\`font-*\`), the right token for each level (h1 page title, h2 section, h3/h4 subsection, body-14 supporting), and a mix of regular/medium/semibold weights so hierarchy isn't flat
|
|
614
|
+
- [ ] Icons are optically centered, not just geometrically
|
|
615
|
+
- [ ] Headings use text-wrap: balance
|
|
616
|
+
- [ ] No \`transition: all\` — only specific properties
|
|
617
|
+
- [ ] \`will-change\` only on transform/opacity/filter, never \`all\`
|
|
571
618
|
|
|
572
619
|
## Components Index
|
|
573
620
|
|
package/lib/tela-build-skill.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
export const telaBuildSkill = {
|
|
2
2
|
name: 'tela-build',
|
|
3
|
-
description:
|
|
4
|
-
'Use when working on any UI in this repository. Ask for component props, slots, variants, and usage examples.',
|
|
5
|
-
'Always consult this Tela Build index BEFORE making any UI changes or creating new UI.',
|
|
6
|
-
'Use it for components, layouts, styling, interactions, and design-token decisions.',
|
|
7
|
-
].join('\n'),
|
|
3
|
+
description: 'Use when working on any UI in this repository. Ask for component props, slots, variants, and usage examples. Always consult this Tela Build index BEFORE making any UI changes or creating new UI. Use it for components, layouts, styling, interactions, and design-token decisions.',
|
|
8
4
|
} as const
|