@meistrari/tela-build 1.32.0 → 1.34.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.
Files changed (29) hide show
  1. package/components/tela/button/button.mdx +22 -0
  2. package/components/tela/card/card.mdx +31 -1
  3. package/components/tela/card/card.vue +4 -5
  4. package/components/tela/dropdown-menu/DropdownMenu.vue +7 -3
  5. package/components/tela/dropdown-menu/dropdown-menu.mdx +17 -0
  6. package/components/tela/header/header.mdx +10 -0
  7. package/components/tela/header/trailing/pagination/pagination-next-button.vue +1 -1
  8. package/components/tela/header/trailing/pagination/pagination-prev-button.vue +1 -1
  9. package/components/tela/header/trailing/trailing-actions.vue +1 -0
  10. package/components/tela/icon-button/icon-button.mdx +94 -0
  11. package/components/tela/initials.vue +1 -1
  12. package/components/tela/input/input.mdx +18 -0
  13. package/components/tela/input/tela-input.vue +7 -8
  14. package/components/tela/menubar/menubar-content.vue +1 -1
  15. package/components/tela/menubar/menubar-item.vue +1 -1
  16. package/components/tela/menubar/menubar-trigger.vue +1 -1
  17. package/components/tela/modal/modal.mdx +71 -0
  18. package/components/tela/scroll-area/scroll-area.vue +17 -8
  19. package/components/tela/select-menu/select-menu.mdx +30 -0
  20. package/components/tela/sidebar/sidebar-logo.vue +1 -1
  21. package/components/tela/sidebar/sidebar-user.vue +2 -2
  22. package/components/tela/sidebar/sidebar.mdx +32 -0
  23. package/components/tela/status/status.mdx +5 -0
  24. package/components/tela/table/table-cell.vue +1 -1
  25. package/components/tela/table/table.mdx +4 -0
  26. package/css/reset.css +9 -1
  27. package/lib/doc-generator.ts +49 -2
  28. package/lib/tela-build-skill.ts +1 -5
  29. 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
@@ -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', rounded: 'rounded-12px' },
17
- md: { padding: 'p-32px sm:p-48px', rounded: 'rounded-24px' },
18
- }) as Record<string, { padding: string, rounded: string }>)[props.size ?? 'md'] ?? { padding: '', rounded: '' })
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, borderRadiusClass, props.class)">
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>
@@ -1,12 +1,15 @@
1
1
  <script setup lang="ts">
2
+ import type { HTMLAttributes } from 'vue'
3
+ import type { TooltipProps } from '../tooltip/tooltip.vue'
4
+
2
5
  import { VisuallyHidden } from 'radix-vue'
6
+
3
7
  import DropdownMenuRoot from './DropdownMenuRoot.vue'
4
8
  import DropdownMenuContent from './DropdownMenuContent.vue'
5
9
  import DropdownMenuGroup from './DropdownMenuGroup.vue'
6
10
  import DropdownMenuItem from './DropdownMenuItem.vue'
7
11
  import DropdownMenuLabel from './DropdownMenuLabel.vue'
8
12
  import DropdownMenuTrigger from './DropdownMenuTrigger.vue'
9
- import type { TooltipProps } from '../tooltip/tooltip.vue'
10
13
 
11
14
  const props = withDefaults(defineProps<{
12
15
  items: {
@@ -27,7 +30,8 @@ const props = withDefaults(defineProps<{
27
30
  allowSearch?: boolean
28
31
  searchPlaceholder?: string
29
32
  shouldBeModal?: boolean
30
- contentClass?: string
33
+ triggerClass?: HTMLAttributes['class']
34
+ contentClass?: HTMLAttributes['class']
31
35
  align?: 'start' | 'end' | 'center'
32
36
  }>(), {
33
37
  align: 'start',
@@ -66,7 +70,7 @@ function onToggle(open: boolean) {
66
70
  <template>
67
71
  <div data-allow-mismatch @click.stop>
68
72
  <DropdownMenuRoot :modal="shouldBeModal" :default-open="isStorybook" @update:open="onToggle">
69
- <DropdownMenuTrigger as-child class="data-[state=open]:bg-gray-100" @click.prevent.stop>
73
+ <DropdownMenuTrigger as-child :class="cn('data-[state=open]:bg-muted', triggerClass)" @click.prevent.stop>
70
74
  <slot />
71
75
  <VisuallyHidden v-if="isStorybook" as-child>
72
76
  <button />
@@ -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
@@ -14,7 +14,7 @@ const emit = defineEmits<{
14
14
  size="md"
15
15
  color="secondary"
16
16
  :icon-class="disabled ? 'text-icon-subtle' : 'text-icon'"
17
- button-class="!disabled:bg-transparent"
17
+ button-class="!rounded-12px !disabled:bg-transparent"
18
18
  :disabled="disabled"
19
19
  @click="emit('click')"
20
20
  />
@@ -14,7 +14,7 @@ const emit = defineEmits<{
14
14
  size="md"
15
15
  color="secondary"
16
16
  :icon-class="disabled ? 'text-icon-subtle' : 'text-icon'"
17
- button-class="!disabled:bg-transparent"
17
+ button-class="!rounded-12px !disabled:bg-transparent"
18
18
  :disabled="disabled"
19
19
  @click="emit('click')"
20
20
  />
@@ -18,6 +18,7 @@ defineProps<{
18
18
  size="md"
19
19
  color="secondary"
20
20
  icon="i-ph-dots-three-bold"
21
+ button-class="!rounded-12px"
21
22
  icon-class="text-neutral-950"
22
23
  />
23
24
  </TelaDropdownMenu>
@@ -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.
@@ -15,7 +15,7 @@ const sizeStyles = computed(() => {
15
15
  case 'sm':
16
16
  return 'w-32px h-32px text-16px'
17
17
  case 'md':
18
- return 'w-40px h-40px text-18px'
18
+ return 'w-40px h-40px text-20px'
19
19
  case 'lg':
20
20
  return 'w-64px h-64px text-28px'
21
21
  case 'xl':
@@ -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
@@ -123,10 +123,10 @@ defineExpose({
123
123
  transition
124
124
  cursor-text
125
125
  overflow-hidden
126
- px-3 py-8px
127
- b="0.5px border-strong"
126
+ px-10px py-4px
127
+ b="0.5px border"
128
128
  rounded-10px bg-white
129
- class="focus-within-b-gray-400 focus-within-ring-2 focus-within-ring-gray-100"
129
+ 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
130
  :class="[
131
131
  size === 'sm' && 'py-7px !px-12px',
132
132
  disabled && '!bg-gray-50 cursor-not-allowed color-gray-600',
@@ -137,8 +137,8 @@ defineExpose({
137
137
  flex items-center
138
138
  @click="$el.querySelector(isTextarea ? 'textarea' : 'input')?.focus()"
139
139
  >
140
- <div v-if="icon" flex mr-6px>
141
- <TelaIcon :name="icon" size="sm" text="gray-400" />
140
+ <div v-if="icon" flex mr-8px>
141
+ <TelaIcon :name="icon" size="13px" text="icon-tertiary" />
142
142
  </div>
143
143
  <div
144
144
  flex="~ col" grow align-center h-full
@@ -155,7 +155,6 @@ defineExpose({
155
155
  align-center
156
156
  :class="[disabled && 'cursor-not-allowed', props.labelClass]"
157
157
  >
158
-
159
158
  <slot name="label-leading" />
160
159
  {{ label }}
161
160
  </label>
@@ -175,13 +174,13 @@ defineExpose({
175
174
  bg-transparent
176
175
  z-0
177
176
  h-full
178
- class="placeholder-gray-300 focus-visible:outline-none! leading-170% tracking-.01em"
177
+ body-14-regular
178
+ class="placeholder-text-tertiary focus-visible:outline-none!"
179
179
  resize-none
180
180
  :disabled="disabled"
181
181
  :tabindex="tabindex"
182
182
  :class="[
183
183
  disabled && 'cursor-not-allowed',
184
- size === 'sm' && 'leading-20px body-14-regular placeholder:body-14-regular',
185
184
  props.inputFontClass ? props.inputFontClass : 'text-14px',
186
185
  !isTextarea && '!h-1.7em',
187
186
  ]"
@@ -14,7 +14,7 @@ const props = withDefaults(
14
14
  {
15
15
  align: 'start',
16
16
  alignOffset: 0,
17
- sideOffset: 8,
17
+ sideOffset: 4,
18
18
  disablePortal: false,
19
19
  },
20
20
  )
@@ -22,7 +22,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
22
22
  <MenubarItem
23
23
  v-bind="forwarded"
24
24
  :class="cn(
25
- 'relative flex cursor-pointer select-none items-center rounded-xl px-3 py-1.5 text-body-14-medium font-460 outline-none focus:bg-muted data-[disabled]:pointer-events-none data-[disabled]:opacity-40',
25
+ 'relative flex cursor-pointer select-none items-center rounded-xl px-3 py-1.5 body-14-medium text-primary outline-none focus:bg-muted data-[disabled]:pointer-events-none data-[disabled]:opacity-40',
26
26
  inset && 'pl-8',
27
27
  props.class,
28
28
  )"
@@ -17,7 +17,7 @@ const forwardedProps = useForwardProps(delegatedProps)
17
17
  v-bind="forwardedProps"
18
18
  :class="
19
19
  cn(
20
- 'flex items-center gap-2 cursor-pointer select-none px-2 py-1.5 text-sm font-medium outline-none rounded-lg hover:bg-lowered data-[state=open]:bg-lowered',
20
+ 'flex items-center gap-2 cursor-pointer select-none px-2 py-1.5 text-sm font-medium outline-none rounded-lg hover:bg-muted data-[state=open]:bg-muted',
21
21
  props.class,
22
22
  )
23
23
  "
@@ -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 />
@@ -1,34 +1,43 @@
1
1
  <script setup lang="ts">
2
+ import type { ScrollAreaRootProps } from 'reka-ui'
3
+ import type { HTMLAttributes } from 'vue'
4
+
5
+ import { computed, onMounted, useTemplateRef } from 'vue'
6
+
2
7
  import {
3
8
  ScrollAreaCorner,
4
9
  ScrollAreaRoot,
5
-
6
10
  ScrollAreaViewport,
7
11
  } from 'reka-ui'
8
- import type { ScrollAreaRootProps } from 'reka-ui'
9
- import { computed } from 'vue'
10
- import type { HTMLAttributes } from 'vue'
12
+
11
13
  import ScrollBar from './scroll-bar.vue'
12
14
 
13
15
  const props = withDefaults(
14
16
  defineProps<ScrollAreaRootProps
15
- & { class?: HTMLAttributes['class'], orientation?: 'vertical' | 'horizontal' | 'both', scrollThumbClass?: HTMLAttributes['class'] }>(),
17
+ & { class?: HTMLAttributes['class'], orientation?: 'vertical' | 'horizontal' | 'both', scrollThumbClass?: HTMLAttributes['class'], disableViewportFocus?: boolean }>(),
16
18
  {
17
19
  orientation: 'vertical',
18
- scrollThumbClass: '',
20
+ disableViewportFocus: false,
19
21
  },
20
22
  )
21
23
 
22
24
  const delegatedProps = computed(() => {
23
- const { class: _, ...delegated } = props
25
+ const { class: _, disableViewportFocus: __, ...delegated } = props
24
26
 
25
27
  return delegated
26
28
  })
29
+
30
+ const viewportRef = useTemplateRef<InstanceType<typeof ScrollAreaViewport>>('viewportRef')
31
+
32
+ onMounted(() => {
33
+ if (props.disableViewportFocus)
34
+ viewportRef.value?.viewportElement?.setAttribute('tabindex', '-1')
35
+ })
27
36
  </script>
28
37
 
29
38
  <template>
30
39
  <ScrollAreaRoot v-bind="delegatedProps" :class="cn('relative overflow-hidden', props.class)">
31
- <ScrollAreaViewport class="h-full w-full rounded-[inherit]">
40
+ <ScrollAreaViewport ref="viewportRef" class="size-full rounded-[inherit] focus-visible:outline-border-strong">
32
41
  <slot />
33
42
  </ScrollAreaViewport>
34
43
  <ScrollBar v-if="props.orientation === 'vertical' || props.orientation === 'both'" orientation="vertical" :class="props.scrollThumbClass" />
@@ -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
@@ -2,7 +2,7 @@
2
2
  import { NuxtLink } from '#components'
3
3
 
4
4
  defineProps<{
5
- src?: string
5
+ src?: string | null
6
6
  alt: string
7
7
  to?: string
8
8
  }>()
@@ -1,8 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  defineProps<{
3
3
  image?: string
4
- name: string
5
- email: string
4
+ name?: string
5
+ email?: string
6
6
  actions: {
7
7
  label: string
8
8
  icon: string
@@ -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
@@ -10,7 +10,7 @@ const props = defineProps<{
10
10
  <td
11
11
  :class="
12
12
  cn(
13
- 'p-4 align-middle [&:has([role=checkbox])]:pr-0',
13
+ 'p-4 body-14-regular text-primary align-middle [&:has([role=checkbox])]:pr-0',
14
14
  props.class,
15
15
  )
16
16
  "
@@ -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/css/reset.css CHANGED
@@ -383,4 +383,12 @@ Make all headers have regular font weight by default
383
383
 
384
384
  h1, h1, h2, h3, h4, h5, h6 {
385
385
  --at-apply: 'font-regular'
386
- }
386
+ }
387
+
388
+ /*
389
+ Reset user-select from images
390
+ */
391
+
392
+ img {
393
+ user-select: none;
394
+ }
@@ -566,8 +566,55 @@ export function generateDocsToDirectory(componentDocs: ComponentDoc[], typeResol
566
566
  const body = dedent`
567
567
  # Tela Build
568
568
 
569
- This Skill provides structured documentation for the Tela Build component library (Vue 3 + Nuxt).
570
- Use it when building, refactoring, or using Tela componentsprops, events, slots, and examples are included where available.
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 detailsanimations, 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
 
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/tela-build",
3
- "version": "1.32.0",
3
+ "version": "1.34.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "app.config.ts",