lightnet 3.12.2 → 4.0.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.
Files changed (111) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/exports/content.ts +7 -2
  3. package/exports/i18n.ts +0 -1
  4. package/exports/index.ts +1 -5
  5. package/exports/utils.ts +0 -1
  6. package/package.json +26 -12
  7. package/src/astro-integration/config.ts +60 -49
  8. package/src/astro-integration/integration.ts +13 -24
  9. package/src/astro-integration/tailwind.ts +86 -0
  10. package/src/astro-integration/validators/validate-inline-translations.ts +51 -0
  11. package/src/astro-integration/validators/validate-languages.ts +39 -0
  12. package/src/astro-integration/virtual.d.ts +8 -6
  13. package/src/astro-integration/vite-plugin-lightnet-config.ts +29 -9
  14. package/src/components/CarouselSection.astro +7 -11
  15. package/src/components/CategoriesSection.astro +2 -2
  16. package/src/components/HighlightSection.astro +4 -7
  17. package/src/components/Icon.tsx +2 -2
  18. package/src/components/MediaGallerySection.astro +88 -68
  19. package/src/components/MediaList.astro +9 -7
  20. package/src/components/SearchInput.astro +7 -4
  21. package/src/components/Section.astro +7 -5
  22. package/src/components/VideoPlayer.astro +2 -3
  23. package/src/content/content-schema.ts +129 -142
  24. package/src/content/get-categories.ts +52 -28
  25. package/src/content/get-languages.ts +29 -8
  26. package/src/content/get-media-collections.ts +43 -0
  27. package/src/content/get-media-types.ts +41 -7
  28. package/src/content/query-media-items.ts +23 -13
  29. package/src/i18n/bcp-47.ts +8 -0
  30. package/src/i18n/get-locale-paths.ts +1 -3
  31. package/src/i18n/locals.d.ts +21 -3
  32. package/src/i18n/locals.ts +18 -11
  33. package/src/i18n/resolve-current-locale.ts +18 -0
  34. package/src/i18n/resolve-language.ts +10 -5
  35. package/src/i18n/translate-map.ts +70 -0
  36. package/src/i18n/translate.ts +68 -47
  37. package/src/layouts/Page.astro +5 -3
  38. package/src/layouts/components/LanguagePicker.astro +22 -17
  39. package/src/layouts/components/Menu.astro +2 -5
  40. package/src/layouts/components/MenuItem.astro +1 -1
  41. package/src/layouts/components/PageNavigation.astro +29 -29
  42. package/src/layouts/components/PageTitle.astro +23 -7
  43. package/src/pages/404Route.astro +2 -1
  44. package/src/pages/RootRoute.astro +6 -1
  45. package/src/pages/details-page/DefaultDetailsPage.astro +9 -2
  46. package/src/pages/details-page/DetailsPageRoute.astro +1 -2
  47. package/src/pages/details-page/components/AudioPanel.astro +7 -3
  48. package/src/pages/details-page/components/AudioPlayer.astro +2 -2
  49. package/src/pages/details-page/components/ContentSection.astro +67 -44
  50. package/src/pages/details-page/components/MediaCollection.astro +8 -4
  51. package/src/pages/details-page/components/MediaCollectionsSection.astro +3 -6
  52. package/src/pages/details-page/components/main-details/EditButton.astro +22 -10
  53. package/src/pages/details-page/components/main-details/OpenButton.astro +17 -12
  54. package/src/pages/details-page/components/main-details/ShareButton.astro +3 -2
  55. package/src/pages/details-page/components/more-details/Categories.astro +5 -3
  56. package/src/pages/details-page/components/more-details/Languages.astro +12 -7
  57. package/src/pages/details-page/utils/create-content-metadata.ts +24 -9
  58. package/src/pages/details-page/utils/get-translations.ts +6 -0
  59. package/src/pages/search-page/components/LoadingSkeleton.tsx +6 -5
  60. package/src/pages/search-page/components/SearchFilter.astro +10 -21
  61. package/src/pages/search-page/components/SearchFilter.tsx +2 -2
  62. package/src/pages/search-page/components/SearchList.astro +10 -7
  63. package/src/pages/search-page/components/SearchListItem.tsx +5 -4
  64. package/src/pages/search-page/hooks/use-search.ts +5 -2
  65. package/src/utils/lazy.ts +20 -0
  66. package/src/utils/paths.ts +40 -3
  67. package/src/utils/urls.ts +1 -2
  68. package/src/utils/verify-schema.ts +12 -10
  69. package/tailwind.config.ts +1 -25
  70. package/__e2e__/admin.spec.ts +0 -20
  71. package/__e2e__/basics-fixture.ts +0 -77
  72. package/__e2e__/fixtures/basics/astro.config.mjs +0 -40
  73. package/__e2e__/fixtures/basics/node_modules/.bin/astro +0 -21
  74. package/__e2e__/fixtures/basics/node_modules/.bin/tailwind +0 -21
  75. package/__e2e__/fixtures/basics/node_modules/.bin/tailwindcss +0 -21
  76. package/__e2e__/fixtures/basics/node_modules/.bin/tsc +0 -21
  77. package/__e2e__/fixtures/basics/node_modules/.bin/tsserver +0 -21
  78. package/__e2e__/fixtures/basics/package.json +0 -21
  79. package/__e2e__/fixtures/basics/public/favicon.svg +0 -1
  80. package/__e2e__/fixtures/basics/public/files/example.pdf +0 -0
  81. package/__e2e__/fixtures/basics/src/assets/logo.png +0 -0
  82. package/__e2e__/fixtures/basics/src/content/categories/christian-living.json +0 -3
  83. package/__e2e__/fixtures/basics/src/content/categories/teens.json +0 -3
  84. package/__e2e__/fixtures/basics/src/content/categories/theology.json +0 -3
  85. package/__e2e__/fixtures/basics/src/content/media/faithful-freestyle--en.json +0 -13
  86. package/__e2e__/fixtures/basics/src/content/media/how-to-kickflip--de.json +0 -12
  87. package/__e2e__/fixtures/basics/src/content/media/images/cover.jpg +0 -0
  88. package/__e2e__/fixtures/basics/src/content/media/images/how-to-kickflip--en.webp +0 -0
  89. package/__e2e__/fixtures/basics/src/content/media/skate-sounds--en.json +0 -15
  90. package/__e2e__/fixtures/basics/src/content/media-collections/how-to-articles.json +0 -3
  91. package/__e2e__/fixtures/basics/src/content/media-types/audio.json +0 -7
  92. package/__e2e__/fixtures/basics/src/content/media-types/book.json +0 -9
  93. package/__e2e__/fixtures/basics/src/content/media-types/video.json +0 -7
  94. package/__e2e__/fixtures/basics/src/content.config.ts +0 -3
  95. package/__e2e__/fixtures/basics/src/pages/[locale]/index.astro +0 -16
  96. package/__e2e__/fixtures/basics/src/translations/de.yml +0 -9
  97. package/__e2e__/fixtures/basics/src/translations/en.yml +0 -9
  98. package/__e2e__/fixtures/basics/tailwind.config.mjs +0 -8
  99. package/__e2e__/global.teardown.ts +0 -5
  100. package/__e2e__/homepage.spec.ts +0 -123
  101. package/__e2e__/search.spec.ts +0 -14
  102. package/__tests__/pages/details-page/create-content-metadata.spec.ts +0 -135
  103. package/__tests__/utils/markdown.spec.ts +0 -74
  104. package/__tests__/utils/urls.spec.ts +0 -27
  105. package/playwright.config.ts +0 -31
  106. package/src/astro-integration/project-context.ts +0 -5
  107. package/src/content/compare-media-collection-items.ts +0 -24
  108. package/src/i18n/resolve-default-locale.ts +0 -19
  109. package/src/i18n/resolve-locales.ts +0 -5
  110. package/src/pages/details-page/utils/get-collection-items.ts +0 -29
  111. package/vitest.config.js +0 -20
@@ -3,25 +3,32 @@ import { fileURLToPath } from "node:url"
3
3
 
4
4
  import type { AstroConfig, AstroIntegrationLogger, ViteUserConfig } from "astro"
5
5
 
6
- import { type LightnetConfig } from "./config"
6
+ import type { ExtendedLightnetConfig } from "./config"
7
7
 
8
8
  const CONFIG = "virtual:lightnet/config"
9
9
  const LOGO = "virtual:lightnet/logo"
10
- const PROJECT_CONTEXT = "virtual:lightnet/project-context"
11
10
  const CUSTOM_HEAD = "virtual:lightnet/components/CustomHead"
12
11
  const CUSTOM_FOOTER = "virtual:lightnet/components/CustomFooter"
12
+ const MEDIA_ITEM_EDIT_BUTTON_CONTROLLER =
13
+ "virtual:lightnet/components/media-item-edit-button-controller"
14
+
15
+ const TRANSLATION_RUNTIME_MODULES = [
16
+ fileURLToPath(new URL("../i18n/translations.ts", import.meta.url)),
17
+ fileURLToPath(new URL("../i18n/translate.ts", import.meta.url)),
18
+ fileURLToPath(new URL("../i18n/locals.ts", import.meta.url)),
19
+ ]
13
20
 
14
21
  const VIRTUAL_MODULES = [
15
22
  CONFIG,
16
23
  LOGO,
17
- PROJECT_CONTEXT,
18
24
  CUSTOM_HEAD,
19
25
  CUSTOM_FOOTER,
26
+ MEDIA_ITEM_EDIT_BUTTON_CONTROLLER,
20
27
  ] as const
21
28
 
22
29
  export function vitePluginLightnetConfig(
23
- config: LightnetConfig,
24
- { root, srcDir, site }: Pick<AstroConfig, "root" | "srcDir" | "site">,
30
+ config: ExtendedLightnetConfig,
31
+ { root }: Pick<AstroConfig, "root">,
25
32
  logger: AstroIntegrationLogger,
26
33
  ): NonNullable<ViteUserConfig["plugins"]>[number] {
27
34
  const resolveFilePath = (id: string) =>
@@ -33,14 +40,27 @@ export function vitePluginLightnetConfig(
33
40
  const module = VIRTUAL_MODULES.find((m) => m === id)
34
41
  if (module) return `\0${module}`
35
42
  },
36
- handleHotUpdate({ file, server }) {
43
+ handleHotUpdate({ file, modules, server }) {
37
44
  const srcPath = resolve(fileURLToPath(root), "src/translations/")
38
45
  if (
39
46
  (file.endsWith(".yml") || file.endsWith(".yaml")) &&
40
47
  file.startsWith(srcPath)
41
48
  ) {
49
+ const affectedModules = [
50
+ ...modules,
51
+ ...TRANSLATION_RUNTIME_MODULES.flatMap((id) => {
52
+ const module = server.moduleGraph.getModuleById(id)
53
+ return module ? [module] : []
54
+ }),
55
+ ]
56
+
57
+ for (const module of affectedModules) {
58
+ server.moduleGraph.invalidateModule(module)
59
+ }
60
+
42
61
  logger.info(`Update translations ${file.slice(srcPath.length)}`)
43
- server.restart()
62
+ server.ws.send({ type: "full-reload" })
63
+ return []
44
64
  }
45
65
  },
46
66
  load(id): string | undefined {
@@ -52,8 +72,6 @@ export function vitePluginLightnetConfig(
52
72
  return config.logo
53
73
  ? `import logo from ${resolveFilePath(config.logo.src)}; export default logo;`
54
74
  : "export default undefined;"
55
- case PROJECT_CONTEXT:
56
- return `export default ${JSON.stringify({ root, srcDir, site })}`
57
75
  case CUSTOM_HEAD:
58
76
  return config.headComponent
59
77
  ? `export { default } from ${resolveFilePath(config.headComponent)};`
@@ -62,6 +80,8 @@ export function vitePluginLightnetConfig(
62
80
  return config.footerComponent
63
81
  ? `export { default } from ${resolveFilePath(config.footerComponent)};`
64
82
  : "export default undefined;"
83
+ case MEDIA_ITEM_EDIT_BUTTON_CONTROLLER:
84
+ return "export default undefined;"
65
85
  }
66
86
  },
67
87
  }
@@ -1,11 +1,13 @@
1
1
  ---
2
- import Icon from "./Icon"
2
+ import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"
3
+
3
4
  import Section, { type Props as SectionProps } from "./Section.astro"
4
5
 
5
6
  type Props = SectionProps
6
7
 
7
8
  const { titleClass = "", ...props } = Astro.props
8
9
  const { t, direction } = Astro.locals.i18n
10
+ const iconDirectionClass = direction === "rtl" ? "scale-x-[-1]" : ""
9
11
  ---
10
12
 
11
13
  <Section {...props} titleClass={"!mb-8 sm:!-mb-1 " + titleClass}>
@@ -15,22 +17,16 @@ const { t, direction } = Astro.locals.i18n
15
17
  data-button-prev
16
18
  aria-label={t("ln.previous")}
17
19
  class="flex h-9 w-9 items-center justify-center rounded-full border-2 border-gray-200 bg-gray-200 text-gray-600 transition-colors ease-in-out hover:border-gray-400 disabled:bg-transparent disabled:text-gray-300 disabled:hover:border-gray-200"
18
- ><Icon
19
- className="mdi--chevron-left"
20
- ariaLabel=""
21
- flipIcon={direction === "rtl"}
22
- /></button
23
20
  >
21
+ <ChevronLeftIcon className={iconDirectionClass} />
22
+ </button>
24
23
  <button
25
24
  data-button-next
26
25
  aria-label={t("ln.next")}
27
26
  class="flex h-9 w-9 items-center justify-center rounded-full border-2 border-gray-200 bg-gray-200 text-gray-600 transition-colors ease-in-out hover:border-gray-400 disabled:bg-transparent disabled:text-gray-300 disabled:hover:border-gray-200"
28
- ><Icon
29
- className="mdi--chevron-right"
30
- ariaLabel=""
31
- flipIcon={direction === "rtl"}
32
- /></button
33
27
  >
28
+ <ChevronRightIcon className={iconDirectionClass} />
29
+ </button>
34
30
  </div>
35
31
 
36
32
  <div data-carousel class="-m-4 overflow-hidden p-4">
@@ -12,10 +12,10 @@ type Props = Omit<SectionProps, "maxWidth"> & {
12
12
  }
13
13
 
14
14
  const { title, layout = "carousel", ...props } = Astro.props
15
- const { t, currentLocale } = Astro.locals.i18n
15
+ const { t, tMap, currentLocale } = Astro.locals.i18n
16
16
  const resolvedTitle = title ?? t("ln.categories")
17
17
 
18
- const categories = await getUsedCategories(currentLocale, t)
18
+ const categories = await getUsedCategories(currentLocale, tMap)
19
19
  type Category = (typeof categories)[number]
20
20
 
21
21
  function getImage({ image, id }: Category) {
@@ -1,8 +1,7 @@
1
1
  ---
2
2
  import type { ImageMetadata } from "astro"
3
3
  import { Image } from "astro:assets"
4
-
5
- import Icon from "./Icon"
4
+ import { ArrowRightIcon } from "lucide-react"
6
5
 
7
6
  interface Props {
8
7
  image: ImageMetadata
@@ -17,6 +16,8 @@ interface Props {
17
16
 
18
17
  const { image, id, title, text, link, className, titleClass, textClass } =
19
18
  Astro.props
19
+ const iconDirectionClass =
20
+ Astro.locals.i18n.direction === "rtl" ? "scale-x-[-1]" : ""
20
21
  ---
21
22
 
22
23
  <section
@@ -57,11 +58,7 @@ const { image, id, title, text, link, className, titleClass, textClass } =
57
58
  href={link.href}
58
59
  >
59
60
  {link.text}
60
- <Icon
61
- flipIcon={Astro.locals.i18n.direction === "rtl"}
62
- className="mdi--arrow-right"
63
- ariaLabel=""
64
- />
61
+ <ArrowRightIcon className={iconDirectionClass} />
65
62
  </a>
66
63
  )
67
64
  }
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Icon Component.
3
3
  *
4
- * @param className containing a material design icon name prefixed with 'mdi--'
4
+ * @param className containing a lucide icon name prefixed with 'lucide--'
5
5
  * @param ariaLabel accessibility label to be added e.g. for people using a screen reader. Empty string will hide the icon from a screen reader.
6
6
  * @param flipIcon if set to true this will mirror the icon along its x-axis. Useful for RTL layouts.
7
- * @see https://pictogrammers.com/library/mdi/ for available icons
7
+ * @see https://lucide.dev/icons/ for available icons
8
8
  * @returns icon
9
9
  */
10
10
  export default function Icon({
@@ -9,8 +9,6 @@ import CoverImageDecorator from "./CoverImageDecorator"
9
9
  import Icon from "./Icon"
10
10
  import Section, { type Props as SectionProps } from "./Section.astro"
11
11
 
12
- type ItemStyle = "book" | "video" | "portrait" | "landscape"
13
-
14
12
  type MediaItem = {
15
13
  id: string
16
14
  data: {
@@ -22,23 +20,28 @@ type MediaItem = {
22
20
 
23
21
  type Props = Omit<SectionProps, "maxWidth"> & {
24
22
  items: (MediaItem | undefined)[]
25
- layout: ItemStyle
26
- viewLayout?: "grid" | "carousel"
23
+ itemWidth?: "infer" | "narrow" | "wide"
24
+ layout?: "grid" | "carousel"
27
25
  }
28
26
 
29
27
  const {
30
28
  items: itemsInput,
31
- layout: itemStyle,
32
- viewLayout = "grid",
29
+ itemWidth = "infer",
30
+ layout = "carousel",
33
31
  ...sectionProps
34
32
  } = Astro.props
35
33
 
36
- const t = Astro.locals.i18n.t
34
+ const { currentLocale, tMap } = Astro.locals.i18n
37
35
 
38
36
  const types = Object.fromEntries(
39
37
  (await getMediaTypes()).map((type) => [
40
38
  type.id,
41
- { ...type.data, labelText: t(type.data.label) },
39
+ {
40
+ ...type.data,
41
+ labelText: tMap(type.data.label, {
42
+ path: ["media-types", type.id, "label"],
43
+ }),
44
+ },
42
45
  ]),
43
46
  )
44
47
 
@@ -46,14 +49,26 @@ const types = Object.fromEntries(
46
49
  // this is in the return type of getEntry
47
50
  const items = itemsInput.filter((item) => !!item)
48
51
 
49
- const itemWidth = (
50
- {
51
- book: "narrow",
52
- portrait: "narrow",
53
- video: "wide",
54
- landscape: "wide",
55
- } as const
56
- )[itemStyle]
52
+ const { landscapeCount, portraitCount } = items.slice(0, 10).reduce(
53
+ (acc, item) => {
54
+ const { width, height } = item.data.image
55
+ if (width > height) {
56
+ acc.landscapeCount += 1
57
+ }
58
+ if (height > width) {
59
+ acc.portraitCount += 1
60
+ }
61
+ return acc
62
+ },
63
+ { landscapeCount: 0, portraitCount: 0 },
64
+ )
65
+
66
+ const resolvedItemWidth =
67
+ itemWidth === "infer"
68
+ ? landscapeCount > portraitCount
69
+ ? "wide"
70
+ : "narrow"
71
+ : itemWidth
57
72
 
58
73
  const imageSizes = {
59
74
  narrow:
@@ -84,23 +99,68 @@ const gridLayouts = {
84
99
  narrow: "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5",
85
100
  wide: "grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4",
86
101
  } as const
87
-
88
- const coverImageStyle =
89
- (["book", "video"] as const).find((style) => itemStyle === style) ?? "default"
90
102
  ---
91
103
 
92
104
  {
93
- viewLayout === "grid" && (
105
+ layout === "grid" && (
94
106
  <Section {...sectionProps}>
95
107
  <ol
96
108
  class="grid items-end justify-between gap-4 md:gap-8"
97
- class:list={gridLayouts[itemWidth]}
109
+ class:list={gridLayouts[resolvedItemWidth]}
98
110
  >
99
- {items.map((item) => (
100
- <li>
111
+ {items.map((item) => {
112
+ const coverImageStyle = types[item.data.type.id].coverImageStyle
113
+ return (
114
+ <li>
115
+ <a
116
+ href={detailsPagePath(currentLocale, item)}
117
+ class="group flex flex-col gap-3"
118
+ >
119
+ <CoverImageDecorator
120
+ style={coverImageStyle}
121
+ className="outline-2 outline-gray-400 transition-all duration-75 ease-in-out sm:group-hover:outline"
122
+ >
123
+ <Image
124
+ class="h-full w-full object-cover"
125
+ class:list={[
126
+ coverImageStyle === "video" && "absolute top-0",
127
+ ]}
128
+ src={item.data.image}
129
+ alt=""
130
+ widths={[128, 256, 512, 768]}
131
+ sizes={imageSizes[resolvedItemWidth]}
132
+ />
133
+ </CoverImageDecorator>
134
+
135
+ <span class="line-clamp-2 h-12 text-balance text-sm font-bold text-gray-700">
136
+ <Icon
137
+ className={`${types[item.data.type.id].icon} me-2 align-bottom`}
138
+ ariaLabel={types[item.data.type.id].labelText}
139
+ />
140
+ {item.data.title}
141
+ </span>
142
+ </a>
143
+ </li>
144
+ )
145
+ })}
146
+ </ol>
147
+ </Section>
148
+ )
149
+ }
150
+ {
151
+ layout === "carousel" && (
152
+ <CarouselSection {...sectionProps}>
153
+ {items.map((item) => {
154
+ const coverImageStyle = types[item.data.type.id].coverImageStyle
155
+ return (
156
+ <li
157
+ class={`carousel-item--${resolvedItemWidth} h-full`}
158
+ role="group"
159
+ aria-roledescription="slide"
160
+ >
101
161
  <a
102
- href={detailsPagePath(Astro.currentLocale, item)}
103
- class="group flex flex-col gap-3"
162
+ href={detailsPagePath(currentLocale, item)}
163
+ class="group flex h-full flex-col justify-end gap-3"
104
164
  >
105
165
  <CoverImageDecorator
106
166
  style={coverImageStyle}
@@ -108,11 +168,10 @@ const coverImageStyle =
108
168
  >
109
169
  <Image
110
170
  class="h-full w-full object-cover"
111
- class:list={[itemStyle === "video" && "absolute top-0"]}
112
171
  src={item.data.image}
113
172
  alt=""
114
173
  widths={[128, 256, 512, 768]}
115
- sizes={imageSizes[itemWidth]}
174
+ sizes={imageSizes[resolvedItemWidth]}
116
175
  />
117
176
  </CoverImageDecorator>
118
177
 
@@ -125,47 +184,8 @@ const coverImageStyle =
125
184
  </span>
126
185
  </a>
127
186
  </li>
128
- ))}
129
- </ol>
130
- </Section>
131
- )
132
- }
133
- {
134
- viewLayout === "carousel" && (
135
- <CarouselSection {...sectionProps}>
136
- {items.map((item) => (
137
- <li
138
- class={`carousel-item--${itemWidth} h-full`}
139
- role="group"
140
- aria-roledescription="slide"
141
- >
142
- <a
143
- href={detailsPagePath(Astro.currentLocale, item)}
144
- class="group flex h-full flex-col justify-end gap-3"
145
- >
146
- <CoverImageDecorator
147
- style={coverImageStyle}
148
- className="outline-2 outline-gray-400 transition-all duration-75 ease-in-out sm:group-hover:outline"
149
- >
150
- <Image
151
- class="h-full w-full object-cover"
152
- src={item.data.image}
153
- alt=""
154
- widths={[128, 256, 512, 768]}
155
- sizes={imageSizes[itemWidth]}
156
- />
157
- </CoverImageDecorator>
158
-
159
- <span class="line-clamp-2 h-12 text-balance text-sm font-bold text-gray-700">
160
- <Icon
161
- className={`${types[item.data.type.id].icon} me-2 align-bottom`}
162
- ariaLabel={types[item.data.type.id].labelText}
163
- />
164
- {item.data.title}
165
- </span>
166
- </a>
167
- </li>
168
- ))}
187
+ )
188
+ })}
169
189
  </CarouselSection>
170
190
  )
171
191
  }
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  import type { ImageMetadata } from "astro"
3
3
  import { Image } from "astro:assets"
4
+ import { ChevronRightIcon } from "lucide-react"
4
5
 
5
6
  import { getMediaTypes } from "../content/get-media-types"
6
7
  import { detailsPagePath } from "../utils/paths"
@@ -27,19 +28,22 @@ interface Props {
27
28
  // this is in the return type of getEntry
28
29
  const items = Astro.props.items.filter((item) => !!item)
29
30
 
30
- const { t, direction } = Astro.locals.i18n
31
+ const { direction, currentLocale, tMap } = Astro.locals.i18n
31
32
 
32
33
  const mediaTypes = Object.fromEntries(
33
34
  (await getMediaTypes()).map((type) => [
34
35
  type.id,
35
36
  {
36
37
  id: type.id,
37
- labelText: t(type.data.label),
38
+ labelText: tMap(type.data.label, {
39
+ path: ["media-types", type.id, "label"],
40
+ }),
38
41
  icon: type.data.icon,
39
42
  coverImageStyle: type.data.coverImageStyle,
40
43
  },
41
44
  ]),
42
45
  )
46
+ const iconDirectionClass = direction === "rtl" ? "scale-x-[-1]" : ""
43
47
  ---
44
48
 
45
49
  <ol class="divide-y divide-gray-200">
@@ -50,7 +54,7 @@ const mediaTypes = Object.fromEntries(
50
54
  href={
51
55
  item.disabled
52
56
  ? "javascript:void(0)"
53
- : detailsPagePath(Astro.currentLocale, {
57
+ : detailsPagePath(currentLocale, {
54
58
  id: item.id,
55
59
  })
56
60
  }
@@ -97,10 +101,8 @@ const mediaTypes = Object.fromEntries(
97
101
  </div>
98
102
  </div>
99
103
 
100
- <Icon
101
- className="mdi--chevron-right hidden sm:block md:group-hover:text-primary my-auto ms-2 me-4 shrink-0 text-2xl text-gray-300"
102
- flipIcon={direction === "rtl"}
103
- ariaLabel=""
104
+ <ChevronRightIcon
105
+ className={`my-auto me-4 ms-2 hidden shrink-0 text-gray-300 sm:block md:group-hover:text-primary ${iconDirectionClass}`}
104
106
  />
105
107
  </a>
106
108
  </li>
@@ -1,15 +1,17 @@
1
1
  ---
2
- import Icon from "./Icon"
2
+ import { SearchIcon } from "lucide-react"
3
+
4
+ import { searchPagePath } from "../utils/paths"
3
5
 
4
6
  type Props = {
5
7
  className?: string
6
8
  }
7
9
 
8
- const { t } = Astro.locals.i18n
10
+ const { t, currentLocale } = Astro.locals.i18n
9
11
  ---
10
12
 
11
13
  <form
12
- action={`/${Astro.currentLocale}/media`}
14
+ action={searchPagePath(currentLocale)}
13
15
  method="get"
14
16
  role="search"
15
17
  class="group flex w-full rounded-2xl shadow-sm outline outline-2 outline-offset-2 outline-transparent transition-all ease-in-out group-focus-within:outline-gray-400"
@@ -24,8 +26,9 @@ const { t } = Astro.locals.i18n
24
26
  />
25
27
  <button
26
28
  type="submit"
29
+ aria-label={t("ln.search.title")}
27
30
  class="flex items-center rounded-e-2xl border border-gray-100/95 bg-gray-800 px-4 py-3 text-gray-50 hover:bg-gray-950 hover:text-gray-300"
28
31
  >
29
- <Icon className="mdi--magnify" ariaLabel={t("ln.search.title")} />
32
+ <SearchIcon />
30
33
  </button>
31
34
  </form>
@@ -1,5 +1,5 @@
1
1
  ---
2
- import Icon from "./Icon"
2
+ import { ChevronRightIcon } from "lucide-react"
3
3
 
4
4
  export interface Props {
5
5
  /**
@@ -64,6 +64,8 @@ const marginTopValues = {
64
64
  sm: "mt-16 md:mt-20",
65
65
  lg: "mt-24 md:mt-28",
66
66
  }
67
+ const iconDirectionClass =
68
+ Astro.locals.i18n.direction === "rtl" ? "scale-x-[-1]" : ""
67
69
  ---
68
70
 
69
71
  <section
@@ -81,10 +83,10 @@ const marginTopValues = {
81
83
  {titleHref ? (
82
84
  <a href={titleHref} class="group flex items-center">
83
85
  {title}
84
- <Icon
85
- className="mdi--chevron-right group-hover:text-primary transition-colors ease-in-out duration-75 text-4xl sm:text-5xl -ms-2"
86
- ariaLabel=""
87
- flipIcon={Astro.locals.i18n.direction === "rtl"}
86
+ <ChevronRightIcon
87
+ size="2rem"
88
+ strokeWidth="3px"
89
+ className={`transition-colors duration-75 ease-in-out group-hover:text-primary ${iconDirectionClass}`}
88
90
  />
89
91
  </a>
90
92
  ) : (
@@ -2,8 +2,7 @@
2
2
  import type { ImageMetadata } from "astro"
3
3
  import { getImage } from "astro:assets"
4
4
  import { Image } from "astro:assets"
5
-
6
- import Icon from "./Icon"
5
+ import { ExternalLinkIcon } from "lucide-react"
7
6
 
8
7
  interface Props {
9
8
  /**
@@ -146,7 +145,7 @@ async function parseUrl(
146
145
  </span>
147
146
  </div>
148
147
  <div class="absolute bottom-0 end-0 start-0 z-20 flex items-center gap-2 rounded bg-black/70 px-4 py-4 text-sm font-medium text-white backdrop-blur md:text-sm">
149
- <Icon className="mdi--external-link" ariaLabel="" />
148
+ <ExternalLinkIcon className="shrink-0" />
150
149
  {t("ln.external-link")}
151
150
  </div>
152
151
  </a>