lightnet 3.10.3 → 3.10.5

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 (49) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/__e2e__/admin.spec.ts +356 -100
  3. package/__e2e__/fixtures/basics/node_modules/.bin/astro +2 -2
  4. package/__e2e__/fixtures/basics/package.json +5 -5
  5. package/package.json +9 -9
  6. package/src/admin/components/form/DynamicArray.tsx +74 -0
  7. package/src/admin/components/form/Input.tsx +36 -0
  8. package/src/admin/components/form/Select.tsx +22 -20
  9. package/src/admin/components/form/SubmitButton.tsx +20 -18
  10. package/src/admin/components/form/atoms/ErrorMessage.tsx +13 -0
  11. package/src/admin/components/form/atoms/Hint.tsx +3 -3
  12. package/src/admin/components/form/atoms/Label.tsx +17 -6
  13. package/src/admin/components/form/atoms/Legend.tsx +20 -0
  14. package/src/admin/components/form/hooks/use-field-error.tsx +13 -0
  15. package/src/admin/i18n/translations/en.yml +18 -7
  16. package/src/admin/pages/media/EditForm.tsx +52 -68
  17. package/src/admin/pages/media/EditRoute.astro +35 -11
  18. package/src/admin/pages/media/fields/Authors.tsx +43 -0
  19. package/src/admin/pages/media/fields/Categories.tsx +64 -0
  20. package/src/admin/pages/media/fields/Collections.tsx +103 -0
  21. package/src/admin/pages/media/media-item-store.ts +14 -7
  22. package/src/admin/types/media-item.ts +38 -2
  23. package/src/components/CategoriesSection.astro +2 -2
  24. package/src/components/HeroSection.astro +1 -1
  25. package/src/components/HighlightSection.astro +1 -1
  26. package/src/components/MediaGallerySection.astro +3 -3
  27. package/src/components/MediaList.astro +2 -2
  28. package/src/components/SearchInput.astro +1 -1
  29. package/src/content/get-categories.ts +18 -3
  30. package/src/i18n/react/i18n-context.ts +14 -12
  31. package/src/i18n/resolve-language.ts +1 -1
  32. package/src/layouts/MarkdownPage.astro +1 -1
  33. package/src/layouts/Page.astro +3 -2
  34. package/src/layouts/components/LanguagePicker.astro +1 -1
  35. package/src/layouts/components/Menu.astro +1 -1
  36. package/src/layouts/components/PageNavigation.astro +1 -1
  37. package/src/pages/details-page/components/main-details/OpenButton.astro +1 -1
  38. package/src/pages/details-page/components/more-details/Languages.astro +2 -2
  39. package/src/pages/search-page/components/LoadingSkeleton.tsx +1 -1
  40. package/src/pages/search-page/components/SearchFilter.astro +7 -7
  41. package/src/pages/search-page/components/SearchFilter.tsx +5 -5
  42. package/src/pages/search-page/components/SearchList.astro +4 -4
  43. package/src/pages/search-page/components/SearchListItem.tsx +5 -5
  44. package/src/pages/search-page/components/Select.tsx +4 -4
  45. package/src/pages/search-page/hooks/use-search.ts +4 -4
  46. package/src/admin/components/form/TextInput.tsx +0 -34
  47. package/src/admin/components/form/atoms/FieldErrors.tsx +0 -22
  48. package/src/admin/components/form/form-context.ts +0 -4
  49. package/src/admin/components/form/index.ts +0 -18
@@ -0,0 +1,103 @@
1
+ import { type Control } from "react-hook-form"
2
+
3
+ import ErrorMessage from "../../../components/form/atoms/ErrorMessage"
4
+ import Label from "../../../components/form/atoms/Label"
5
+ import DynamicArray from "../../../components/form/DynamicArray"
6
+ import { useFieldError } from "../../../components/form/hooks/use-field-error"
7
+ import type { MediaItem } from "../../../types/media-item"
8
+
9
+ export default function Collections({
10
+ control,
11
+ collections,
12
+ }: {
13
+ control: Control<MediaItem>
14
+ collections: { id: string; labelText: string }[]
15
+ }) {
16
+ return (
17
+ <DynamicArray
18
+ control={control}
19
+ name="collections"
20
+ label="ln.admin.collections"
21
+ renderElement={(index) => (
22
+ <div className="flex w-full flex-col py-2">
23
+ <CollectionSelect
24
+ collections={collections}
25
+ control={control}
26
+ index={index}
27
+ />
28
+ <CollectionIndex control={control} index={index} />
29
+ </div>
30
+ )}
31
+ addButton={{
32
+ label: "ln.admin.add-collection",
33
+ onClick: (append, index) =>
34
+ append(
35
+ { collection: "" },
36
+ { focusName: `collections.${index}.collection` },
37
+ ),
38
+ }}
39
+ />
40
+ )
41
+ }
42
+
43
+ function CollectionSelect({
44
+ control,
45
+ collections,
46
+ index,
47
+ }: {
48
+ control: Control<MediaItem>
49
+ collections: { id: string; labelText: string }[]
50
+ index: number
51
+ }) {
52
+ const name = `collections.${index}.collection` as const
53
+ const errorMessage = useFieldError({ name, control })
54
+ return (
55
+ <>
56
+ <Label for={name} label="ln.admin.name" size="xs" />
57
+ <select
58
+ {...control.register(name)}
59
+ id={name}
60
+ aria-invalid={!!errorMessage}
61
+ className={`dy-select dy-select-bordered text-base shadow-sm ${errorMessage ? "dy-select-error" : ""}`}
62
+ >
63
+ {collections.map(({ id, labelText }) => (
64
+ <option key={id} value={id}>
65
+ {labelText}
66
+ </option>
67
+ ))}
68
+ </select>
69
+ <ErrorMessage message={errorMessage} />
70
+ </>
71
+ )
72
+ }
73
+
74
+ function CollectionIndex({
75
+ control,
76
+ index,
77
+ }: {
78
+ control: Control<MediaItem>
79
+ index: number
80
+ }) {
81
+ const name = `collections.${index}.index` as const
82
+ const errorMessage = useFieldError({ name, control })
83
+ return (
84
+ <>
85
+ <Label
86
+ for={name}
87
+ label="ln.admin.position-in-collection"
88
+ size="xs"
89
+ className="mt-3"
90
+ />
91
+ <input
92
+ className={`dy-input dy-input-bordered shadow-inner ${errorMessage ? "dy-input-error" : ""}`}
93
+ aria-invalid={!!errorMessage}
94
+ type="number"
95
+ step={1}
96
+ {...control.register(name, {
97
+ setValueAs: (value) => (value === "" ? undefined : Number(value)),
98
+ })}
99
+ />
100
+ <ErrorMessage message={errorMessage} />
101
+ </>
102
+ )
103
+ }
@@ -1,11 +1,18 @@
1
- import { type MediaItem, mediaItemSchema } from "../../types/media-item"
1
+ import { type MediaItem } from "../../types/media-item"
2
2
  import { writeJson } from "./file-system"
3
3
 
4
- export const loadMediaItem = (id: string) =>
5
- fetch(`/api/media/${id}.json`)
6
- .then((response) => response.json())
7
- .then((json) => mediaItemSchema.parse(json.content))
8
-
9
4
  export const updateMediaItem = async (id: string, item: MediaItem) => {
10
- return writeJson(`/src/content/media/${id}.json`, item)
5
+ return writeJson(`/src/content/media/${id}.json`, mapToContentSchema(item))
6
+ }
7
+
8
+ const mapToContentSchema = (item: MediaItem) => {
9
+ return {
10
+ ...item,
11
+ authors: flatten(item.authors),
12
+ categories: flatten(item.categories),
13
+ }
14
+ }
15
+
16
+ const flatten = <TValue>(valueArray: { value: TValue }[]) => {
17
+ return valueArray.map(({ value }) => value)
11
18
  }
@@ -1,15 +1,51 @@
1
- import { z } from "astro/zod"
1
+ import { type RefinementCtx, z } from "astro/zod"
2
2
 
3
3
  const NON_EMPTY_STRING = "ln.admin.errors.non-empty-string"
4
4
  const INVALID_DATE = "ln.admin.errors.invalid-date"
5
5
  const REQUIRED = "ln.admin.errors.required"
6
+ const GTE_0 = "ln.admin.errors.gte-0"
7
+ const INTEGER = "ln.admin.errors.integer"
8
+ const UNIQUE_ELEMENTS = "ln.admin.errors.unique-elements"
9
+
10
+ const unique = <TArrayItem>(path: Extract<keyof TArrayItem, string>) => {
11
+ return (values: TArrayItem[], ctx: RefinementCtx) => {
12
+ const seenValues = new Set<unknown>()
13
+ values.forEach((value, index) => {
14
+ if (seenValues.has(value[path])) {
15
+ ctx.addIssue({
16
+ path: [index, path],
17
+ message: UNIQUE_ELEMENTS,
18
+ code: "custom",
19
+ })
20
+ }
21
+ seenValues.add(value[path])
22
+ })
23
+ }
24
+ }
6
25
 
7
26
  export const mediaItemSchema = z.object({
8
27
  commonId: z.string().nonempty(NON_EMPTY_STRING),
9
28
  title: z.string().nonempty(NON_EMPTY_STRING),
10
29
  type: z.string().nonempty(REQUIRED),
11
30
  language: z.string().nonempty(REQUIRED),
31
+ authors: z
32
+ .object({ value: z.string().nonempty(NON_EMPTY_STRING) })
33
+ .array()
34
+ .superRefine(unique("value")),
35
+ categories: z
36
+ .object({
37
+ value: z.string().nonempty(REQUIRED),
38
+ })
39
+ .array()
40
+ .superRefine(unique("value")),
41
+ collections: z
42
+ .object({
43
+ collection: z.string().nonempty(REQUIRED),
44
+ index: z.number().int(INTEGER).gte(0, GTE_0).optional(),
45
+ })
46
+ .array()
47
+ .superRefine(unique("collection")),
12
48
  dateCreated: z.string().date(INVALID_DATE),
13
49
  })
14
50
 
15
- export type MediaItem = z.infer<typeof mediaItemSchema>
51
+ export type MediaItem = z.input<typeof mediaItemSchema>
@@ -70,7 +70,7 @@ function getImage({ image, id }: Category) {
70
70
  ]}
71
71
  >
72
72
  <span class="line-clamp-3 select-none text-balance font-bold">
73
- {category.name}
73
+ {category.labelText}
74
74
  </span>
75
75
  </div>
76
76
  </div>
@@ -128,7 +128,7 @@ function getImage({ image, id }: Category) {
128
128
  ]}
129
129
  >
130
130
  <span class="line-clamp-3 select-none text-balance font-bold">
131
- {category.name}
131
+ {category.labelText}
132
132
  </span>
133
133
  </div>
134
134
  </div>
@@ -52,7 +52,7 @@ const subtitleSizes = {
52
52
  alt=""
53
53
  />
54
54
  <div
55
- class="bg-gradient-radial absolute top-0 flex h-full w-full flex-col items-center justify-center from-black/30 to-black/40 p-4 text-center text-gray-50"
55
+ class="absolute top-0 flex h-full w-full flex-col items-center justify-center bg-gradient-radial from-black/30 to-black/40 p-4 text-center text-gray-50"
56
56
  class:list={[className]}
57
57
  >
58
58
  {
@@ -53,7 +53,7 @@ const { image, id, title, text, link, className, titleClass, textClass } =
53
53
  {
54
54
  link && (
55
55
  <a
56
- class="bg-primary hover:bg-primary/85 inline-flex items-center justify-center gap-2 rounded-2xl px-6 py-3 text-sm font-bold uppercase text-gray-50 shadow-sm hover:text-gray-100"
56
+ class="inline-flex items-center justify-center gap-2 rounded-2xl bg-primary px-6 py-3 text-sm font-bold uppercase text-gray-50 shadow-sm hover:bg-primary/85 hover:text-gray-100"
57
57
  href={link.href}
58
58
  >
59
59
  {link.text}
@@ -38,7 +38,7 @@ const t = Astro.locals.i18n.t
38
38
  const types = Object.fromEntries(
39
39
  (await getMediaTypes()).map((type) => [
40
40
  type.id,
41
- { ...type.data, name: t(type.data.label) },
41
+ { ...type.data, labelText: t(type.data.label) },
42
42
  ]),
43
43
  )
44
44
 
@@ -119,7 +119,7 @@ const coverImageStyle =
119
119
  <span class="line-clamp-2 h-12 text-balance text-sm font-bold text-gray-700">
120
120
  <Icon
121
121
  className={`${types[item.data.type.id].icon} me-2 align-bottom`}
122
- ariaLabel={types[item.data.type.id].name}
122
+ ariaLabel={types[item.data.type.id].labelText}
123
123
  />
124
124
  {item.data.title}
125
125
  </span>
@@ -159,7 +159,7 @@ const coverImageStyle =
159
159
  <span class="line-clamp-2 h-12 text-balance text-sm font-bold text-gray-700">
160
160
  <Icon
161
161
  className={`${types[item.data.type.id].icon} me-2 align-bottom`}
162
- ariaLabel={types[item.data.type.id].name}
162
+ ariaLabel={types[item.data.type.id].labelText}
163
163
  />
164
164
  {item.data.title}
165
165
  </span>
@@ -34,7 +34,7 @@ const mediaTypes = Object.fromEntries(
34
34
  type.id,
35
35
  {
36
36
  id: type.id,
37
- name: t(type.data.label),
37
+ labelText: t(type.data.label),
38
38
  icon: type.data.icon,
39
39
  coverImageStyle: type.data.coverImageStyle,
40
40
  },
@@ -84,7 +84,7 @@ const mediaTypes = Object.fromEntries(
84
84
  <p class="mb-1 line-clamp-3 text-balance font-bold text-gray-700 md:mb-3">
85
85
  <Icon
86
86
  className={`${mediaTypes[item.data.type.id].icon} me-2 align-bottom text-2xl text-gray-700`}
87
- ariaLabel={mediaTypes[item.data.type.id].name}
87
+ ariaLabel={mediaTypes[item.data.type.id].labelText}
88
88
  />
89
89
  <span>{item.data.title}</span>
90
90
  </p>
@@ -12,7 +12,7 @@ const { t } = Astro.locals.i18n
12
12
  action={`/${Astro.currentLocale}/media`}
13
13
  method="get"
14
14
  role="search"
15
- class="dy-join group w-full rounded-2xl shadow-sm outline-2 outline-offset-2 outline-gray-400 group-focus-within:outline"
15
+ class="group dy-join w-full rounded-2xl shadow-sm outline-2 outline-offset-2 outline-gray-400 group-focus-within:outline"
16
16
  class:list={[Astro.props.className]}
17
17
  >
18
18
  <input
@@ -33,12 +33,27 @@ const contentCategories = Object.fromEntries(
33
33
  *
34
34
  * @param currentLocale current locale
35
35
  * @param t translate function
36
- * @returns categories sorted by name
36
+ * @returns categories sorted by labelText
37
37
  */
38
38
  export async function getUsedCategories(currentLocale: string, t: TranslateFn) {
39
39
  return [...Object.entries(contentCategories)]
40
- .map(([id, data]) => ({ id, ...data, name: t(data.label) }))
41
- .sort((a, b) => a.name.localeCompare(b.name, currentLocale))
40
+ .map(([id, data]) => ({ id, ...data, labelText: t(data.label) }))
41
+ .sort((a, b) => a.labelText.localeCompare(b.labelText, currentLocale))
42
+ }
43
+
44
+ /**
45
+ * Get all categories. This includes categories that are not
46
+ * referenced by any media item. If you only need categories that are referenced
47
+ * by media items use `getUsedCategories`.
48
+ *
49
+ * @param currentLocale current locale
50
+ * @param t translate function
51
+ * @returns categories sorted by labelText
52
+ */
53
+ export async function getCategories(currentLocale: string, t: TranslateFn) {
54
+ return [...Object.entries(categoriesById)]
55
+ .map(([id, data]) => ({ id, ...data, labelText: t(data.label) }))
56
+ .sort((a, b) => a.labelText.localeCompare(b.labelText, currentLocale))
42
57
  }
43
58
 
44
59
  export async function getCategory(id: string) {
@@ -1,4 +1,4 @@
1
- import { createContext } from "react"
1
+ import { createContext, useMemo } from "react"
2
2
 
3
3
  export type I18n = {
4
4
  t: (key: string) => string
@@ -21,16 +21,18 @@ export const createI18n = ({
21
21
  currentLocale,
22
22
  direction,
23
23
  }: I18nConfig) => {
24
- const t = (key: string) => {
25
- const value = translations[key]
26
- if (value) {
27
- return value
24
+ return useMemo(() => {
25
+ const t = (key: string) => {
26
+ const value = translations[key]
27
+ if (value) {
28
+ return value
29
+ }
30
+ if (key.match(/^(?:ln|x)\../i)) {
31
+ console.error(`Missing translation for key ${key}`)
32
+ return ""
33
+ }
34
+ return key
28
35
  }
29
- if (key.match(/^(?:ln|x)\../i)) {
30
- console.error(`Missing translation for key ${key}`)
31
- return ""
32
- }
33
- return key
34
- }
35
- return { t, currentLocale, direction }
36
+ return { t, currentLocale, direction }
37
+ }, [])
36
38
  }
@@ -27,6 +27,6 @@ export const resolveTranslatedLanguage = (bcp47: string, t: TranslateFn) => {
27
27
  const language = resolveLanguage(bcp47)
28
28
  return {
29
29
  ...language,
30
- name: t(language.label),
30
+ labelText: t(language.label),
31
31
  }
32
32
  }
@@ -9,7 +9,7 @@ type Props = {
9
9
 
10
10
  <Page>
11
11
  <article
12
- class="prose prose-img:rounded-md mx-auto mt-8 max-w-screen-md px-4 sm:mt-16 md:px-8"
12
+ class="prose mx-auto mt-8 max-w-screen-md px-4 prose-img:rounded-md sm:mt-16 md:px-8"
13
13
  class:list={[Astro.props.className]}
14
14
  >
15
15
  <slot />
@@ -13,9 +13,10 @@ import ViewTransition from "./components/ViewTransition.astro"
13
13
  interface Props {
14
14
  title?: string
15
15
  description?: string
16
+ mainClass?: string
16
17
  }
17
18
 
18
- const { title, description } = Astro.props
19
+ const { title, description, mainClass } = Astro.props
19
20
  const configTitle = Astro.locals.i18n.t(config.title)
20
21
 
21
22
  const { currentLocale } = Astro.locals.i18n
@@ -39,7 +40,7 @@ const language = resolveLanguage(currentLocale)
39
40
  class="flex min-h-screen flex-col overflow-y-scroll bg-gray-50 text-gray-900"
40
41
  >
41
42
  <Header />
42
- <main class="grow pb-8 pt-14 sm:py-20">
43
+ <main class={`grow pb-8 pt-14 sm:py-20 ${mainClass ?? ""}`}>
43
44
  <slot />
44
45
  </main>
45
46
  {CustomFooter ? <CustomFooter /> : <Footer />}
@@ -9,7 +9,7 @@ const { t, locales } = Astro.locals.i18n
9
9
  const translations = locales
10
10
  .map((locale) => ({
11
11
  locale,
12
- label: resolveTranslatedLanguage(locale, t).name,
12
+ label: resolveTranslatedLanguage(locale, t).labelText,
13
13
  active: locale === Astro.currentLocale,
14
14
  href: currentPathWithLocale(locale),
15
15
  }))
@@ -14,7 +14,7 @@ const { icon, label } = Astro.props
14
14
  role="button"
15
15
  tabindex="0"
16
16
  aria-label={Astro.locals.i18n.t(label)}
17
- class="hover:text-primary flex cursor-pointer rounded-md p-3 text-gray-600"
17
+ class="flex cursor-pointer rounded-md p-3 text-gray-600 hover:text-primary"
18
18
  >
19
19
  <Icon className={icon} ariaLabel="" />
20
20
  </div>
@@ -35,7 +35,7 @@ const t = Astro.locals.i18n.t
35
35
  {
36
36
  !config.searchPage?.hideHeaderSearchIcon && (
37
37
  <a
38
- class="hover:text-primary flex p-3 text-gray-600"
38
+ class="flex p-3 text-gray-600 hover:text-primary"
39
39
  aria-label={t("ln.search.title")}
40
40
  data-astro-prefetch="viewport"
41
41
  href={searchPagePath(Astro.currentLocale)}
@@ -24,7 +24,7 @@ const content = createContentMetadata(item.data.content[0])
24
24
  content.isExternal && (
25
25
  <Icon
26
26
  ariaLabel={Astro.locals.i18n.t("ln.external-link")}
27
- className={`mdi--external-link shrink-0`}
27
+ className={`shrink-0 mdi--external-link`}
28
28
  />
29
29
  )
30
30
  }
@@ -21,7 +21,7 @@ const { t } = Astro.locals.i18n
21
21
  <Label>{translations.length ? t("ln.languages") : t("ln.language")}</Label>
22
22
  <ul class="flex flex-wrap gap-2">
23
23
  <li class="py-1 pe-2 text-gray-800">
24
- {resolveTranslatedLanguage(item.data.language, t).name}
24
+ {resolveTranslatedLanguage(item.data.language, t).labelText}
25
25
  </li>
26
26
  {
27
27
  translations.map((translation) => (
@@ -30,7 +30,7 @@ const { t } = Astro.locals.i18n
30
30
  href={detailsPagePath(Astro.currentLocale, translation)}
31
31
  hreflang={translation.language}
32
32
  >
33
- {resolveTranslatedLanguage(translation.language, t).name}
33
+ {resolveTranslatedLanguage(translation.language, t).labelText}
34
34
  </a>
35
35
  </li>
36
36
  ))
@@ -12,7 +12,7 @@ export default function LoadingSkeleton() {
12
12
  <div className="h-4 w-5/6 rounded-md bg-gray-200 md:h-6"></div>
13
13
  </div>
14
14
  <Icon
15
- className="mdi--chevron-right my-auto me-4 ms-2 hidden shrink-0 text-2xl text-gray-300 sm:block"
15
+ className="my-auto me-4 ms-2 hidden shrink-0 text-2xl text-gray-300 mdi--chevron-right sm:block"
16
16
  flipIcon={direction === "rtl"}
17
17
  ariaLabel=""
18
18
  />
@@ -9,24 +9,24 @@ import SearchFilterReact from "./SearchFilter.tsx"
9
9
 
10
10
  const { t, currentLocale } = Astro.locals.i18n
11
11
 
12
- const sortByName = (array: { id: string; name: string }[]) =>
13
- array.sort((a, b) => a.name.localeCompare(b.name, currentLocale))
12
+ const sortByLabelText = (array: { id: string; labelText: string }[]) =>
13
+ array.sort((a, b) => a.labelText.localeCompare(b.labelText, currentLocale))
14
14
 
15
15
  const categories = (await getUsedCategories(currentLocale, t)).map(
16
- ({ id, name }) => ({ id, name }),
16
+ ({ id, labelText }) => ({ id, labelText }),
17
17
  )
18
18
 
19
- const mediaTypes = sortByName(
19
+ const mediaTypes = sortByLabelText(
20
20
  (await getMediaTypes()).map((type) => ({
21
21
  id: type.id,
22
- name: t(type.data.label),
22
+ labelText: t(type.data.label),
23
23
  })),
24
24
  )
25
25
 
26
- const languages = sortByName(
26
+ const languages = sortByLabelText(
27
27
  contentLanguages.map((language) => ({
28
28
  id: language.code,
29
- name: t(language.label),
29
+ labelText: t(language.label),
30
30
  })),
31
31
  )
32
32
 
@@ -7,7 +7,7 @@ import { useSearchQueryParam } from "../hooks/use-search-query-param"
7
7
  import { CATEGORY, LANGUAGE, SEARCH, TYPE } from "../utils/search-query"
8
8
  import Select from "./Select"
9
9
 
10
- type FilterValue = { id: string; name: string }
10
+ type FilterValue = { id: string; labelText: string }
11
11
 
12
12
  interface Props {
13
13
  languages: FilterValue[]
@@ -57,7 +57,7 @@ export default function SearchFilter({
57
57
  onInput={(e) => debouncedSetSearch(e.currentTarget.value)}
58
58
  onKeyDown={(e) => e.key === "Enter" && searchInput.current?.blur()}
59
59
  />
60
- <Icon className="mdi--magnify text-xl" ariaLabel="" />
60
+ <Icon className="text-xl mdi--magnify" ariaLabel="" />
61
61
  </label>
62
62
  <div className="mb-8 grid grid-cols-1 gap-2 sm:grid-cols-3 sm:gap-6 md:mb-10">
63
63
  {languageFilterEnabled && (
@@ -66,7 +66,7 @@ export default function SearchFilter({
66
66
  initialValue={language}
67
67
  valueChange={(val) => setLanguage(val)}
68
68
  options={[
69
- { id: "", name: t("ln.search.all-languages") },
69
+ { id: "", labelText: t("ln.search.all-languages") },
70
70
  ...languages,
71
71
  ]}
72
72
  />
@@ -78,7 +78,7 @@ export default function SearchFilter({
78
78
  initialValue={type}
79
79
  valueChange={(val) => setType(val)}
80
80
  options={[
81
- { id: "", name: t("ln.search.all-types") },
81
+ { id: "", labelText: t("ln.search.all-types") },
82
82
  ...mediaTypes,
83
83
  ]}
84
84
  />
@@ -90,7 +90,7 @@ export default function SearchFilter({
90
90
  initialValue={category}
91
91
  valueChange={(val) => setCategory(val)}
92
92
  options={[
93
- { id: "", name: t("ln.search.all-categories") },
93
+ { id: "", labelText: t("ln.search.all-categories") },
94
94
  ...categories,
95
95
  ]}
96
96
  />
@@ -10,15 +10,15 @@ import SearchListReact from "./SearchList.tsx"
10
10
  const { t, currentLocale } = Astro.locals.i18n
11
11
 
12
12
  const categories: Record<string, string> = {}
13
- for (const { id, name } of await getUsedCategories(currentLocale, t)) {
14
- categories[id] = name
13
+ for (const { id, labelText } of await getUsedCategories(currentLocale, t)) {
14
+ categories[id] = labelText
15
15
  }
16
16
 
17
17
  const mediaTypes = Object.fromEntries(
18
18
  (await getMediaTypes()).map((type) => [
19
19
  type.id,
20
20
  {
21
- name: t(type.data.label),
21
+ labelText: t(type.data.label),
22
22
  icon: type.data.icon,
23
23
  coverImageStyle: type.data.coverImageStyle,
24
24
  },
@@ -28,7 +28,7 @@ const mediaTypes = Object.fromEntries(
28
28
  const languages = Object.fromEntries(
29
29
  contentLanguages.map((language) => [
30
30
  language.code,
31
- { direction: language.direction, name: t(language.label) },
31
+ { direction: language.direction, labelText: t(language.label) },
32
32
  ]),
33
33
  )
34
34
 
@@ -5,13 +5,13 @@ import { detailsPagePath } from "../../../utils/paths"
5
5
  import type { SearchItem } from "../api/search-response"
6
6
 
7
7
  export type MediaType = {
8
- name: string
8
+ labelText: string
9
9
  icon: string
10
10
  coverImageStyle: "default" | "book" | "video"
11
11
  }
12
12
 
13
13
  export type TranslatedLanguage = {
14
- name: string
14
+ labelText: string
15
15
  direction: "rtl" | "ltr"
16
16
  }
17
17
 
@@ -58,7 +58,7 @@ export default function SearchListItem({
58
58
  <h2 className="mb-1 line-clamp-2 text-balance text-sm font-bold text-gray-700 md:mb-3 md:text-base">
59
59
  <Icon
60
60
  className={`${mediaTypes[item.type].icon} me-2 align-bottom text-2xl text-gray-700`}
61
- ariaLabel={mediaTypes[item.type].name}
61
+ ariaLabel={mediaTypes[item.type].labelText}
62
62
  />
63
63
  <span>{item.title}</span>
64
64
  </h2>
@@ -70,7 +70,7 @@ export default function SearchListItem({
70
70
  )}
71
71
  {showLanguage && (
72
72
  <span className="rounded-lg border border-gray-300 px-2 py-1 text-gray-500">
73
- {languages[item.language].name}
73
+ {languages[item.language].labelText}
74
74
  </span>
75
75
  )}
76
76
  <ul
@@ -99,7 +99,7 @@ export default function SearchListItem({
99
99
  </div>
100
100
  </div>
101
101
  <Icon
102
- className="mdi--chevron-right md:group-hover:text-primary my-auto me-4 ms-2 hidden shrink-0 text-2xl text-gray-300 sm:block"
102
+ className="my-auto me-4 ms-2 hidden shrink-0 text-2xl text-gray-300 mdi--chevron-right sm:block md:group-hover:text-primary"
103
103
  flipIcon={direction === "rtl"}
104
104
  ariaLabel=""
105
105
  />
@@ -2,7 +2,7 @@ type Props = {
2
2
  label: string
3
3
  initialValue: string | undefined
4
4
  valueChange: (value: string) => void
5
- options: { id: string; name: string }[]
5
+ options: { id: string; labelText: string }[]
6
6
  }
7
7
 
8
8
  export default function Select({
@@ -17,13 +17,13 @@ export default function Select({
17
17
  {label}
18
18
  </span>
19
19
  <select
20
- className="dy-select dy-select-bordered sm:dy-select-sm w-full rounded-xl"
20
+ className="dy-select dy-select-bordered w-full rounded-xl sm:dy-select-sm"
21
21
  value={initialValue}
22
22
  onChange={(e) => valueChange(e.currentTarget.value)}
23
23
  >
24
- {options.map(({ id, name }) => (
24
+ {options.map(({ id, labelText }) => (
25
25
  <option key={id} value={id}>
26
- {name}
26
+ {labelText}
27
27
  </option>
28
28
  ))}
29
29
  </select>
@@ -6,8 +6,8 @@ import { observeSearchQuery, type SearchQuery } from "../utils/search-query"
6
6
 
7
7
  interface Context {
8
8
  categories: Record<string, string>
9
- mediaTypes: Record<string, { name: string }>
10
- languages: Record<string, { name: string }>
9
+ mediaTypes: Record<string, { labelText: string }>
10
+ languages: Record<string, { labelText: string }>
11
11
  }
12
12
 
13
13
  export function useSearch({ categories, mediaTypes, languages }: Context) {
@@ -39,8 +39,8 @@ export function useSearch({ categories, mediaTypes, languages }: Context) {
39
39
  const translatedCategories =
40
40
  item.categories &&
41
41
  item.categories.map((categoryId) => categories[categoryId])
42
- const translatedType = mediaTypes[item.type].name
43
- const translatedLanguage = languages[item.language].name
42
+ const translatedType = mediaTypes[item.type].labelText
43
+ const translatedLanguage = languages[item.language].labelText
44
44
 
45
45
  return {
46
46
  ...item,