create-nextjs-cms 0.7.7 → 0.7.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nextjs-cms",
3
- "version": "0.7.7",
3
+ "version": "0.7.9",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,14 +16,14 @@ export default function DashboardPage() {
16
16
  src='/examples/dashboard-light.png'
17
17
  width={1280}
18
18
  height={866}
19
- alt='Dashboard'
19
+ alt={t('dashboard') as string}
20
20
  className='block dark:hidden'
21
21
  />
22
22
  <Image
23
23
  src='/examples/dashboard-dark.png'
24
24
  width={1280}
25
25
  height={866}
26
- alt='Dashboard'
26
+ alt={t('dashboard') as string}
27
27
  className='hidden dark:block'
28
28
  />
29
29
  </div>
@@ -39,7 +39,7 @@ export default function LogPage() {
39
39
  if (isLoading) {
40
40
  return (
41
41
  <div className='flex w-full items-center justify-center p-8'>
42
- <Spinner className='size-8' />
42
+ <Spinner className='size-8' aria-label={t('loading') as string} />
43
43
  </div>
44
44
  )
45
45
  }
@@ -96,7 +96,7 @@ export default function Navbar(props: Props) {
96
96
  className='border-foreground text-foreground hover:text-foreground/90 relative inline-flex items-center justify-center rounded-md border p-2 focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800 focus:outline-hidden'
97
97
  >
98
98
  <span className='absolute -inset-0.5' />
99
- <span className='sr-only'>Open main menu</span>
99
+ <span className='sr-only'>{t('openMainMenu')}</span>
100
100
  <HamburgerMenuIcon className='block h-6 w-6' aria-hidden='true' />
101
101
  </button>
102
102
  </div>
@@ -123,7 +123,7 @@ export default function Navbar(props: Props) {
123
123
  <button
124
124
  type='button'
125
125
  className='relative flex h-10 items-center justify-center rounded-full focus:outline-hidden'
126
- aria-label='Notifications'
126
+ aria-label={t('notifications') as string}
127
127
  >
128
128
  <span className='absolute -inset-1.5' />
129
129
  <BellIcon className='h-6 w-6' />
@@ -198,7 +198,7 @@ export default function Navbar(props: Props) {
198
198
  className='relative flex h-10 items-center justify-center rounded-full'
199
199
  >
200
200
  <span className='absolute -inset-1.5' />
201
- <span className='sr-only'>Open user menu</span>
201
+ <span className='sr-only'>{t('openUserMenu')}</span>
202
202
  {session?.data?.user.image ? (
203
203
  <ProtectedImage
204
204
  section={'admins'}
@@ -215,7 +215,7 @@ export default function Navbar(props: Props) {
215
215
  src='/blank_avatar.png'
216
216
  height={40}
217
217
  width={40}
218
- alt='profile image'
218
+ alt={t('profileImage') as string}
219
219
  className='rounded-full ring-2 ring-amber-300 ring-inset'
220
220
  />
221
221
  )}
@@ -7,6 +7,7 @@ import { Button } from '@/components/ui/button'
7
7
  import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command'
8
8
  import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
9
9
  import { SelectOption } from 'nextjs-cms/core/fields'
10
+ import { useI18n } from 'nextjs-cms/translations/client'
10
11
 
11
12
  export default function SelectBox({
12
13
  items,
@@ -19,8 +20,7 @@ export default function SelectBox({
19
20
  onChange: any
20
21
  classname: string
21
22
  }) {
22
- // Set the selected item,
23
- // check for defaultValue and set it if it exists,
23
+ const t = useI18n()
24
24
  const [selected, setSelected] = useState<SelectOption | undefined>(
25
25
  defaultValue !== undefined && defaultValue !== null
26
26
  ? items.find((item) => item.value?.toString() === defaultValue.toString())
@@ -61,9 +61,9 @@ export default function SelectBox({
61
61
  align='start'
62
62
  >
63
63
  <Command>
64
- <CommandInput placeholder='Search...' className='h-9' />
64
+ <CommandInput placeholder={t('searchDots') as string} className='h-9' />
65
65
  <CommandList>
66
- <CommandEmpty>No item found.</CommandEmpty>
66
+ <CommandEmpty>{t('noItemFound')}</CommandEmpty>
67
67
  <CommandGroup>
68
68
  {items.map((item: SelectOption, index: number) => {
69
69
  // Use label for searchable value, but include value in a way that we can identify it
@@ -60,7 +60,7 @@ const Sidebar = (props: SidebarProps & { logoUrlPath: string; logoText: string;
60
60
  'justify-start gap-2 p-4': true,
61
61
  })}
62
62
  >
63
- <Image src={props.logoUrlPath} alt='logo' width={32} height={32} />
63
+ <Image src={props.logoUrlPath} alt={t('logo') as string} width={32} height={32} />
64
64
  <span className='bg-transparent px-2 font-normal tracking-[0.1em] text-white/90 transition-all duration-150'>
65
65
  {props.logoText}
66
66
  </span>
@@ -181,7 +181,7 @@ const Sidebar = (props: SidebarProps & { logoUrlPath: string; logoText: string;
181
181
  src='/blank_avatar.png'
182
182
  height={36}
183
183
  width={36}
184
- alt='profile image'
184
+ alt={t('profileImage') as string}
185
185
  className='rounded-full ring-2 ring-amber-300 ring-inset'
186
186
  />
187
187
  )}
@@ -95,7 +95,13 @@ export default function FormInputs({ inputs, sectionName }: { inputs: FieldClien
95
95
  case 'password':
96
96
  return <PasswordFormInput input={input as PasswordFieldClientConfig} key={input.name} />
97
97
  case 'tags':
98
- return <TagsFormInput input={input as TagsFieldClientConfig} key={input.name} />
98
+ return (
99
+ <TagsFormInput
100
+ input={input as TagsFieldClientConfig}
101
+ sectionName={sectionName}
102
+ key={input.name}
103
+ />
104
+ )
99
105
  case 'map':
100
106
  return <MapFormInput input={input as MapFieldClientConfig} key={input.name} />
101
107
  case 'checkbox':
@@ -3,8 +3,10 @@ import { useEffect, useState } from 'react'
3
3
  import { SelectMultipleFieldClientConfig, SelectOption } from 'nextjs-cms/core/fields'
4
4
  import { useController, useFormContext } from 'react-hook-form'
5
5
  import { MultiSelect, MultiSelectOption } from '@/components/multi-select'
6
+ import { useI18n } from 'nextjs-cms/translations/client'
6
7
 
7
8
  export default function MultipleSelectFormInput({ input }: { input: SelectMultipleFieldClientConfig }) {
9
+ const t = useI18n()
8
10
  const { control } = useFormContext()
9
11
  const {
10
12
  field,
@@ -74,7 +76,7 @@ export default function MultipleSelectFormInput({ input }: { input: SelectMultip
74
76
  options={multiSelectOptions}
75
77
  onValueChange={handleValueChange}
76
78
  defaultValue={currentValue}
77
- // placeholder={input.label || 'Select options'}
79
+ placeholder={t('selectOptions') as string}
78
80
  disabled={field.disabled}
79
81
  className='w-full py-3 ring-2 ring-gray-300'
80
82
  />
@@ -126,7 +126,7 @@ export default function PhotoFormInput({ input, sectionName }: { input: PhotoFie
126
126
  <Image
127
127
  className='mb-4 rounded p-1 ring-3 ring-green-600'
128
128
  src={image}
129
- alt='new'
129
+ alt={t('newImage') as string}
130
130
  fill={true}
131
131
  style={{
132
132
  objectFit: 'contain',
@@ -6,6 +6,7 @@ import type { SlugFieldClientConfig } from 'nextjs-cms/core/fields'
6
6
  import { useFormContext, useController, useWatch } from 'react-hook-form'
7
7
  import { InputGroup, InputGroupInput, InputGroupAddon } from '@/components/ui/input-group'
8
8
  import { Link2 } from 'lucide-react'
9
+ import { useI18n } from 'nextjs-cms/translations/client'
9
10
 
10
11
  /**
11
12
  * Convert a string to a URL-friendly slug (for real-time input).
@@ -41,6 +42,7 @@ export default function SlugFormInput({
41
42
  direction?: 'row' | 'col'
42
43
  disabled?: boolean
43
44
  }) {
45
+ const t = useI18n()
44
46
  const { control } = useFormContext()
45
47
  const {
46
48
  field,
@@ -109,7 +111,7 @@ export default function SlugFormInput({
109
111
  required={input.required}
110
112
  >
111
113
  <InputGroup className='bg-input'>
112
- <InputGroupAddon align='inline-start' title='Auto-generated from linked field'>
114
+ <InputGroupAddon align='inline-start' title={t('autoGeneratedFromLinkedField') as string}>
113
115
  <Link2 className='h-4 w-4' />
114
116
  </InputGroupAddon>
115
117
  <InputGroupInput
@@ -5,10 +5,17 @@ import { useController, useFormContext } from 'react-hook-form'
5
5
  import { Badge } from '@/components/ui/badge'
6
6
  import { Input } from '@/components/ui/input'
7
7
  import { X } from 'lucide-react'
8
- import { useState, useRef } from 'react'
8
+ import { useState, useRef, useEffect, useCallback } from 'react'
9
9
  import { cn } from '@/lib/utils'
10
+ import { trpc } from '@/app/_trpc/client'
10
11
 
11
- export default function TagsFormInput({ input }: { input: TagsFieldClientConfig }) {
12
+ export default function TagsFormInput({
13
+ input,
14
+ sectionName,
15
+ }: {
16
+ input: TagsFieldClientConfig
17
+ sectionName: string
18
+ }) {
12
19
  const t = useI18n()
13
20
  const { control } = useFormContext()
14
21
  const {
@@ -22,13 +29,40 @@ export default function TagsFormInput({ input }: { input: TagsFieldClientConfig
22
29
 
23
30
  const [inputValue, setInputValue] = useState('')
24
31
  const [highlightedTag, setHighlightedTag] = useState<string | null>(null)
32
+ const [highlightedIndex, setHighlightedIndex] = useState(-1)
33
+ const [showDropdown, setShowDropdown] = useState(false)
25
34
  const inputRef = useRef<HTMLInputElement>(null)
35
+ const dropdownRef = useRef<HTMLDivElement>(null)
36
+ const [debouncedQuery, setDebouncedQuery] = useState('')
37
+
38
+ // Debounce the query input
39
+ useEffect(() => {
40
+ if (!input.hasAutoCompletion) return
41
+ const timer = setTimeout(() => {
42
+ setDebouncedQuery(inputValue)
43
+ }, 300)
44
+ return () => clearTimeout(timer)
45
+ }, [inputValue, input.hasAutoCompletion])
46
+
47
+ const { data: suggestions } = trpc.fields.tagsAutoComplete.useQuery(
48
+ { sectionName, fieldName: input.name, query: debouncedQuery },
49
+ { enabled: input.hasAutoCompletion && debouncedQuery.length >= 1 },
50
+ )
26
51
 
27
52
  // Parse tags from comma-separated string
28
- const getTags = (): string[] => {
53
+ const getTags = useCallback((): string[] => {
29
54
  if (!field.value || typeof field.value !== 'string') return []
30
55
  return field.value.split(',').filter((tag) => tag.trim() !== '')
31
- }
56
+ }, [field.value])
57
+
58
+ // Filter out already-added tags from suggestions
59
+ const filteredSuggestions = suggestions?.filter((s) => !getTags().includes(s)) ?? []
60
+
61
+ // Show/hide dropdown based on suggestions
62
+ useEffect(() => {
63
+ setShowDropdown(filteredSuggestions.length > 0 && inputValue.length >= 1)
64
+ setHighlightedIndex(-1)
65
+ }, [filteredSuggestions.length, inputValue.length])
32
66
 
33
67
  // Convert tags array to comma-separated string
34
68
  const setTags = (tags: string[]) => {
@@ -49,6 +83,7 @@ export default function TagsFormInput({ input }: { input: TagsFieldClientConfig
49
83
  const newTags = [...existingTags, trimmedTag]
50
84
  setTags(newTags)
51
85
  setInputValue('')
86
+ setShowDropdown(false)
52
87
  }
53
88
  }
54
89
  }
@@ -59,6 +94,34 @@ export default function TagsFormInput({ input }: { input: TagsFieldClientConfig
59
94
  }
60
95
 
61
96
  const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
97
+ if (showDropdown && filteredSuggestions.length > 0) {
98
+ if (event.key === 'ArrowDown') {
99
+ event.preventDefault()
100
+ setHighlightedIndex((prev) =>
101
+ prev < filteredSuggestions.length - 1 ? prev + 1 : 0,
102
+ )
103
+ return
104
+ }
105
+ if (event.key === 'ArrowUp') {
106
+ event.preventDefault()
107
+ setHighlightedIndex((prev) =>
108
+ prev > 0 ? prev - 1 : filteredSuggestions.length - 1,
109
+ )
110
+ return
111
+ }
112
+ if (event.key === 'Enter' && highlightedIndex >= 0) {
113
+ event.preventDefault()
114
+ addTag(filteredSuggestions[highlightedIndex])
115
+ return
116
+ }
117
+ if (event.key === 'Escape') {
118
+ event.preventDefault()
119
+ setShowDropdown(false)
120
+ setHighlightedIndex(-1)
121
+ return
122
+ }
123
+ }
124
+
62
125
  if (event.key === 'Enter' || event.key === ',') {
63
126
  event.preventDefault()
64
127
  if (inputValue.trim()) {
@@ -81,6 +144,14 @@ export default function TagsFormInput({ input }: { input: TagsFieldClientConfig
81
144
  inputRef.current?.focus()
82
145
  }
83
146
 
147
+ const handleBlur = () => {
148
+ // Small delay to allow click events on dropdown items to fire first
149
+ setTimeout(() => {
150
+ setShowDropdown(false)
151
+ setHighlightedIndex(-1)
152
+ }, 200)
153
+ }
154
+
84
155
  const tags = getTags()
85
156
 
86
157
  return (
@@ -101,53 +172,88 @@ export default function TagsFormInput({ input }: { input: TagsFieldClientConfig
101
172
  ref={field.ref}
102
173
  />
103
174
 
104
- <div
105
- className={cn(
106
- 'bg-input text-foreground w-full rounded px-3 py-1 shadow-xs ring-2 ring-gray-300 outline-0 transition-colors hover:ring-gray-400 focus-within:ring-blue-500',
107
- 'flex min-h-9 flex-wrap items-center gap-1 text-sm',
108
- 'cursor-text',
109
- field.disabled && 'cursor-not-allowed opacity-50',
110
- )}
111
- onClick={handleContainerClick}
112
- >
113
- {tags.map((tag, index) => (
114
- <Badge
115
- key={`${tag}-${index}`}
116
- variant='secondary'
117
- className={cn(
118
- 'flex items-center gap-1 px-2 py-1 text-xs transition-all duration-200 border-foreground/40',
119
- highlightedTag === tag ? 'bg-success/20 border-success/50 animate-pulse' : '',
120
- )}
175
+ <div className='relative'>
176
+ <div
177
+ className={cn(
178
+ 'bg-input text-foreground w-full rounded px-3 py-1 shadow-xs ring-2 ring-gray-300 outline-0 transition-colors hover:ring-gray-400 focus-within:ring-blue-500',
179
+ 'flex min-h-9 flex-wrap items-center gap-1 text-sm',
180
+ 'cursor-text',
181
+ field.disabled && 'cursor-not-allowed opacity-50',
182
+ )}
183
+ onClick={handleContainerClick}
184
+ >
185
+ {tags.map((tag, index) => (
186
+ <Badge
187
+ key={`${tag}-${index}`}
188
+ variant='secondary'
189
+ className={cn(
190
+ 'flex items-center gap-1 px-2 py-1 text-xs transition-all duration-200 border-foreground/40',
191
+ highlightedTag === tag ? 'bg-success/20 border-success/50 animate-pulse' : '',
192
+ )}
193
+ >
194
+ <span>{tag}</span>
195
+ {!field.disabled ? (
196
+ <button
197
+ type='button'
198
+ onClick={(e) => {
199
+ e.stopPropagation()
200
+ removeTag(tag)
201
+ }}
202
+ className='hover:bg-muted-foreground/20 focus:ring-ring ml-1 rounded-sm focus:ring-1 focus:outline-none'
203
+ aria-label={`Remove ${tag} tag`}
204
+ >
205
+ <X className='h-3 w-3' />
206
+ </button>
207
+ ) : null}
208
+ </Badge>
209
+ ))}
210
+
211
+ <Input
212
+ ref={inputRef}
213
+ type='text'
214
+ value={inputValue}
215
+ onChange={handleInputChange}
216
+ onKeyDown={handleKeyDown}
217
+ onBlur={handleBlur}
218
+ placeholder={
219
+ tags.length === 0
220
+ ? input.placeholder
221
+ ? input.placeholder
222
+ : input.hasAutoCompletion
223
+ ? t('startTypingForSuggestions')
224
+ : t('startTyping')
225
+ : ''
226
+ }
227
+ className='min-w-20 flex-1 border-0 p-0 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0'
228
+ disabled={field.disabled}
229
+ />
230
+ </div>
231
+
232
+ {showDropdown && filteredSuggestions.length > 0 && (
233
+ <div
234
+ ref={dropdownRef}
235
+ className='bg-popover border-border absolute z-50 mt-1 max-h-48 w-full overflow-auto rounded-md border shadow-md'
121
236
  >
122
- <span>{tag}</span>
123
- {!field.disabled ? (
237
+ {filteredSuggestions.map((suggestion, index) => (
124
238
  <button
239
+ key={suggestion}
125
240
  type='button'
126
- onClick={(e) => {
127
- e.stopPropagation()
128
- removeTag(tag)
241
+ className={cn(
242
+ 'text-popover-foreground w-full px-3 py-2 text-left text-sm',
243
+ 'hover:bg-accent hover:text-accent-foreground',
244
+ highlightedIndex === index && 'bg-accent text-accent-foreground',
245
+ )}
246
+ onMouseDown={(e) => {
247
+ e.preventDefault()
248
+ addTag(suggestion)
129
249
  }}
130
- className='hover:bg-muted-foreground/20 focus:ring-ring ml-1 rounded-sm focus:ring-1 focus:outline-none'
131
- aria-label={`Remove ${tag} tag`}
250
+ onMouseEnter={() => setHighlightedIndex(index)}
132
251
  >
133
- <X className='h-3 w-3' />
252
+ {suggestion}
134
253
  </button>
135
- ) : null}
136
- </Badge>
137
- ))}
138
-
139
- <Input
140
- ref={inputRef}
141
- type='text'
142
- value={inputValue}
143
- onChange={handleInputChange}
144
- onKeyDown={handleKeyDown}
145
- placeholder={
146
- tags.length === 0 ? (input.placeholder ? input.placeholder : t('startTyping')) : ''
147
- }
148
- className='min-w-20 flex-1 border-0 p-0 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0'
149
- disabled={field.disabled}
150
- />
254
+ ))}
255
+ </div>
256
+ )}
151
257
  </div>
152
258
  </FormInputElement>
153
259
  )
@@ -3,6 +3,7 @@ import { cva, type VariantProps } from 'class-variance-authority'
3
3
  import { CheckIcon, XCircle, ChevronDown, XIcon, WandSparkles } from 'lucide-react'
4
4
 
5
5
  import { cn } from '@/lib/utils'
6
+ import { useI18n } from 'nextjs-cms/translations/client'
6
7
  import { Separator } from '@/components/ui/separator'
7
8
  import { Button } from '@/components/ui/button'
8
9
  import { Badge } from '@/components/ui/badge'
@@ -329,6 +330,7 @@ export const MultiSelect = React.forwardRef<MultiSelectRef, MultiSelectProps>(
329
330
  },
330
331
  ref,
331
332
  ) => {
333
+ const t = useI18n()
332
334
  const [selectedValues, setSelectedValues] = React.useState<string[]>(defaultValue)
333
335
  const [isPopoverOpen, setIsPopoverOpen] = React.useState(false)
334
336
  const [isAnimating, setIsAnimating] = React.useState(false)
@@ -911,7 +913,7 @@ export const MultiSelect = React.forwardRef<MultiSelectRef, MultiSelectProps>(
911
913
  handleClear()
912
914
  }
913
915
  }}
914
- aria-label={`Clear all ${selectedValues.length} selected options`}
916
+ aria-label={t('clearAllSelectedOptions', { count: String(selectedValues.length) }) as string}
915
917
  className='text-muted-foreground hover:text-foreground focus:ring-ring mx-2 flex h-4 w-4 cursor-pointer items-center justify-center rounded-sm focus:ring-2 focus:ring-offset-1 focus:outline-none'
916
918
  >
917
919
  <XIcon className='h-4 w-4' />
@@ -935,7 +937,7 @@ export const MultiSelect = React.forwardRef<MultiSelectRef, MultiSelectProps>(
935
937
  id={listboxId}
936
938
  role='listbox'
937
939
  aria-multiselectable='true'
938
- aria-label='Available options'
940
+ aria-label={t('availableOptions') as string}
939
941
  className={cn(
940
942
  'w-auto p-0',
941
943
  getPopoverAnimationClass(),
@@ -957,17 +959,17 @@ export const MultiSelect = React.forwardRef<MultiSelectRef, MultiSelectProps>(
957
959
  <Command>
958
960
  {searchable && (
959
961
  <CommandInput
960
- placeholder='Search options...'
962
+ placeholder={t('searchOptionsDots') as string}
961
963
  onKeyDown={handleInputKeyDown}
962
964
  value={searchValue}
963
965
  onValueChange={setSearchValue}
964
- aria-label='Search through available options'
966
+ aria-label={t('searchThroughOptions') as string}
965
967
  aria-describedby={`${multiSelectId}-search-help`}
966
968
  />
967
969
  )}
968
970
  {searchable && (
969
971
  <div id={`${multiSelectId}-search-help`} className='sr-only'>
970
- Type to filter options. Use arrow keys to navigate results.
972
+ {t('filterOptionsHint')}
971
973
  </div>
972
974
  )}
973
975
  <CommandList
@@ -1109,7 +1111,7 @@ export const MultiSelect = React.forwardRef<MultiSelectRef, MultiSelectProps>(
1109
1111
  onSelect={handleClear}
1110
1112
  className='flex-1 cursor-pointer justify-center'
1111
1113
  >
1112
- Clear
1114
+ {t('clear')}
1113
1115
  </CommandItem>
1114
1116
  <Separator orientation='vertical' className='flex h-full min-h-6' />
1115
1117
  </>
@@ -1118,7 +1120,7 @@ export const MultiSelect = React.forwardRef<MultiSelectRef, MultiSelectProps>(
1118
1120
  onSelect={() => setIsPopoverOpen(false)}
1119
1121
  className='max-w-full flex-1 cursor-pointer justify-center'
1120
1122
  >
1121
- Close
1123
+ {t('close')}
1122
1124
  </CommandItem>
1123
1125
  </div>
1124
1126
  </CommandGroup>
@@ -2,8 +2,10 @@ import { useState, useEffect } from 'react'
2
2
  import { useTheme } from 'next-themes'
3
3
  import { MoonIcon, SunIcon } from '@radix-ui/react-icons'
4
4
  import { Spinner } from '@/components/ui/spinner'
5
+ import { useI18n } from 'nextjs-cms/translations/client'
5
6
 
6
7
  const ThemeToggle = () => {
8
+ const t = useI18n()
7
9
  const [mounted, setMounted] = useState(false)
8
10
  const { theme, setTheme } = useTheme()
9
11
 
@@ -12,7 +14,7 @@ const ThemeToggle = () => {
12
14
  }, [])
13
15
 
14
16
  if (!mounted) {
15
- return <Spinner className='size-6' />
17
+ return <Spinner className='size-6' aria-label={t('loading') as string} />
16
18
  }
17
19
 
18
20
  return (
@@ -24,7 +26,7 @@ const ThemeToggle = () => {
24
26
  className='text-foreground hover:text-foreground/90 relative flex h-10 items-center justify-center rounded-full focus:outline-hidden cursor-pointer'
25
27
  >
26
28
  <span className='absolute -inset-1.5' />
27
- <span className='sr-only'>Theme</span>
29
+ <span className='sr-only'>{t('theme')}</span>
28
30
  {theme === 'dark' ? (
29
31
  <SunIcon className='h-6 w-6' aria-hidden='true' />
30
32
  ) : (
@@ -66,7 +66,7 @@
66
66
  "nanoid": "^5.1.2",
67
67
  "next": "16.1.1",
68
68
  "next-themes": "^0.4.6",
69
- "nextjs-cms": "0.7.7",
69
+ "nextjs-cms": "0.7.9",
70
70
  "plaiceholder": "^3.0.0",
71
71
  "prettier-plugin-tailwindcss": "^0.7.2",
72
72
  "qrcode": "^1.5.4",