@meistrari/tela-build 1.74.3 → 1.75.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/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  Latest updates and announcements.
4
4
 
5
+ ## September 22, 2026
6
+
7
+ - [Prompt Input](/templates/chat#prompt-input) adapts to its shell: inside `TelaChatFloatingWindow` and `TelaChatSidepanelWindow` it uses a 12px radius and 8px right padding, and the action menu trigger stays inline. On full-page chats it keeps the 16px radius and pulls the trigger flush with the editor.
8
+
9
+ - [Empty State](/templates/chat#empty-state) uses the `heading-h3` and `body-14` type scale instead of custom sizes, with 4px between title and description.
10
+
11
+ - [Model Selector](/templates/chat#model-selector) trigger uses `body-14-medium` with 7px vertical padding, and its caret is now `icon-tertiary`, matching the select menu.
12
+
13
+ - [Icon Button](/components/icon-button) `sm` padding is 7px so the button lands on 32px with its 18px (`md`) icon.
14
+
15
+ - [Select Menu](/components/select-menu) and [Combobox](/components/combobox) caret uses `icon-tertiary`.
16
+
17
+ - [Colors](/colors) adds `taupe` and `stone` palettes: warm and cool neutral scales available as `bg-`, `text-`, and `border-` utilities.
18
+
5
19
  ## September 18, 2026
6
20
 
7
21
  - [Floating chat](/templates/chat#floating) docs now show `TelaChatAnswer` docked above the prompt input inside the window.
@@ -15,16 +15,16 @@ defineProps<{
15
15
  :class="compact ? 'gap-24px px-12px pt-16px' : 'gap-40px px-24px'"
16
16
  >
17
17
  <div flex="~ col" gap-32px :class="compact ? 'mt-auto' : ''">
18
- <div v-if="title || description" flex="~ col" items-center gap-2px text-center>
18
+ <div v-if="title || description" flex="~ col" items-center gap-4px text-center>
19
19
  <component
20
20
  :is="compact ? 'h5' : 'h1'"
21
21
  v-if="title"
22
22
  text-primary
23
- :class="compact ? 'heading-h4-semibold' : 'text-24px font-semibold leading-28px tracking-[-.8px]'"
23
+ :class="compact ? 'heading-h4-semibold' : 'heading-h3-semibold'"
24
24
  >
25
25
  {{ title }}
26
26
  </component>
27
- <p v-if="description" text-secondary :class="compact ? 'body-14-regular' : 'text-16px leading-20px tracking-[-.2px]'">
27
+ <p v-if="description" text-secondary body-14-regular>
28
28
  {{ description }}
29
29
  </p>
30
30
  </div>
@@ -58,7 +58,7 @@ function getTabLabel(tab: string) {
58
58
  :get-tab-label="getTabLabel"
59
59
  icon-class="scale-90"
60
60
  content-class="w-[min(440px,calc(100vw-32px))]"
61
- trigger-class="rounded-8px! gap-6px! bg-transparent! border-none! hover:bg-muted! shadow-none! duration-80 pl-10px! pr-8px! data-[state=open]:bg-muted!"
61
+ trigger-class="rounded-8px! gap-6px! bg-transparent! body-14-medium! py-7px! border-none! hover:bg-muted! shadow-none! duration-80 pl-10px! pr-8px! data-[state=open]:bg-muted!"
62
62
  @update:model-value="model = $event ?? null"
63
63
  />
64
64
  </template>
@@ -0,0 +1,6 @@
1
+ import type { InjectionKey } from 'vue'
2
+
3
+ export type ChatShell = 'floating' | 'sidepanel'
4
+
5
+ /** Provided by `TelaChatFloatingWindow` and `TelaChatSidepanelWindow` so nested primitives can adapt to the shell. */
6
+ export const chatShellContextKey: InjectionKey<ChatShell> = Symbol('tela-chat-shell')
@@ -149,11 +149,8 @@ The window opens over its own launcher: the surface springs out of the launcher'
149
149
 
150
150
  The widget header inside the window gets two extra actions automatically: **expand** grows the window until 8px from the top of the viewport (and back), and **minimize** closes it. See [Widget Header & History](#widget-header--history).
151
151
 
152
- When the assistant needs a decision, dock a [`TelaChatAnswer`](#answer) above the prompt input: wrap both in one `[data-chat-composer]` element with a 12px gap so `TelaChatBody` pins and measures them together.
153
-
154
152
  ```vue
155
153
  <script setup lang="ts">
156
- import type { TelaChatAnswerQuestion } from '@meistrari/tela-build/types'
157
154
  import { getMimeTypeIcon } from '@meistrari/tela-build/utils/files'
158
155
 
159
156
  const isOpen = ref(false)
@@ -176,61 +173,6 @@ const conversations = [
176
173
  { id: 'c2', title: 'Sidepanel layout questions' },
177
174
  { id: 'c3', title: 'Model selector options' },
178
175
  ]
179
-
180
- // The assistant needs a decision before it continues. The host owns the
181
- // question sequence and the selection; the card only emits intent.
182
- const questions: TelaChatAnswerQuestion[] = [
183
- {
184
- id: 'tone',
185
- title: 'Which tone should the announcement use?',
186
- type: 'single',
187
- recommendFirst: true,
188
- options: [
189
- { id: 'concise', label: 'Concise', description: 'Short and factual, for the changelog.' },
190
- { id: 'friendly', label: 'Friendly', description: 'Warmer copy, for the newsletter.' },
191
- { id: 'formal', label: 'Formal', description: 'For enterprise customers.' },
192
- ],
193
- },
194
- {
195
- id: 'sections',
196
- title: 'Which sections should it include?',
197
- type: 'multiple',
198
- options: [
199
- { id: 'highlights', label: 'Highlights' },
200
- { id: 'breaking', label: 'Breaking changes' },
201
- { id: 'upgrade', label: 'Upgrade guide' },
202
- ],
203
- },
204
- ]
205
- const index = ref(0)
206
- const expanded = ref(true)
207
- const answering = ref(true)
208
- const answers = ref<Record<string, string[]>>({})
209
- const question = computed(() => questions[index.value]!)
210
- const selected = computed(() => answers.value[question.value.id] ?? [])
211
-
212
- function select(id: string) {
213
- answers.value[question.value.id] = [id]
214
- }
215
-
216
- function toggle(id: string, checked: boolean) {
217
- answers.value[question.value.id] = checked
218
- ? [...selected.value, id]
219
- : selected.value.filter(value => value !== id)
220
- }
221
-
222
- function advance() {
223
- if (index.value < questions.length - 1) {
224
- index.value++
225
- return
226
- }
227
-
228
- const summary = questions
229
- .map(item => `${item.title} ${(answers.value[item.id] ?? []).join(', ') || 'skipped'}`)
230
- .join(' ')
231
- messages.value.push({ id: `${Date.now()}`, role: 'user', text: summary })
232
- answering.value = false
233
- }
234
176
  </script>
235
177
 
236
178
  <template>
@@ -255,76 +197,53 @@ function advance() {
255
197
  </TelaChatConversationContent>
256
198
  </TelaChatConversation>
257
199
 
258
- <!-- One composer wrapper: the answer card docks above the prompt input. -->
259
- <div data-chat-composer class="flex flex-col gap-12px">
260
- <TelaChatAnswer
261
- v-if="answering"
262
- v-model:expanded="expanded"
263
- :question="question"
264
- :selected="selected"
265
- :current="index + 1"
266
- :total="questions.length"
267
- :can-continue="selected.length > 0"
268
- show-navigation
269
- :can-previous="index > 0"
270
- :can-next="index < questions.length - 1"
271
- @select="select"
272
- @toggle="toggle"
273
- @previous="index--"
274
- @next="index++"
275
- @skip="advance"
276
- @continue="advance"
277
- @dismiss="answering = false"
278
- />
279
-
280
- <TelaChatPromptInput>
281
- <TelaChatPromptInputTextarea placeholder="Ask anything" :empty="!draft" />
282
- <TelaChatPromptInputToolbar>
283
- <TelaChatPromptInputTools>
284
- <TelaChatPromptInputActionMenu>
285
- <TelaChatPromptInputActionMenuTrigger />
286
- <TelaChatPromptInputActionMenuContent>
287
- <TelaChatPromptInputActionMenuItem icon="i-ph-paperclip">
288
- Add files or photos
289
- </TelaChatPromptInputActionMenuItem>
290
- <TelaChatPromptInputActionMenuSub>
291
- <TelaChatPromptInputActionMenuSubTrigger icon="i-ph-clock-counter-clockwise">
292
- Recent files
293
- </TelaChatPromptInputActionMenuSubTrigger>
294
- <TelaChatPromptInputActionMenuSubContent>
295
- <TelaChatPromptInputActionMenuItem
296
- v-for="file in recentFiles"
297
- :key="file.id"
298
- >
299
- <img :src="getMimeTypeIcon(file.type)" class="w-16px h-16px mr-8px shrink-0" alt="">
300
- <span class="min-w-0 max-w-200px truncate">{{ file.label }}</span>
301
- </TelaChatPromptInputActionMenuItem>
302
- </TelaChatPromptInputActionMenuSubContent>
303
- </TelaChatPromptInputActionMenuSub>
304
- <TelaMenubarSeparator />
305
- <TelaChatPromptInputActionMenuSub>
306
- <TelaChatPromptInputActionMenuSubTrigger icon="i-ph-notepad" icon-size="16px">
307
- Skills
308
- </TelaChatPromptInputActionMenuSubTrigger>
309
- <TelaChatPromptInputActionMenuSubContent>
310
- <TelaChatPromptInputActionMenuItem
311
- v-for="skill in skills"
312
- :key="skill.id"
313
- @click="draft = skill.label"
314
- >
315
- <span class="min-w-0 max-w-200px truncate">{{ skill.label }}</span>
316
- </TelaChatPromptInputActionMenuItem>
317
- </TelaChatPromptInputActionMenuSubContent>
318
- </TelaChatPromptInputActionMenuSub>
319
- </TelaChatPromptInputActionMenuContent>
320
- </TelaChatPromptInputActionMenu>
321
- </TelaChatPromptInputTools>
322
- <TelaChatPromptInputActions>
323
- <TelaChatPromptSubmitButton status="ready" :disabled="!draft.trim()" />
324
- </TelaChatPromptInputActions>
325
- </TelaChatPromptInputToolbar>
326
- </TelaChatPromptInput>
327
- </div>
200
+ <TelaChatPromptInput>
201
+ <TelaChatPromptInputTextarea placeholder="Ask anything" :empty="!draft" />
202
+ <TelaChatPromptInputToolbar>
203
+ <TelaChatPromptInputTools>
204
+ <TelaChatPromptInputActionMenu>
205
+ <TelaChatPromptInputActionMenuTrigger />
206
+ <TelaChatPromptInputActionMenuContent>
207
+ <TelaChatPromptInputActionMenuItem icon="i-ph-paperclip">
208
+ Add files or photos
209
+ </TelaChatPromptInputActionMenuItem>
210
+ <TelaChatPromptInputActionMenuSub>
211
+ <TelaChatPromptInputActionMenuSubTrigger icon="i-ph-clock-counter-clockwise">
212
+ Recent files
213
+ </TelaChatPromptInputActionMenuSubTrigger>
214
+ <TelaChatPromptInputActionMenuSubContent>
215
+ <TelaChatPromptInputActionMenuItem
216
+ v-for="file in recentFiles"
217
+ :key="file.id"
218
+ >
219
+ <img :src="getMimeTypeIcon(file.type)" class="w-16px h-16px mr-8px shrink-0" alt="">
220
+ <span class="min-w-0 max-w-200px truncate">{{ file.label }}</span>
221
+ </TelaChatPromptInputActionMenuItem>
222
+ </TelaChatPromptInputActionMenuSubContent>
223
+ </TelaChatPromptInputActionMenuSub>
224
+ <TelaMenubarSeparator />
225
+ <TelaChatPromptInputActionMenuSub>
226
+ <TelaChatPromptInputActionMenuSubTrigger icon="i-ph-notepad" icon-size="16px">
227
+ Skills
228
+ </TelaChatPromptInputActionMenuSubTrigger>
229
+ <TelaChatPromptInputActionMenuSubContent>
230
+ <TelaChatPromptInputActionMenuItem
231
+ v-for="skill in skills"
232
+ :key="skill.id"
233
+ @click="draft = skill.label"
234
+ >
235
+ <span class="min-w-0 max-w-200px truncate">{{ skill.label }}</span>
236
+ </TelaChatPromptInputActionMenuItem>
237
+ </TelaChatPromptInputActionMenuSubContent>
238
+ </TelaChatPromptInputActionMenuSub>
239
+ </TelaChatPromptInputActionMenuContent>
240
+ </TelaChatPromptInputActionMenu>
241
+ </TelaChatPromptInputTools>
242
+ <TelaChatPromptInputActions>
243
+ <TelaChatPromptSubmitButton status="ready" :disabled="!draft.trim()" />
244
+ </TelaChatPromptInputActions>
245
+ </TelaChatPromptInputToolbar>
246
+ </TelaChatPromptInput>
328
247
  </TelaChatBody>
329
248
  </TelaChatFloatingWindow>
330
249
  </div>
@@ -1740,3 +1659,49 @@ type TextShimmerProps = {
1740
1659
  <TelaChatConversation />
1741
1660
  </div>
1742
1661
  ```
1662
+
1663
+ ## Folder Map
1664
+
1665
+ Where each primitive lives in `components/tela/chat/`. Component names come from the file path
1666
+ (`chat/chat-widget-header.vue` → `TelaChatWidgetHeader`).
1667
+
1668
+ ### Shells
1669
+
1670
+ | Folder / file | What it is |
1671
+ | --- | --- |
1672
+ | `floating/` | `TelaChatFloatingWindow` popover shell, its launcher `TelaChatFloatingWindowTrigger`, escape-layer tracking, and the window context (expand, close). |
1673
+ | `sidepanel/` | `TelaChatSidepanelWindow`, the in-flow docked shell. |
1674
+ | `chat-shell-context.ts` | Injection key both shells provide (`floating` or `sidepanel`) so nested primitives can adapt. |
1675
+ | `chat-widget-*.vue` | Chrome shared by the shells: `TelaChatWidgetHeader`, `TelaChatWidgetCard`, `TelaChatWidgetHistoryList`, `TelaChatWidgetSources` / `Source`. |
1676
+
1677
+ ### Body and conversation
1678
+
1679
+ | Folder / file | What it is |
1680
+ | --- | --- |
1681
+ | `chat-body.vue` | `TelaChatBody`, the scroll region that docks the prompt input and pads the conversation for it. `variant="widget"` or `"page"`. |
1682
+ | `conversation/` | `TelaChatConversation` and `TelaChatConversationContent`, the message list. |
1683
+ | `message/` | `TelaChatMessage` with avatar, content, and feedback controls. |
1684
+ | `answer/` | `TelaChatAnswer`, choice questions the assistant asks inline. |
1685
+ | `chat-empty-state.vue` | `TelaChatEmptyState`, shown before the first message. |
1686
+ | `chat-suggestions.vue`, `chat-suggestion.vue` | Suggested prompts. |
1687
+ | `chat-reasoning-step.vue` | `TelaChatReasoningStep`, collapsible tool or reasoning entry. |
1688
+ | `chat-text-shimmer.vue` | `TelaChatTextShimmer`, streaming placeholder text. |
1689
+
1690
+ ### Composer and run state
1691
+
1692
+ | Folder / file | What it is |
1693
+ | --- | --- |
1694
+ | `prompt-input/` | `TelaChatPromptInput` and its parts: textarea, toolbar, tools, action menu, attachments, hints. |
1695
+ | `chat-prompt-submit-button.vue` | `TelaChatPromptSubmitButton`, the send control. |
1696
+ | `chat-model-selector.vue` | `TelaChatModelSelector`, model picker for the toolbar. |
1697
+ | `chat-working-indicator.vue` | `TelaChatWorkingIndicator`, running-turn row with elapsed time. |
1698
+ | `chat-stop-run-button.vue` | `TelaChatStopRunButton`, replaces the prompt input while a turn runs. |
1699
+ | `chat-animate-enter.vue` | `TelaChatAnimateEnter`, enter/exit preset for swapping the input and stop button. |
1700
+ | `chat-powered-by-tela.vue` | `TelaChatPoweredByTela` footer. |
1701
+ | `icons/` | Provider marks (`TelaChatIconsAnthropic`, `Openai`, `Gemini`) and the Tela wordmark. |
1702
+
1703
+ ### Legacy thread chat
1704
+
1705
+ `index.vue` (`TelaChat`), `text-message/`, `text-input/`, `pure-text-input/`, `command/`,
1706
+ and `types.ts` are the older comment-thread chat used by the application. They are kept
1707
+ as is; new work should compose the primitives above.
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { hasOpenEscapeLayer } from './escape-layer'
3
+ import { chatShellContextKey } from '../chat-shell-context'
3
4
  import { floatingWindowContextKey } from './floating-window-context'
4
5
 
5
6
  const props = withDefaults(defineProps<{
@@ -90,6 +91,7 @@ onMounted(() => {
90
91
  measureLauncher()
91
92
  })
92
93
 
94
+ provide(chatShellContextKey, 'floating')
93
95
  provide(floatingWindowContextKey, {
94
96
  expanded,
95
97
  toggleExpanded: () => {
@@ -1,5 +1,11 @@
1
+ <script setup lang="ts">
2
+ import { chatShellContextKey } from '../chat-shell-context'
3
+
4
+ const isWidgetShell = Boolean(inject(chatShellContextKey, null))
5
+ </script>
6
+
1
7
  <template>
2
- <div ml--8px>
8
+ <div :class="cn(isWidgetShell ? 'ml--8px' : 'ml--10px')">
3
9
  <TelaMenubarRoot>
4
10
  <TelaMenubarMenu>
5
11
  <slot />
@@ -28,7 +28,6 @@ const EDITOR_MAX_HEIGHT = 228
28
28
  const COMPACT_EDITOR_MAX_HEIGHT = 140
29
29
 
30
30
  const editorRef = ref<HTMLElement | null>(null)
31
- // Widget-variant bodies get the lower editor cap; full-page bodies the taller one.
32
31
  const isCompact = ref(false)
33
32
 
34
33
  const maxHeight = computed(() => isCompact.value ? COMPACT_EDITOR_MAX_HEIGHT : EDITOR_MAX_HEIGHT)
@@ -1,4 +1,6 @@
1
1
  <script setup lang="ts">
2
+ import { chatShellContextKey } from '../chat-shell-context'
3
+
2
4
  const props = defineProps<{
3
5
  /** Locked inputs render muted and non-interactive. */
4
6
  locked?: boolean
@@ -6,6 +8,8 @@ const props = defineProps<{
6
8
 
7
9
  const rootRef = ref<HTMLElement | null>(null)
8
10
 
11
+ const isWidgetShell = Boolean(inject(chatShellContextKey, null))
12
+
9
13
  const INTERACTIVE = '[data-prompt-editor], button, a, input, select, textarea, [contenteditable], [role="button"], [role="combobox"], [role="menuitem"], [role="option"]'
10
14
 
11
15
  function focusEditor(event: MouseEvent) {
@@ -42,7 +46,8 @@ function focusEditor(event: MouseEvent) {
42
46
  ref="rootRef"
43
47
  data-chat-prompt-input
44
48
  :class="cn(
45
- 'flex flex-col gap-8px pl-16px pb-8px pr-8px rounded-12px bg border-[0.5px] border-strong overflow-hidden relative transition',
49
+ 'flex flex-col bg border-[0.5px] border-strong overflow-hidden relative [box-shadow:0_4px_16px_0_rgba(85,91,98,0.14)]',
50
+ isWidgetShell ? 'rounded-12px gap-12px pl-16px pr-8px pb-8px' : 'rounded-16px gap-10px pl-20px pr-10px pb-10px',
46
51
  locked ? 'bg-muted cursor-not-allowed' : 'cursor-text',
47
52
  )"
48
53
  @mousedown="focusEditor"
@@ -1,3 +1,9 @@
1
+ <script setup lang="ts">
2
+ import { chatShellContextKey } from '../chat-shell-context'
3
+
4
+ provide(chatShellContextKey, 'sidepanel')
5
+ </script>
6
+
1
7
  <template>
2
8
  <aside class="sticky top-0 w-[min(440px,100vw)] flex flex-col h-100dvh overflow-hidden shrink-0 border-l-0.5px border bg-muted p-8px">
3
9
  <div data-widget-frame class="flex-1 min-h-0 flex flex-col border-0.5px rounded-12px overflow-hidden bg">
@@ -26,7 +26,7 @@ const delegatedProps = reactiveOmit(props, 'class')
26
26
  v-if="!props.asChild"
27
27
  name="i-ph-caret-down-bold"
28
28
  size="15px"
29
- color="icon-secondary"
29
+ color="icon-tertiary"
30
30
  class="shrink-0 transition-transform ease-out duration-150 group-data-[state=open]:rotate-180"
31
31
  />
32
32
  </ComboboxTrigger>
@@ -34,7 +34,7 @@ const style = computed(() => {
34
34
  '3xs': 'text-6px p-1 rounded-6px',
35
35
  '2xs': 'text-8px p-4px rounded-4px', // 16px
36
36
  'xs': 'text-12px p-6px rounded-8px', // 26px
37
- 'sm': 'text-16px p-6px rounded-10px', // 32px
37
+ 'sm': 'text-16px p-7px rounded-10px', // 32px
38
38
  'md': 'text-20px p-6.4px rounded-10px', // 44px
39
39
  } as Record<typeof props.size, string>)[props.size]
40
40
 
@@ -33,7 +33,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
33
33
  <SelectContent
34
34
  v-bind="{ ...forwarded, ...$attrs }"
35
35
  :class="cn(
36
- 'SelectContent popover-enter-exit relative z-999 max-h-80 overflow-hidden rounded-[10px] border-[0.5px] border bg',
36
+ 'SelectContent popover-enter-exit relative z-999 max-h-80 overflow-hidden rounded-[10px] border-0.5px border bg',
37
37
  props.class,
38
38
  )"
39
39
  :side-offset="4"
@@ -49,7 +49,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
49
49
  v-else
50
50
  v-bind="{ ...forwarded, ...$attrs }"
51
51
  :class="cn(
52
- 'SelectContent popover-enter-exit relative z-999 max-h-80 overflow-hidden rounded-[16px] border-[0.5px] border bg',
52
+ 'SelectContent popover-enter-exit relative z-999 max-h-80 overflow-hidden rounded-[16px] border-0.5px border bg',
53
53
  props.class,
54
54
  )"
55
55
  :side-offset="4"
@@ -29,7 +29,7 @@ const forwardedProps = useForwardProps(delegatedProps)
29
29
  >
30
30
  <slot />
31
31
  <SelectIcon as-child>
32
- <TelaIcon name="i-ph-caret-down-bold" :size="props.triggerIconSize" color="icon-secondary" class="shrink-0 transition-all ease-out duration-150 group-data-[state=open]:rotate-180" />
32
+ <TelaIcon name="i-ph-caret-down-bold" :size="props.triggerIconSize" color="icon-tertiary" class="shrink-0 transition-all ease-out duration-150 group-data-[state=open]:rotate-180" />
33
33
  </SelectIcon>
34
34
  </SelectTrigger>
35
35
  </template>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/tela-build",
3
- "version": "1.74.3",
3
+ "version": "1.75.1",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "app.config.ts",
@@ -43,6 +43,32 @@ const BASE_COLORS = {
43
43
  900: '#171717',
44
44
  950: '#0A0A0A',
45
45
  },
46
+ taupe: {
47
+ 50: '#F9F9F9',
48
+ 100: '#F5F1EB',
49
+ 200: '#E8E4DE',
50
+ 300: '#D1CABE',
51
+ 400: '#A89D8C',
52
+ 500: '#837969',
53
+ 600: '#695F50',
54
+ 700: '#554C3D',
55
+ 800: '#3D372E',
56
+ 900: '#2E281F',
57
+ 950: '#16130F',
58
+ },
59
+ stone: {
60
+ 50: '#F9F9F9',
61
+ 100: '#F5F5F5',
62
+ 200: '#E5E5E5',
63
+ 300: '#D4D4D4',
64
+ 400: '#A9A49F',
65
+ 500: '#75716D',
66
+ 600: '#55524D',
67
+ 700: '#433F3B',
68
+ 800: '#2A2524',
69
+ 900: '#1E1A16',
70
+ 950: '#0B0B0B',
71
+ },
46
72
  orange: {
47
73
  50: '#FFF7ED',
48
74
  100: '#FFEDD5',
@@ -478,7 +504,7 @@ const PALETTE_STEPS = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950] as
478
504
  type PaletteStep = (typeof PALETTE_STEPS)[number]
479
505
 
480
506
  /** Display order for palette hues; hues missing from this list are appended at the end rather than dropped. */
481
- const PALETTE_HUE_ORDER: readonly string[] = ['gray', 'neutral', 'red', 'orange', 'amber', 'yellow', 'lime', 'green', 'emerald', 'teal', 'cyan', 'sky', 'blue', 'indigo', 'purple', 'pink', 'rose']
507
+ const PALETTE_HUE_ORDER: readonly string[] = ['gray', 'neutral', 'taupe', 'stone', 'red', 'orange', 'amber', 'yellow', 'lime', 'green', 'emerald', 'teal', 'cyan', 'sky', 'blue', 'indigo', 'purple', 'pink', 'rose']
482
508
 
483
509
  function isPaletteScale(scale: unknown): scale is Record<PaletteStep, string> {
484
510
  return typeof scale === 'object' && scale !== null && PALETTE_STEPS.every(step => step in scale)