@terrafirmagreg-team/ui 0.1.0 → 1.2.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 (157) hide show
  1. package/package.json +14 -14
  2. package/src/components/blocks/button/icon-button.tsx +1 -0
  3. package/src/components/blocks/canvas/chevron-edge.tsx +126 -18
  4. package/src/components/blocks/canvas/flow-canvas.tsx +4 -0
  5. package/src/components/blocks/canvas/flow-toolbar.tsx +1 -0
  6. package/src/components/blocks/canvas/flow-viewport.tsx +1 -0
  7. package/src/components/blocks/fit-box.tsx +26 -0
  8. package/src/components/blocks/git/github-info.tsx +1 -0
  9. package/src/components/blocks/lang/content.tsx +40 -10
  10. package/src/components/blocks/lang/flag.tsx +1 -0
  11. package/src/components/blocks/lang/link.tsx +1 -0
  12. package/src/components/blocks/lang/redirect.tsx +2 -1
  13. package/src/components/blocks/lang/switcher.tsx +1 -0
  14. package/src/components/blocks/lang/untranslated-callout.tsx +4 -3
  15. package/src/components/blocks/lang/untranslated-chip.tsx +53 -0
  16. package/src/components/blocks/link/link-preview.tsx +2 -1
  17. package/src/components/blocks/list/row.tsx +1 -0
  18. package/src/components/blocks/media/glb/model.tsx +1 -0
  19. package/src/components/blocks/media/glb/scene.tsx +13 -12
  20. package/src/components/blocks/media/glb/viewer.tsx +1 -0
  21. package/src/components/blocks/media/image.tsx +3 -2
  22. package/src/components/blocks/media/slideshow.tsx +4 -3
  23. package/src/components/blocks/nav/breadcrumb.tsx +1 -0
  24. package/src/components/blocks/nav/copy-menu.tsx +37 -0
  25. package/src/components/blocks/nav/link.tsx +1 -0
  26. package/src/components/blocks/nav/site-menu.tsx +1 -0
  27. package/src/components/blocks/nav/site-title.tsx +1 -0
  28. package/src/components/blocks/nav/switcher.tsx +1 -0
  29. package/src/components/blocks/page/empty-state.tsx +1 -0
  30. package/src/components/blocks/page/heading.tsx +3 -2
  31. package/src/components/blocks/page/pager.tsx +110 -2
  32. package/src/components/blocks/readability/fields.tsx +250 -0
  33. package/src/components/blocks/readability/menu.tsx +74 -258
  34. package/src/components/blocks/readability/track.tsx +2 -1
  35. package/src/components/blocks/search/command.tsx +1 -0
  36. package/src/components/blocks/search/dialog.tsx +21 -9
  37. package/src/components/blocks/search/provider.tsx +2 -1
  38. package/src/components/blocks/sidebar.tsx +5 -5
  39. package/src/components/blocks/sprite.tsx +231 -0
  40. package/src/components/blocks/text/format-text.tsx +110 -0
  41. package/src/components/blocks/theme/toggle.tsx +4 -0
  42. package/src/components/blocks/tooltip/hint.tsx +1 -0
  43. package/src/components/blocks/tooltip/lazy.tsx +41 -0
  44. package/src/components/blocks/viewer/controls.tsx +11 -6
  45. package/src/components/blocks/viewer/float-bar.tsx +1 -0
  46. package/src/components/layouts/layout.tsx +2 -1
  47. package/src/components/layouts/left-sidebar/content.tsx +1 -0
  48. package/src/components/layouts/left-sidebar/context.tsx +1 -0
  49. package/src/components/layouts/left-sidebar/expand.tsx +2 -1
  50. package/src/components/layouts/left-sidebar/footer.tsx +10 -4
  51. package/src/components/layouts/left-sidebar/index.ts +1 -0
  52. package/src/components/layouts/left-sidebar/items.tsx +2 -1
  53. package/src/components/layouts/left-sidebar/search.tsx +4 -3
  54. package/src/components/layouts/left-sidebar/sidebar.tsx +3 -2
  55. package/src/components/layouts/left-sidebar/tree.tsx +1 -0
  56. package/src/components/layouts/right-sidebar.tsx +1 -0
  57. package/src/components/layouts/tree-context.tsx +1 -0
  58. package/src/components/mdx/accordion.tsx +3 -2
  59. package/src/components/mdx/banner.tsx +15 -12
  60. package/src/components/mdx/callout.tsx +1 -0
  61. package/src/components/mdx/card.tsx +12 -5
  62. package/src/components/mdx/codeblock.tsx +5 -4
  63. package/src/components/mdx/collapsible.tsx +1 -0
  64. package/src/components/mdx/files.tsx +1 -0
  65. package/src/components/mdx/glow-header.tsx +17 -3
  66. package/src/components/mdx/gradient-text.tsx +1 -0
  67. package/src/components/mdx/icon.tsx +35 -0
  68. package/src/components/mdx/image-zoom.tsx +5 -4
  69. package/src/components/mdx/math.tsx +30 -0
  70. package/src/components/mdx/mermaid.tsx +8 -1
  71. package/src/components/mdx/multiblock.tsx +1 -0
  72. package/src/components/mdx/preview.tsx +2 -1
  73. package/src/components/mdx/steps.tsx +1 -0
  74. package/src/components/mdx/table.tsx +1 -0
  75. package/src/components/mdx/tabs.tsx +2 -1
  76. package/src/components/mdx/type-table.tsx +7 -6
  77. package/src/{provider.tsx → components/provider.tsx} +1 -3
  78. package/src/hooks/canvas/use-flow-hover.ts +1 -0
  79. package/src/hooks/canvas/use-flow-viewport-persist.ts +1 -0
  80. package/src/hooks/page/use-fit-pages.ts +1 -0
  81. package/src/hooks/page/use-location-search.ts +8 -2
  82. package/src/hooks/page/use-page-preview.ts +2 -1
  83. package/src/hooks/page/use-paged-list.ts +1 -0
  84. package/src/hooks/readability/{provider.ts → use-readability.ts} +1 -0
  85. package/src/hooks/search/{dialog.ts → use-dialog.ts} +1 -0
  86. package/src/hooks/search/use-highlight-query.ts +8 -1
  87. package/src/hooks/search/{provider.ts → use-provider.ts} +1 -0
  88. package/src/hooks/use-async-value.ts +6 -3
  89. package/src/hooks/use-close-on-scroll.ts +1 -0
  90. package/src/hooks/use-copy-button.ts +1 -0
  91. package/src/hooks/use-cycle-index.ts +1 -0
  92. package/src/hooks/use-document-title.ts +1 -0
  93. package/src/hooks/use-element-size.ts +1 -0
  94. package/src/hooks/use-external-meta.ts +2 -1
  95. package/src/hooks/use-hot-key.ts +1 -0
  96. package/src/hooks/use-hover-follow.ts +1 -0
  97. package/src/hooks/use-in-view.ts +1 -0
  98. package/src/hooks/{lang.ts → use-lang.ts} +1 -0
  99. package/src/hooks/use-last-value.ts +1 -0
  100. package/src/hooks/{layout.ts → use-layout.ts} +1 -0
  101. package/src/hooks/use-marquee.ts +1 -0
  102. package/src/hooks/use-media-query.ts +1 -0
  103. package/src/hooks/use-mobile.ts +1 -0
  104. package/src/hooks/use-modifier-keys.ts +1 -0
  105. package/src/hooks/use-on-change.ts +1 -0
  106. package/src/hooks/use-pane-keys.ts +1 -0
  107. package/src/hooks/{sidebar.ts → use-sidebar.ts} +1 -0
  108. package/src/hooks/use-swipe.ts +1 -0
  109. package/src/hooks/use-tree.ts +1 -3
  110. package/src/hooks/viewer/use-in-float-bar.ts +1 -0
  111. package/src/hooks/viewer/use-viewer-prefs.ts +1 -0
  112. package/src/lib/canvas/edge.ts +78 -49
  113. package/src/lib/canvas/geometry.ts +1 -0
  114. package/src/lib/canvas/state.ts +1 -0
  115. package/src/lib/canvas/viewport.ts +1 -0
  116. package/src/lib/canvas/xyflow.ts +1 -0
  117. package/src/lib/config.ts +25 -0
  118. package/src/lib/mdx/callout.ts +1 -0
  119. package/src/lib/mdx/gradient.ts +1 -0
  120. package/src/lib/mdx/mermaid.ts +1 -0
  121. package/src/lib/media/crop-opaque.ts +1 -0
  122. package/src/lib/media/nearby-indexes.ts +1 -0
  123. package/src/lib/media/trim-transparent.ts +1 -0
  124. package/src/lib/readability/config.ts +1 -0
  125. package/src/lib/readability/spotlight.ts +1 -0
  126. package/src/lib/readability/store.ts +1 -0
  127. package/src/lib/tree.ts +1 -3
  128. package/src/lib/utils/cn.ts +1 -0
  129. package/src/lib/utils/format.ts +3 -3
  130. package/src/lib/utils/highlight-style.ts +10 -0
  131. package/src/lib/utils/history.ts +1 -0
  132. package/src/lib/utils/merge-refs.ts +1 -0
  133. package/src/lib/utils/nav-active.ts +1 -0
  134. package/src/lib/utils/stored-value.ts +1 -0
  135. package/src/lib/utils/urls.ts +2 -1
  136. package/src/locales/en_us.json +108 -117
  137. package/src/locales/pl_pl.json +33 -35
  138. package/src/locales/ru_ru.json +108 -117
  139. package/src/locales/uk_ua.json +33 -35
  140. package/src/locales/zh_cn.json +15 -17
  141. package/src/locales/zh_tw.json +15 -17
  142. package/src/locales.ts +1 -0
  143. package/src/types/canvas.ts +6 -1
  144. package/src/types/glb.ts +1 -0
  145. package/src/types/mdx-directives.ts +106 -0
  146. package/src/types/nav.ts +1 -0
  147. package/src/types/preview.ts +7 -1
  148. package/src/types/readability.ts +1 -0
  149. package/src/types/search.ts +1 -0
  150. package/src/types/tree.ts +1 -0
  151. package/src/types/viewer.ts +1 -0
  152. package/styles/content/base.css +198 -194
  153. package/styles/content/shiki.css +107 -108
  154. package/styles/content/typography.mjs +3637 -3941
  155. package/styles/content.css +5 -7
  156. package/styles.css +430 -413
  157. package/src/lib/utils/async-cache.ts +0 -32
@@ -1,9 +1,8 @@
1
1
  'use client';
2
2
  import { usePathname } from 'next/navigation';
3
- import { useState, type ComponentProps, type ReactNode } from 'react';
3
+ import type { ComponentProps, ReactNode } from 'react';
4
4
  import {
5
5
  AlignStartVertical,
6
- CircleHelp,
7
6
  Columns2,
8
7
  Flashlight,
9
8
  FoldHorizontal,
@@ -16,9 +15,8 @@ import {
16
15
  Settings,
17
16
  UnfoldHorizontal,
18
17
  } from 'lucide-react';
19
- import { cn } from '@terrafirmagreg-team/ui/lib/utils/cn';
20
18
  import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
21
- import { useReadability } from '@terrafirmagreg-team/ui/hooks/readability/provider';
19
+ import { useReadability } from '@terrafirmagreg-team/ui/hooks/readability/use-readability';
22
20
  import { readabilityConfig } from '@terrafirmagreg-team/ui/lib/readability/config';
23
21
  import {
24
22
  Popover,
@@ -26,24 +24,18 @@ import {
26
24
  PopoverTrigger,
27
25
  } from '@terrafirmagreg-team/ui/components/ui/popover';
28
26
  import { IconButton } from '@terrafirmagreg-team/ui/components/blocks/button/icon-button';
29
- import { Hint } from '@terrafirmagreg-team/ui/components/blocks/tooltip/hint';
30
- import {
31
- Field,
32
- FieldContent,
33
- FieldDescription,
34
- FieldLabel,
35
- } from '@terrafirmagreg-team/ui/components/ui/field';
36
- import { Slider } from '@terrafirmagreg-team/ui/components/ui/slider';
37
- import {
38
- ToggleGroup,
39
- ToggleGroupItem,
40
- } from '@terrafirmagreg-team/ui/components/ui/toggle-group';
41
27
  import {
42
28
  readabilityLayouts,
43
29
  spotlightStyles,
44
30
  type ReadabilityLayout,
45
31
  type SpotlightStyle,
46
32
  } from '@terrafirmagreg-team/ui/types/readability';
33
+ import {
34
+ ReadabilityField,
35
+ ReadabilityHelp,
36
+ Segment,
37
+ WidthField,
38
+ } from './fields';
47
39
 
48
40
  const layoutIcons: Record<ReadabilityLayout, ReactNode> = {
49
41
  full: <Maximize2 />,
@@ -59,223 +51,6 @@ const styleIcons: Record<SpotlightStyle, ReactNode> = {
59
51
 
60
52
  const switches = ['on', 'off'] as const;
61
53
 
62
- function ReadabilityHelp({
63
- title,
64
- description,
65
- items,
66
- }: {
67
- title: string;
68
- description?: string;
69
- items?: { icon?: ReactNode; label: string; help: string }[];
70
- }) {
71
- return (
72
- <div className="space-y-2">
73
- <div className="space-y-1">
74
- <p className="font-medium">{title}</p>
75
- {description && <FieldDescription className="text-xs">{description}</FieldDescription>}
76
- </div>
77
- {items?.map((item) => (
78
- <div key={item.label} className="space-y-1 rounded-lg bg-muted/60 p-2.5">
79
- <p className="flex items-center gap-1.5 text-xs font-medium [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:shrink-0">
80
- {item.icon}
81
- {item.label}
82
- </p>
83
- <FieldDescription className="text-xs">{item.help}</FieldDescription>
84
- </div>
85
- ))}
86
- </div>
87
- );
88
- }
89
-
90
- function ReadabilityField({
91
- label,
92
- icon,
93
- help,
94
- children,
95
- }: {
96
- label: ReactNode;
97
- icon?: ReactNode;
98
- help?: ReactNode;
99
- children: ReactNode;
100
- }) {
101
- const [helpOpen, setHelpOpen] = useState(false);
102
- return (
103
- <Field className="gap-1.5">
104
- <div className="flex items-center gap-1 [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:shrink-0">
105
- {icon}
106
- <FieldLabel className="flex-1 text-xs text-muted-foreground">{label}</FieldLabel>
107
- {help && (
108
- <Popover onOpenChange={setHelpOpen}>
109
- <PopoverTrigger
110
- render={
111
- <IconButton
112
- variant="ghost"
113
- size="icon-xs"
114
- tooltip={false}
115
- label={typeof label === 'string' ? label : 'Help'}
116
- >
117
- <CircleHelp />
118
- </IconButton>
119
- }
120
- />
121
- <PopoverContent className="w-56">{help}</PopoverContent>
122
- </Popover>
123
- )}
124
- </div>
125
- <FieldContent
126
- className={cn(
127
- 'rounded-lg outline-2 outline-offset-4 outline-dashed outline-transparent transition-[outline-color] duration-200',
128
- helpOpen && 'outline-primary',
129
- )}
130
- >
131
- {children}
132
- </FieldContent>
133
- </Field>
134
- );
135
- }
136
-
137
- function ReadabilitySlider({
138
- label,
139
- icon,
140
- help,
141
- value,
142
- onChange,
143
- min = 0,
144
- max = 100,
145
- step = 1,
146
- format = String,
147
- disabled,
148
- live,
149
- }: {
150
- label: ReactNode;
151
- icon?: ReactNode;
152
- help?: ReactNode;
153
- value: number;
154
- onChange: (value: number) => void;
155
- min?: number;
156
- max?: number;
157
- step?: number;
158
- format?: (value: number) => string;
159
- disabled?: boolean;
160
- live?: boolean;
161
- }) {
162
- const [draft, setDraft] = useState<{ key: number; value: number }>({ key: value, value });
163
- const shown = draft.key === value ? draft.value : value;
164
- return (
165
- <ReadabilityField label={label} icon={icon} help={help}>
166
- <div className="flex items-center gap-3 rounded-lg bg-muted/70 px-3 py-2.5">
167
- <Slider
168
- min={min}
169
- max={max}
170
- step={step}
171
- value={shown}
172
- disabled={disabled}
173
- className="flex-1 [&_[data-slot=slider-track]]:bg-muted-foreground/25"
174
- onValueChange={(next) => {
175
- if (live) onChange(next as number);
176
- else setDraft({ key: value, value: next as number });
177
- }}
178
- onValueCommitted={() => {
179
- if (!live && shown !== value) onChange(shown);
180
- }}
181
- />
182
- <span className="w-9 shrink-0 text-end text-xs tabular-nums text-muted-foreground">
183
- {format(shown)}
184
- </span>
185
- </div>
186
- </ReadabilityField>
187
- );
188
- }
189
-
190
- function Segment<T extends string>({
191
- options,
192
- value,
193
- onChange,
194
- disabled,
195
- itemContent,
196
- itemLabel,
197
- itemHelp,
198
- itemClassName,
199
- }: {
200
- options: readonly T[];
201
- value: T;
202
- onChange: (value: T) => void;
203
- disabled?: boolean;
204
- itemContent: (id: T) => ReactNode;
205
- itemLabel: (id: T) => string;
206
- itemHelp: (id: T) => string;
207
- itemClassName?: string;
208
- }) {
209
- return (
210
- <ToggleGroup
211
- value={[value]}
212
- onValueChange={(values) => {
213
- const next = values.at(-1) as T | undefined;
214
- if (next && next !== value) onChange(next);
215
- }}
216
- disabled={disabled}
217
- size="sm"
218
- spacing={1}
219
- className="w-full rounded-lg bg-muted/70 p-1"
220
- >
221
- {options.map((id) => (
222
- <Hint key={id} label={itemHelp(id)}>
223
- <ToggleGroupItem
224
- value={id}
225
- aria-label={itemLabel(id)}
226
- className={cn(
227
- 'flex-1 rounded-md text-muted-foreground hover:text-foreground aria-pressed:bg-background aria-pressed:text-foreground aria-pressed:shadow-sm',
228
- itemClassName,
229
- )}
230
- >
231
- {itemContent(id)}
232
- </ToggleGroupItem>
233
- </Hint>
234
- ))}
235
- </ToggleGroup>
236
- );
237
- }
238
-
239
- function WidthField({
240
- field,
241
- icon,
242
- value,
243
- onChange,
244
- }: {
245
- field: 'page_width' | 'content_width';
246
- icon: ReactNode;
247
- value: number;
248
- onChange: (value: number) => void;
249
- }) {
250
- const t = useTranslations();
251
- return (
252
- <ReadabilitySlider
253
- live
254
- label={t(`ui.readability.${field}`)}
255
- icon={icon}
256
- help={
257
- <ReadabilityHelp
258
- title={t(`ui.readability.${field}`)}
259
- description={t(`ui.readability.${field}.help`)}
260
- items={[
261
- {
262
- icon,
263
- label: t(`ui.readability.${field}.slider`),
264
- help: t(`ui.readability.${field}.slider.help`),
265
- },
266
- ]}
267
- />
268
- }
269
- value={value}
270
- onChange={onChange}
271
- min={60}
272
- max={100}
273
- step={1}
274
- format={(v) => `${v}%`}
275
- />
276
- );
277
- }
278
-
279
54
  export function ReadabilityMenu({
280
55
  anchor,
281
56
  }: {
@@ -297,6 +72,46 @@ export function ReadabilityMenu({
297
72
  smallScreen,
298
73
  } = useReadability();
299
74
 
75
+ const layoutT: Record<ReadabilityLayout, { label: string; help: string }> = {
76
+ full: {
77
+ label: t('Expand all', { note: 'ui.readability.layout.full' }),
78
+ help: t('The article takes the full panel width; the sidebar stays at the screen edge', { note: 'ui.readability.layout.full.help' }),
79
+ },
80
+ sidebar: {
81
+ label: t('Adjustable page width', { note: 'ui.readability.layout.sidebar' }),
82
+ help: t('The slider narrows the whole page block: the sidebar moves with it', { note: 'ui.readability.layout.sidebar.help' }),
83
+ },
84
+ both: {
85
+ label: t('Adjustable page and content', { note: 'ui.readability.layout.both' }),
86
+ help: t('Both sliders apply: page block width and the text column inside', { note: 'ui.readability.layout.both.help' }),
87
+ },
88
+ original: {
89
+ label: t('Original width', { note: 'ui.readability.layout.original' }),
90
+ help: t('Default centered page width', { note: 'ui.readability.layout.original.help' }),
91
+ },
92
+ };
93
+ const spotlightT: Record<
94
+ SpotlightStyle | 'on' | 'off',
95
+ { label: string; help: string }
96
+ > = {
97
+ on: {
98
+ label: t('On', { note: 'ui.readability.spotlight.on' }),
99
+ help: t('Turn on Spotlight', { note: 'ui.readability.spotlight.on.help' }),
100
+ },
101
+ off: {
102
+ label: t('Off', { note: 'ui.readability.spotlight.off' }),
103
+ help: t('Turn off Spotlight', { note: 'ui.readability.spotlight.off.help' }),
104
+ },
105
+ under: {
106
+ label: t('Under', { note: 'ui.readability.spotlight.under' }),
107
+ help: t('A solid background under the hovered block shows where the cursor is', { note: 'ui.readability.spotlight.under.help' }),
108
+ },
109
+ aside: {
110
+ label: t('Aside', { note: 'ui.readability.spotlight.aside' }),
111
+ help: t('A solid line beside the hovered element shows where the cursor is', { note: 'ui.readability.spotlight.aside.help' }),
112
+ },
113
+ };
114
+
300
115
  const layoutDisabled = smallScreen;
301
116
  const pathname = usePathname();
302
117
  const spotlightDisabled =
@@ -310,7 +125,7 @@ export function ReadabilityMenu({
310
125
  variant="ghost"
311
126
  size="icon-sm"
312
127
  tooltip={false}
313
- label={t('ui.readability.menu')}
128
+ label={t('Readability', { note: 'ui.readability.menu' })}
314
129
  className="text-muted-foreground! data-[popup-open]:bg-accent! data-[popup-open]:text-accent-foreground!"
315
130
  >
316
131
  <Settings />
@@ -325,20 +140,20 @@ export function ReadabilityMenu({
325
140
  className="w-64 space-y-3 p-3"
326
141
  >
327
142
  <ReadabilityField
328
- label={t('ui.readability.layout')}
143
+ label={t('Layout', { note: 'ui.readability.layout' })}
329
144
  icon={<Proportions />}
330
145
  help={
331
146
  <ReadabilityHelp
332
- title={t('ui.readability.layout')}
147
+ title={t('Layout', { note: 'ui.readability.layout' })}
333
148
  description={
334
149
  layoutDisabled
335
- ? t('ui.readability.layout.unavailable')
336
- : t('ui.readability.layout.help')
150
+ ? t('Layout switch is not available on small screens', { note: 'ui.readability.layout.unavailable' })
151
+ : t('Adjust the site layout to fit different reading needs and screens', { note: 'ui.readability.layout.help' })
337
152
  }
338
153
  items={readabilityLayouts.map((id) => ({
339
154
  icon: layoutIcons[id],
340
- label: t(`ui.readability.layout.${id}`),
341
- help: t(`ui.readability.layout.${id}.help`),
155
+ label: layoutT[id].label,
156
+ help: layoutT[id].help,
342
157
  }))}
343
158
  />
344
159
  }
@@ -349,8 +164,8 @@ export function ReadabilityMenu({
349
164
  onChange={setLayout}
350
165
  disabled={layoutDisabled}
351
166
  itemContent={(id) => layoutIcons[id]}
352
- itemLabel={(id) => t(`ui.readability.layout.${id}`)}
353
- itemHelp={(id) => t(`ui.readability.layout.${id}.help`)}
167
+ itemLabel={(id) => layoutT[id].label}
168
+ itemHelp={(id) => layoutT[id].help}
354
169
  />
355
170
  </ReadabilityField>
356
171
  {(layout === 'sidebar' || layout === 'both') && !smallScreen && (
@@ -370,19 +185,19 @@ export function ReadabilityMenu({
370
185
  />
371
186
  )}
372
187
  <ReadabilityField
373
- label={t('ui.readability.spotlight')}
188
+ label={t('Spotlight', { note: 'ui.readability.spotlight' })}
374
189
  icon={<Flashlight />}
375
190
  help={
376
191
  <ReadabilityHelp
377
- title={t('ui.readability.spotlight')}
192
+ title={t('Spotlight', { note: 'ui.readability.spotlight' })}
378
193
  description={
379
194
  spotlightDisabled
380
- ? t('ui.readability.spotlight.unavailable')
381
- : t('ui.readability.spotlight.help')
195
+ ? t('Spotlight is not available on touch screens', { note: 'ui.readability.spotlight.unavailable' })
196
+ : t('Highlight the line or block under the cursor to help readers stay focused', { note: 'ui.readability.spotlight.help' })
382
197
  }
383
198
  items={switches.map((id) => ({
384
- label: t(`ui.readability.spotlight.${id}`),
385
- help: t(`ui.readability.spotlight.${id}.help`),
199
+ label: spotlightT[id].label,
200
+ help: spotlightT[id].help,
386
201
  }))}
387
202
  />
388
203
  }
@@ -392,24 +207,24 @@ export function ReadabilityMenu({
392
207
  value={spotlight ? 'on' : 'off'}
393
208
  onChange={(id) => setSpotlight(id === 'on')}
394
209
  disabled={spotlightDisabled}
395
- itemContent={(id) => t(`ui.readability.spotlight.${id}`)}
396
- itemLabel={(id) => t(`ui.readability.spotlight.${id}`)}
397
- itemHelp={(id) => t(`ui.readability.spotlight.${id}.help`)}
210
+ itemContent={(id) => spotlightT[id].label}
211
+ itemLabel={(id) => spotlightT[id].label}
212
+ itemHelp={(id) => spotlightT[id].help}
398
213
  itemClassName="text-xs"
399
214
  />
400
215
  </ReadabilityField>
401
216
  {spotlight && !spotlightDisabled && (
402
217
  <ReadabilityField
403
- label={t('ui.readability.spotlight.styles')}
218
+ label={t('Style', { note: 'ui.readability.spotlight.styles' })}
404
219
  icon={<Palette />}
405
220
  help={
406
221
  <ReadabilityHelp
407
- title={t('ui.readability.spotlight.styles')}
408
- description={t('ui.readability.spotlight.styles.help')}
222
+ title={t('Style', { note: 'ui.readability.spotlight.styles' })}
223
+ description={t('Adjust how the spotlight highlight is drawn', { note: 'ui.readability.spotlight.styles.help' })}
409
224
  items={spotlightStyles.map((id) => ({
410
225
  icon: styleIcons[id],
411
- label: t(`ui.readability.spotlight.${id}`),
412
- help: t(`ui.readability.spotlight.${id}.help`),
226
+ label: spotlightT[id].label,
227
+ help: spotlightT[id].help,
413
228
  }))}
414
229
  />
415
230
  }
@@ -420,8 +235,8 @@ export function ReadabilityMenu({
420
235
  onChange={setSpotlightStyle}
421
236
  disabled={spotlightDisabled || !spotlight}
422
237
  itemContent={(id) => styleIcons[id]}
423
- itemLabel={(id) => t(`ui.readability.spotlight.${id}`)}
424
- itemHelp={(id) => t(`ui.readability.spotlight.${id}.help`)}
238
+ itemLabel={(id) => spotlightT[id].label}
239
+ itemHelp={(id) => spotlightT[id].help}
425
240
  />
426
241
  </ReadabilityField>
427
242
  )}
@@ -429,3 +244,4 @@ export function ReadabilityMenu({
429
244
  </Popover>
430
245
  );
431
246
  }
247
+
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { useEffect, useRef } from 'react';
3
3
  import { usePathname } from 'next/navigation';
4
- import { useReadability } from '@terrafirmagreg-team/ui/hooks/readability/provider';
4
+ import { useReadability } from '@terrafirmagreg-team/ui/hooks/readability/use-readability';
5
5
  import { readabilityConfig } from '@terrafirmagreg-team/ui/lib/readability/config';
6
6
  import {
7
7
  contentRoot,
@@ -162,3 +162,4 @@ export function SpotlightTrack() {
162
162
  />
163
163
  );
164
164
  }
165
+
@@ -77,3 +77,4 @@ export function CommandSearch<T = unknown>({
77
77
  </CommandDialog>
78
78
  );
79
79
  }
80
+
@@ -24,7 +24,7 @@ import {
24
24
  SearchTagsListContext,
25
25
  useSearch,
26
26
  useSearchTagsList,
27
- } from '@terrafirmagreg-team/ui/hooks/search/dialog';
27
+ } from '@terrafirmagreg-team/ui/hooks/search/use-dialog';
28
28
  import type {
29
29
  ReactSortedResult,
30
30
  SearchItemType,
@@ -239,12 +239,12 @@ export function SearchDialogInput(props: ComponentProps<'input'>) {
239
239
  role="combobox"
240
240
  aria-expanded={active !== null}
241
241
  aria-activedescendant={active !== null ? `search-option-${active}` : undefined}
242
- aria-label={t('ui.search_dialog')}
242
+ aria-label={t('Search', { note: 'ui.search_dialog' })}
243
243
  aria-autocomplete="list"
244
244
  aria-controls="search-list"
245
245
  value={search}
246
246
  onChange={(e) => onSearchChange(e.target.value)}
247
- placeholder={t('ui.search_dialog')}
247
+ placeholder={t('Search', { note: 'ui.search_dialog' })}
248
248
  {...props}
249
249
  className={cn(
250
250
  'w-0 flex-1 bg-transparent text-lg placeholder:text-muted-foreground focus-visible:outline-none',
@@ -265,7 +265,7 @@ export function SearchDialogClose({
265
265
  return (
266
266
  <button
267
267
  type="button"
268
- aria-label={t('ui.search_close')}
268
+ aria-label={t('Close Search', { note: 'ui.search_close' })}
269
269
  onClick={() => onOpenChange(false)}
270
270
  className={cn(
271
271
  buttonVariants({
@@ -337,7 +337,7 @@ export function SearchDialogContent({
337
337
  }
338
338
  {...props}
339
339
  >
340
- <Dialog.Title className="hidden">{t('ui.search_dialog')}</Dialog.Title>
340
+ <Dialog.Title className="hidden">{t('Search', { note: 'ui.search_dialog' })}</Dialog.Title>
341
341
  {children}
342
342
  </Dialog.Popup>
343
343
  </Dialog.Portal>
@@ -427,7 +427,7 @@ export function SearchDialogList({
427
427
  ref={ref}
428
428
  data-empty={items === null && !error}
429
429
  className={cn(
430
- 'overflow-hidden h-(--animated-height) transition-[height] [&_::highlight(search-hit)]:text-primary [&_::highlight(search-hit)]:underline',
430
+ 'overflow-hidden h-(--animated-height) transition-[height]',
431
431
  props.className,
432
432
  )}
433
433
  >
@@ -455,8 +455,8 @@ export function SearchDialogList({
455
455
  id="search-list"
456
456
 
457
457
  role={items?.length ? 'listbox' : undefined}
458
- aria-label={items?.length ? t('ui.search_dialog') : undefined}
459
- className={cn('w-full flex flex-col overflow-y-auto max-h-[460px] p-1', !items && 'hidden')}
458
+ aria-label={items?.length ? t('Search', { note: 'ui.search_dialog' }) : undefined}
459
+ className={cn('w-full flex flex-col overflow-y-auto max-h-[460px] p-1 scrollbar-thin', !items && 'hidden')}
460
460
  >
461
461
  {items?.length === 0 && Empty()}
462
462
 
@@ -530,12 +530,14 @@ export function SearchDialogListItem({
530
530
  className,
531
531
  children,
532
532
  renderMarkdown,
533
+ badge,
533
534
  ...props
534
535
  }: ComponentProps<'div'> & {
535
536
 
536
537
  table?: SearchResultTable;
537
538
 
538
539
  renderMarkdown?: (v: string) => ReactNode;
540
+ badge?: ReactNode;
539
541
  item: SearchItemType;
540
542
  }) {
541
543
  const { query, getActive, setActive, subscribeActive } = useSearch();
@@ -569,7 +571,17 @@ export function SearchDialogListItem({
569
571
  } else {
570
572
  children ??= (
571
573
  <>
572
- <div className="inline-flex items-center text-muted-foreground text-xs empty:hidden">
574
+ {badge != null && (
575
+ <span className="absolute end-2 top-1.5 rounded-md border border-border bg-background px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-foreground/80">
576
+ {badge}
577
+ </span>
578
+ )}
579
+ <div
580
+ className={cn(
581
+ 'inline-flex items-center text-muted-foreground text-xs empty:hidden',
582
+ badge != null && 'pe-14',
583
+ )}
584
+ >
573
585
  {item.breadcrumbs?.map((item, i) => (
574
586
  <Fragment key={i}>
575
587
  {i > 0 && <ChevronRight className="size-4 rtl:rotate-180" />}
@@ -12,7 +12,7 @@ import {
12
12
  dialogHandle,
13
13
  SearchContext,
14
14
  useSearchContext,
15
- } from '@terrafirmagreg-team/ui/hooks/search/provider';
15
+ } from '@terrafirmagreg-team/ui/hooks/search/use-provider';
16
16
  import type { SharedProps } from '@terrafirmagreg-team/ui/types/search';
17
17
  export type { SharedProps } from '@terrafirmagreg-team/ui/types/search';
18
18
 
@@ -115,3 +115,4 @@ export function SearchOnly({ children }: { children: ReactNode }) {
115
115
 
116
116
  if (search.enabled) return children;
117
117
  }
118
+
@@ -22,7 +22,7 @@ import {
22
22
  useAutoScroll,
23
23
  useFolderDepth,
24
24
  useSidebar,
25
- } from '@terrafirmagreg-team/ui/hooks/sidebar';
25
+ } from '@terrafirmagreg-team/ui/hooks/use-sidebar';
26
26
  import { Link } from '@terrafirmagreg-team/ui/components/blocks/nav/link';
27
27
  import {
28
28
  Collapsible,
@@ -31,7 +31,7 @@ import {
31
31
  } from '@terrafirmagreg-team/ui/components/ui/collapsible';
32
32
  import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
33
33
 
34
- export type { SidebarContextValue } from '@terrafirmagreg-team/ui/hooks/sidebar';
34
+ export type { SidebarContextValue } from '@terrafirmagreg-team/ui/hooks/use-sidebar';
35
35
 
36
36
  export function SidebarProvider({
37
37
  defaultOpenLevel = 0,
@@ -363,8 +363,8 @@ export function SidebarTrigger({
363
363
  type="button"
364
364
  aria-label={
365
365
  open
366
- ? t('ui.sidebar_close', )
367
- : t('ui.sidebar_open', )
366
+ ? t('Close Sidebar', { note: 'ui.sidebar_close' })
367
+ : t('Open Sidebar', { note: 'ui.sidebar_open' })
368
368
  }
369
369
  aria-expanded={open}
370
370
  aria-controls="sidebar-mobile"
@@ -383,7 +383,7 @@ export function SidebarCollapseTrigger(props: ComponentProps<'button'>) {
383
383
  return (
384
384
  <button
385
385
  type="button"
386
- aria-label={t('ui.sidebar_collapse', )}
386
+ aria-label={t('Collapse Sidebar', { note: 'ui.sidebar_collapse' })}
387
387
  aria-controls="sidebar"
388
388
  aria-expanded={!collapsed}
389
389
  data-collapsed={collapsed}