frappe-ui 1.0.0-beta.75 → 1.0.0-beta.76

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 (36) hide show
  1. package/experimental/CommandPalette/CommandPaletteFooter.vue +1 -1
  2. package/experimental/CommandPalette/CommandPaletteGroup.vue +1 -1
  3. package/experimental/CommandPalette/CommandPaletteInput.vue +1 -1
  4. package/experimental/CommandPalette/CommandPaletteItem.vue +1 -1
  5. package/package.json +1 -1
  6. package/src/components/Badge/Badge.vue +1 -1
  7. package/src/components/Breadcrumbs/Breadcrumbs.vue +6 -4
  8. package/src/components/Button/Button.vue +6 -1
  9. package/src/components/Combobox/Combobox.vue +1 -1
  10. package/src/components/Combobox/ComboboxResults.vue +2 -2
  11. package/src/components/DatePicker/CalendarPanel.vue +2 -2
  12. package/src/components/FormLabel/FormLabel.vue +5 -1
  13. package/src/components/InputLabeling/InputLabel.vue +1 -1
  14. package/src/components/ItemListRow/ItemListRow.vue +1 -1
  15. package/src/components/Menu/MenuItemContent.vue +1 -1
  16. package/src/components/Menu/utils.ts +2 -1
  17. package/src/components/MobileNav/MobileNavItem.vue +2 -2
  18. package/src/components/MultiSelect/MultiSelectResults.vue +1 -1
  19. package/src/components/Select/Select.vue +3 -3
  20. package/src/components/SettingsDialog/SettingsRow.vue +1 -1
  21. package/src/components/Sidebar/SidebarHeader.vue +6 -2
  22. package/src/components/Sidebar/SidebarItem.vue +6 -2
  23. package/src/components/Sidebar/SidebarLabel.vue +1 -1
  24. package/src/components/Sidebar/SidebarSection.vue +1 -1
  25. package/src/components/Switch/Switch.vue +4 -1
  26. package/src/components/TextInput/TextInput.vue +1 -1
  27. package/src/components/TimePicker/TimePicker.vue +1 -1
  28. package/src/components/Tree/TreeItem.vue +3 -1
  29. package/src/components/shared/tabs/Pill.vue +3 -1
  30. package/src/molecules/list/ListGroup.vue +1 -1
  31. package/src/molecules/list/ListHeader.vue +5 -1
  32. package/src/molecules/list/ListHeaderCell.vue +1 -1
  33. package/src/molecules/list/ListHeaderCellSort.vue +2 -2
  34. package/tailwind/plugin.js +7 -0
  35. package/tailwind/tokens/build.js +11 -2
  36. package/tailwind/tokens/typography.js +10 -10
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div
3
3
  data-slot="command-palette-footer"
4
- class="flex shrink-0 items-center gap-4 border-t border-outline-gray-1 px-4.5 py-2 text-sm text-ink-gray-6 dark:border-outline-gray-2"
4
+ class="flex shrink-0 items-center gap-4 border-t border-outline-gray-1 px-4.5 py-2 text-sm leading-tighter text-ink-gray-6 dark:border-outline-gray-2"
5
5
  >
6
6
  <slot v-bind="slotProps" />
7
7
  </div>
@@ -9,7 +9,7 @@
9
9
  v-if="label"
10
10
  as="div"
11
11
  data-slot="command-palette-group-label"
12
- class="mb-2.5 px-4.5 text-base text-ink-gray-5"
12
+ class="mb-2.5 px-4.5 text-base leading-tighter text-ink-gray-5"
13
13
  >
14
14
  {{ label }}
15
15
  </ListboxGroupLabel>
@@ -12,7 +12,7 @@
12
12
  auto-focus
13
13
  autocomplete="off"
14
14
  :placeholder="placeholder"
15
- class="w-full border-none bg-transparent py-3 px-0 text-base text-ink-gray-8 placeholder-ink-gray-4 focus:ring-0"
15
+ class="w-full border-none bg-transparent py-3 px-0 text-base leading-tighter text-ink-gray-8 placeholder-ink-gray-4 focus:ring-0"
16
16
  v-bind="$attrs"
17
17
  />
18
18
  <slot name="suffix" />
@@ -13,7 +13,7 @@
13
13
  data-slot="command-palette-item"
14
14
  :data-state="active ? 'active' : undefined"
15
15
  :data-disabled="disabled ? '' : undefined"
16
- class="mx-2.5 flex min-w-0 cursor-default items-center rounded-4 px-2 py-2 text-base-medium text-ink-gray-8 outline-none data-[state=active]:bg-surface-gray-2 data-[disabled]:cursor-not-allowed data-[disabled]:text-ink-gray-4"
16
+ class="mx-2.5 flex min-w-0 cursor-default items-center rounded-4 px-2 py-2 text-base-medium leading-tighter text-ink-gray-8 outline-none data-[state=active]:bg-surface-gray-2 data-[disabled]:cursor-not-allowed data-[disabled]:text-ink-gray-4"
17
17
  v-bind="$attrs"
18
18
  >
19
19
  <slot name="prefix" v-bind="slotProps" />
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frappe-ui",
3
- "version": "1.0.0-beta.75",
3
+ "version": "1.0.0-beta.76",
4
4
  "description": "A set of components and utilities for rapid UI development",
5
5
  "engines": {
6
6
  "node": ">=20.19.0"
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div
3
- class="inline-flex select-none items-center gap-1 overflow-clip rounded-full whitespace-nowrap"
3
+ class="inline-flex select-none items-center gap-1 overflow-clip rounded-full leading-tighter whitespace-nowrap"
4
4
  :class="classes"
5
5
  >
6
6
  <div
@@ -13,13 +13,15 @@
13
13
  </span>
14
14
  </template>
15
15
 
16
- <div class="flex min-w-0 items-center text-ellipsis whitespace-nowrap">
16
+ <div
17
+ class="flex min-w-0 items-center text-ellipsis whitespace-nowrap leading-tighter"
18
+ >
17
19
  <template v-for="(item, i) in crumbs" :key="item.label">
18
20
  <router-link
19
21
  v-if="item.route"
20
22
  :to="item.route"
21
23
  @click="item.onClick?.($event)"
22
- class="flex items-center rounded-4 px-0.5 py-1 text-lg-medium"
24
+ class="flex items-center rounded-4 px-0.5 py-1 text-lg-medium leading-tighter"
23
25
  :class="[
24
26
  i == crumbs.length - 1
25
27
  ? 'min-w-0 text-ink-gray-9'
@@ -39,7 +41,7 @@
39
41
  v-else-if="item.href"
40
42
  :href="item.href"
41
43
  @click="onLinkClick(item, $event)"
42
- class="flex items-center rounded-4 px-0.5 py-1 text-lg-medium"
44
+ class="flex items-center rounded-4 px-0.5 py-1 text-lg-medium leading-tighter"
43
45
  :class="[
44
46
  i == crumbs.length - 1
45
47
  ? 'min-w-0 text-ink-gray-9'
@@ -58,7 +60,7 @@
58
60
  <button
59
61
  v-else
60
62
  @click="item.onClick?.($event)"
61
- class="flex items-center rounded-4 px-0.5 py-1 text-lg-medium"
63
+ class="flex items-center rounded-4 px-0.5 py-1 text-lg-medium leading-tighter"
62
64
  :class="[
63
65
  i == crumbs.length - 1
64
66
  ? 'min-w-0 text-ink-gray-9'
@@ -321,7 +321,12 @@ export default defineComponent({
321
321
  }
322
322
  return h(
323
323
  'span',
324
- { class: ['truncate', { 'sr-only': isIconButton.value }] },
324
+ {
325
+ class: [
326
+ 'truncate leading-tighter',
327
+ { 'sr-only': isIconButton.value },
328
+ ],
329
+ },
325
330
  slots.default?.() ?? props.label,
326
331
  )
327
332
  }
@@ -587,7 +587,7 @@ defineSlots<ComboboxSlots>()
587
587
 
588
588
  <span
589
589
  :class="[
590
- 'min-w-0 flex-1 truncate text-left font-normal',
590
+ 'min-w-0 flex-1 truncate text-left font-normal leading-tighter',
591
591
  !selectedOption && 'text-ink-gray-4',
592
592
  ]"
593
593
  >
@@ -140,7 +140,7 @@ function handleSelect(item: NormalizedItem, event: Event) {
140
140
  <ComboboxLabel
141
141
  v-if="group.group && !group.hideLabel"
142
142
  data-slot="group-label"
143
- class="flex h-7 items-center px-2 text-sm-medium text-ink-gray-4"
143
+ class="flex h-7 items-center px-2 text-sm-medium leading-tighter text-ink-gray-4"
144
144
  >
145
145
  <component
146
146
  :is="ItemSlotRender"
@@ -245,7 +245,7 @@ function handleSelect(item: NormalizedItem, event: Event) {
245
245
  />
246
246
 
247
247
  <div v-else class="min-w-0">
248
- <div class="truncate">{{ item.label }}</div>
248
+ <div class="truncate leading-tighter">{{ item.label }}</div>
249
249
  <div
250
250
  v-if="item.description"
251
251
  class="truncate text-p-sm text-ink-gray-5"
@@ -158,7 +158,7 @@
158
158
  v-for="y in years"
159
159
  type="button"
160
160
  :key="y"
161
- class="w-full text-ink-gray-8 h-7 shrink-0 rounded-4 py-1 text-sm text-center cursor-pointer transition-colors duration-100"
161
+ class="w-full text-ink-gray-8 h-7 shrink-0 rounded-4 py-1 text-sm leading-tighter text-center cursor-pointer transition-colors duration-100"
162
162
  :class="
163
163
  y === currentYear
164
164
  ? 'bg-surface-gray-2 hover:bg-surface-gray-3'
@@ -181,7 +181,7 @@
181
181
  v-for="(m, i) in months"
182
182
  type="button"
183
183
  :key="m"
184
- class="w-full text-ink-gray-8 shrink-0 h-7 rounded-4 py-1 text-sm text-center cursor-pointer transition-colors duration-100"
184
+ class="w-full text-ink-gray-8 shrink-0 h-7 rounded-4 py-1 text-sm leading-tighter text-center cursor-pointer transition-colors duration-100"
185
185
  :class="
186
186
  i === currentMonth
187
187
  ? 'bg-surface-gray-2 hover:bg-surface-gray-3'
@@ -1,5 +1,9 @@
1
1
  <template>
2
- <label class="block text-sm text-ink-gray-6" :for="id" data-slot="label">
2
+ <label
3
+ class="block text-sm leading-tighter text-ink-gray-6"
4
+ :for="id"
5
+ data-slot="label"
6
+ >
3
7
  {{ label }}
4
8
  <RequiredIndicator :required="required" />
5
9
  </label>
@@ -41,7 +41,7 @@ const labelClasses = computed(() => {
41
41
  // Disabled wins over `color` — a dimmed label is a state, not a choice, so
42
42
  // callers shouldn't have to remember to swap the colour themselves.
43
43
  return [
44
- 'block text-sm',
44
+ 'block text-sm leading-tighter',
45
45
  props.disabled
46
46
  ? 'text-ink-gray-4'
47
47
  : props.color === 'gray-7'
@@ -69,7 +69,7 @@ defineSlots<{
69
69
  <slot name="prefix" />
70
70
  </div>
71
71
 
72
- <div data-slot="item-label" class="min-w-0 flex-1">
72
+ <div data-slot="item-label" class="min-w-0 flex-1 leading-tighter">
73
73
  <slot name="label">
74
74
  <slot />
75
75
  </slot>
@@ -167,7 +167,7 @@ function handleSwitchChange(value: boolean) {
167
167
  :content="itemSlotsLabelContent"
168
168
  />
169
169
  <div v-else class="min-w-0">
170
- <div :class="['truncate', getMenuTextColor(item)]">
170
+ <div :class="['truncate leading-tighter', getMenuTextColor(item)]">
171
171
  {{ item.label }}
172
172
  </div>
173
173
  <div v-if="item.description" class="truncate text-p-sm text-ink-gray-5">
@@ -18,7 +18,8 @@ export const menuClasses = {
18
18
  content:
19
19
  'menu-content z-[100] min-w-40 divide-y divide-outline-elevation-2 rounded-6 bg-surface-elevation-2 shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none',
20
20
  group: 'p-1.5',
21
- groupLabel: 'flex h-7 items-center px-2 text-sm font-medium text-ink-gray-4',
21
+ groupLabel:
22
+ 'flex h-7 items-center px-2 text-sm font-medium leading-tighter text-ink-gray-4',
22
23
  itemIcon: 'size-4 shrink-0',
23
24
  itemIconPlaceholder: 'size-4 shrink-0',
24
25
  chevronIcon: 'size-4 shrink-0',
@@ -26,7 +26,7 @@
26
26
  />
27
27
  </slot>
28
28
  <span
29
- class="text-xs-medium"
29
+ class="text-xs-medium leading-tighter"
30
30
  :class="resolvedActive ? 'text-ink-gray-8' : 'text-ink-gray-5'"
31
31
  >
32
32
  {{ label }}
@@ -51,7 +51,7 @@
51
51
  />
52
52
  </slot>
53
53
  <span
54
- class="text-xs-medium"
54
+ class="text-xs-medium leading-tighter"
55
55
  :class="resolvedActive ? 'text-ink-gray-8' : 'text-ink-gray-5'"
56
56
  >
57
57
  {{ label }}
@@ -109,7 +109,7 @@ function getItemTextValue(item: NormalizedOption) {
109
109
  <ComboboxLabel
110
110
  v-if="group.group && !group.hideLabel"
111
111
  data-slot="group-label"
112
- class="flex h-7 items-center px-2 text-sm-medium text-ink-gray-4"
112
+ class="flex h-7 items-center px-2 text-sm-medium leading-tighter text-ink-gray-4"
113
113
  >
114
114
  <component
115
115
  :is="ItemSlotRender"
@@ -362,7 +362,7 @@ defineExpose(exposed)
362
362
  >
363
363
  <SelectValue
364
364
  :placeholder="placeholder"
365
- class="max-w-full truncate opacity-0"
365
+ class="max-w-full truncate leading-tighter opacity-0"
366
366
  :class="{ 'text-ink-gray-4': showPlaceholderForSelected }"
367
367
  >
368
368
  <template v-if="selectedOption">
@@ -400,7 +400,7 @@ defineExpose(exposed)
400
400
  <div class="grid min-w-0 text-left truncate">
401
401
  <SelectValue
402
402
  :placeholder="placeholder"
403
- class="col-start-1 row-start-1 max-w-full truncate"
403
+ class="col-start-1 row-start-1 max-w-full truncate leading-tighter"
404
404
  :class="{ 'text-ink-gray-4': showPlaceholderForSelected }"
405
405
  >
406
406
  <template v-if="selectedOption">
@@ -540,7 +540,7 @@ defineExpose(exposed)
540
540
  name="item-label"
541
541
  v-bind="getItemSlotProps(internalOption.option)"
542
542
  >
543
- <div class="truncate">
543
+ <div class="truncate leading-tighter">
544
544
  {{ internalOption.option.label }}
545
545
  </div>
546
546
  <div
@@ -4,7 +4,7 @@
4
4
  <component
5
5
  :is="resolvedFor ? 'label' : 'div'"
6
6
  :for="resolvedFor"
7
- class="text-base-medium block text-ink-gray-8"
7
+ class="text-base-medium leading-tighter block text-ink-gray-8"
8
8
  :class="{ 'cursor-pointer': resolvedFor }"
9
9
  >
10
10
  {{ title }}
@@ -51,10 +51,14 @@
51
51
  inherited size and text at another puts the baseline at a
52
52
  different offset in an app whose layout sets a smaller base
53
53
  size, and the header text sat a px lower there. -->
54
- <div class="truncate text-base-medium text-ink-gray-8">
54
+ <div
55
+ class="truncate text-base-medium leading-tighter text-ink-gray-8"
56
+ >
55
57
  {{ props.title }}
56
58
  </div>
57
- <div class="mt-0.5 truncate text-sm text-ink-gray-6">
59
+ <div
60
+ class="mt-0.5 truncate text-sm leading-tighter text-ink-gray-6"
61
+ >
58
62
  {{ props.subtitle }}
59
63
  </div>
60
64
  </div>
@@ -52,7 +52,9 @@
52
52
  >
53
53
  <span ref="labelEl" class="flex min-w-0 items-center">
54
54
  <slot
55
- ><span class="truncate text-sm">{{ label }}</span></slot
55
+ ><span class="truncate text-sm leading-tighter">{{
56
+ label
57
+ }}</span></slot
56
58
  >
57
59
  </span>
58
60
  </span>
@@ -91,7 +93,9 @@
91
93
  >
92
94
  <span ref="labelEl" class="flex min-w-0 items-center">
93
95
  <slot
94
- ><span class="truncate text-sm">{{ label }}</span></slot
96
+ ><span class="truncate text-sm leading-tighter">{{
97
+ label
98
+ }}</span></slot
95
99
  >
96
100
  </span>
97
101
  </span>
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div data-slot="sidebar-label" class="relative flex h-7 items-center pl-2">
3
3
  <h3
4
- class="text-base text-ink-gray-5 transition-all duration-300 ease-in-out"
4
+ class="text-base leading-tighter text-ink-gray-5 transition-all duration-300 ease-in-out"
5
5
  :class="
6
6
  isCollapsed ? 'w-0 overflow-hidden opacity-0' : 'w-auto opacity-100'
7
7
  "
@@ -16,7 +16,7 @@
16
16
  <h3> inside it strips the heading from the accessibility tree.
17
17
  -->
18
18
  <h3
19
- class="h-4 text-sm text-ink-gray-5 transition-all duration-300 ease-in-out"
19
+ class="h-4 text-sm leading-tighter text-ink-gray-5 transition-all duration-300 ease-in-out"
20
20
  :class="
21
21
  isSidebarCollapsed
22
22
  ? 'w-0 overflow-hidden opacity-0'
@@ -189,7 +189,10 @@ const controlPosition = computed(() => props.controlPosition ?? 'end')
189
189
  const switchGroupClasses = computed(() => {
190
190
  const hasLabel = props.label || slots.label
191
191
  const hasDescription = props.description || slots.description
192
- if (!hasLabel && !hasDescription) return undefined
192
+ // A bare switch still gets `flex`: in a plain block the inline-flex control
193
+ // sits on a text line, and that line takes its height from the parent's
194
+ // line-height. In a 28px menu row that made the row 29px.
195
+ if (!hasLabel && !hasDescription) return 'flex'
193
196
 
194
197
  // `flex-row-reverse` / `justify-*` are inline-axis aware, so this flips
195
198
  // correctly under RTL without any physical left/right values.
@@ -252,7 +252,7 @@ const inputClasses = computed(() => {
252
252
  paddingClasses,
253
253
  variantClasses,
254
254
  textColor.value,
255
- 'transition-colors w-full dark:[color-scheme:dark]',
255
+ 'leading-tighter transition-colors w-full dark:[color-scheme:dark]',
256
256
  ]
257
257
  })
258
258
 
@@ -82,7 +82,7 @@
82
82
  :id="optionId(idx)"
83
83
  type="button"
84
84
  role="option"
85
- class="group flex h-7 w-full items-center rounded-4 px-2 text-left tabular-nums"
85
+ class="group flex h-7 w-full items-center rounded-4 px-2 text-left leading-tighter tabular-nums"
86
86
  :class="rowClass(opt, idx)"
87
87
  :aria-selected="canonicalValue === opt.value || undefined"
88
88
  @click="selectOption(opt.value)"
@@ -66,7 +66,9 @@
66
66
  <slot name="item-prefix" v-bind="itemSlotProps" />
67
67
 
68
68
  <slot name="item-label" v-bind="itemSlotProps">
69
- <span class="truncate text-base text-ink-gray-8">{{ label }}</span>
69
+ <span class="truncate text-base leading-tighter text-ink-gray-8">{{
70
+ label
71
+ }}</span>
70
72
  </slot>
71
73
 
72
74
  <span class="ml-auto flex items-center">
@@ -76,7 +76,9 @@ const rootClasses = computed(() => [
76
76
  'inline-flex box-border shrink-0 select-none items-center justify-center whitespace-nowrap outline-none transition-[background-color,color,box-shadow] duration-150 ease-out motion-reduce:transition-none',
77
77
  // Shipped v1 subtle typography (overrides Figma): 13px regular at both
78
78
  // sizes, never medium.
79
- props.variant === 'subtle' ? 'text-sm leading-[16.1px]' : 'text-base',
79
+ props.variant === 'subtle'
80
+ ? 'text-sm leading-[16.1px]'
81
+ : 'text-base leading-tighter',
80
82
  props.active ? 'text-ink-gray-8' : 'text-ink-gray-5',
81
83
  // Figma: md ghost/browser labels use text/base/medium (500, 0.015em);
82
84
  // md underline and all sm labels stay regular. The selected state
@@ -7,7 +7,7 @@
7
7
  <div
8
8
  v-if="label || $slots.label"
9
9
  data-slot="list-group-header"
10
- class="flex h-8 items-center text-sm-medium text-ink-gray-5"
10
+ class="flex h-8 items-center text-sm-medium leading-tighter text-ink-gray-5"
11
11
  :class="sticky && 'sticky top-0 z-10 bg-surface-base'"
12
12
  >
13
13
  <slot name="label">{{ label }}</slot>
@@ -1,5 +1,9 @@
1
1
  <template>
2
- <div data-slot="list-header" role="row" class="h-8 text-sm text-ink-gray-5">
2
+ <div
3
+ data-slot="list-header"
4
+ role="row"
5
+ class="h-8 text-sm leading-tighter text-ink-gray-5"
6
+ >
3
7
  <slot />
4
8
  <Transition
5
9
  enter-active-class="transition-transform duration-75 ease-out"
@@ -7,7 +7,7 @@
7
7
  <span v-if="$slots.prefix" class="shrink-0">
8
8
  <slot name="prefix" />
9
9
  </span>
10
- <span class="truncate"><slot /></span>
10
+ <span class="truncate leading-tighter"><slot /></span>
11
11
  <span v-if="$slots.suffix" class="shrink-0">
12
12
  <slot name="suffix" />
13
13
  </span>
@@ -16,7 +16,7 @@
16
16
  <button
17
17
  ref="button"
18
18
  type="button"
19
- class="group inline-flex h-7 min-w-0 items-center gap-1 rounded-4 text-sm-medium text-ink-gray-5 transition-colors"
19
+ class="group inline-flex h-7 min-w-0 items-center gap-1 rounded-4 text-sm-medium leading-tighter text-ink-gray-5 transition-colors"
20
20
  @click="emit('click', $event)"
21
21
  >
22
22
  <!-- End-aligned: the glyph leads so the label stays flush with the
@@ -35,7 +35,7 @@
35
35
  <span v-if="$slots.prefix" class="shrink-0">
36
36
  <slot name="prefix" :direction="direction ?? null" />
37
37
  </span>
38
- <span class="truncate"><slot /></span>
38
+ <span class="truncate leading-tighter"><slot /></span>
39
39
  <!-- A built-in arrow derived from `direction` by default, so the common
40
40
  case needs no glyph wiring; supply #sort-indicator to override. The cell
41
41
  owns the reveal: an inactive column's glyph shows on hover. -->
@@ -239,6 +239,13 @@ export default plugin(
239
239
  fontSize: buildFontSize(),
240
240
  screens: screenTokens,
241
241
  extend: {
242
+ // `leading-tighter` pins text to 1.15. It is the opt-out for
243
+ // single-line chrome whose height must not move: buttons, badges,
244
+ // labels, table headers. It sits between Tailwind's `leading-none` (1)
245
+ // and `leading-tight` (1.25). Tailwind's `leading-tight` does not change.
246
+ lineHeight: {
247
+ tighter: '1.15',
248
+ },
242
249
  textColor: {
243
250
  ink: semanticColors.ink,
244
251
  },
@@ -11,7 +11,8 @@
11
11
  * ships nothing, it changed six times in four months, and it does not say
12
12
  * what frappe-ui actually uses. The committed record is the output above, and
13
13
  * this file is where frappe-ui deliberately overrules the export —
14
- * RADIUS_OVERRIDE, FONT_WEIGHT_MAP, DROPPED_SIZES, DROPPED_CUSTOM_ELEVATIONS,
14
+ * RADIUS_OVERRIDE, FONT_WEIGHT_MAP, TEXT_LINE_HEIGHT_OVERRIDE, DROPPED_SIZES,
15
+ * DROPPED_CUSTOM_ELEVATIONS,
15
16
  * the hex→oklch conversion and the shadow layer reversal. Those are code-side
16
17
  * opinions, which is why they live in a tested Node script rather than in a
17
18
  * Figma plugin that no CI can run.
@@ -379,6 +380,14 @@ const pctToRatio = (v) => String(round(parseFloat(v) / 100, 4)) // "115%" -> "1.
379
380
  // letter-spacing % of font size === em. `paragraph/5xl` exports as "0.5px" by an
380
381
  // exporter bug (should be "0.5%"); parseFloat keeps the number and we treat it
381
382
  // as a percent regardless of unit, which yields the intended value either way.
383
+ // The UI text styles, sizes 2xs to 4xl, export at 115%, which cramps
384
+ // any text that wraps. frappe-ui ships them at 135%. Single-line chrome opts
385
+ // back to 1.15 with `leading-tighter` (tailwind/plugin.js). The paragraph and
386
+ // display styles export other values and are not touched. Keep the override
387
+ // until the Figma text styles move to 135%.
388
+ const TEXT_LINE_HEIGHT_OVERRIDE = { '115%': '135%' }
389
+ export const textLineHeight = (v) =>
390
+ pctToRatio(TEXT_LINE_HEIGHT_OVERRIDE[v] ?? v)
382
391
  const lsToEm = (v) => `${round(parseFloat(v) / 100, 5)}em` // "2%" -> "0.02em"
383
392
 
384
393
  function round(n, places) {
@@ -448,7 +457,7 @@ function buildTypography() {
448
457
  fontSize[key] = [
449
458
  v.fontSize,
450
459
  {
451
- lineHeight: pctToRatio(v.lineHeight),
460
+ lineHeight: textLineHeight(v.lineHeight),
452
461
  letterSpacing: lsToEm(v.letterSpacing),
453
462
  fontWeight: FONT_WEIGHT_MAP.regular,
454
463
  },
@@ -16,7 +16,7 @@ export default {
16
16
  "2xs": [
17
17
  "11px",
18
18
  {
19
- "lineHeight": "1.15",
19
+ "lineHeight": "1.35",
20
20
  "letterSpacing": "0.01em",
21
21
  "fontWeight": 420
22
22
  }
@@ -24,7 +24,7 @@ export default {
24
24
  "xs": [
25
25
  "12px",
26
26
  {
27
- "lineHeight": "1.15",
27
+ "lineHeight": "1.35",
28
28
  "letterSpacing": "0.02em",
29
29
  "fontWeight": 420
30
30
  }
@@ -32,7 +32,7 @@ export default {
32
32
  "sm": [
33
33
  "13px",
34
34
  {
35
- "lineHeight": "1.15",
35
+ "lineHeight": "1.35",
36
36
  "letterSpacing": "0.02em",
37
37
  "fontWeight": 420
38
38
  }
@@ -40,7 +40,7 @@ export default {
40
40
  "base": [
41
41
  "14px",
42
42
  {
43
- "lineHeight": "1.15",
43
+ "lineHeight": "1.35",
44
44
  "letterSpacing": "0.02em",
45
45
  "fontWeight": 420
46
46
  }
@@ -48,7 +48,7 @@ export default {
48
48
  "md": [
49
49
  "15px",
50
50
  {
51
- "lineHeight": "1.15",
51
+ "lineHeight": "1.35",
52
52
  "letterSpacing": "0.02em",
53
53
  "fontWeight": 420
54
54
  }
@@ -56,7 +56,7 @@ export default {
56
56
  "lg": [
57
57
  "16px",
58
58
  {
59
- "lineHeight": "1.15",
59
+ "lineHeight": "1.35",
60
60
  "letterSpacing": "0.02em",
61
61
  "fontWeight": 420
62
62
  }
@@ -64,7 +64,7 @@ export default {
64
64
  "xl": [
65
65
  "17px",
66
66
  {
67
- "lineHeight": "1.15",
67
+ "lineHeight": "1.35",
68
68
  "letterSpacing": "0.02em",
69
69
  "fontWeight": 420
70
70
  }
@@ -72,7 +72,7 @@ export default {
72
72
  "2xl": [
73
73
  "18px",
74
74
  {
75
- "lineHeight": "1.15",
75
+ "lineHeight": "1.35",
76
76
  "letterSpacing": "0.01em",
77
77
  "fontWeight": 420
78
78
  }
@@ -80,7 +80,7 @@ export default {
80
80
  "3xl": [
81
81
  "20px",
82
82
  {
83
- "lineHeight": "1.15",
83
+ "lineHeight": "1.35",
84
84
  "letterSpacing": "0.005em",
85
85
  "fontWeight": 420
86
86
  }
@@ -88,7 +88,7 @@ export default {
88
88
  "4xl": [
89
89
  "24px",
90
90
  {
91
- "lineHeight": "1.15",
91
+ "lineHeight": "1.35",
92
92
  "letterSpacing": "0.005em",
93
93
  "fontWeight": 420
94
94
  }