ThemeSettings.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import { LOCAL_STORAGE_KEYS } from 'common'
  2. import { useTheme } from 'next-themes'
  3. import { useEffect, useState } from 'react'
  4. import SVG from 'react-inlinesvg'
  5. import {
  6. Card,
  7. CardContent,
  8. Label,
  9. RadioGroup,
  10. RadioGroupLargeItem,
  11. Select,
  12. SelectContent,
  13. SelectItem,
  14. SelectTrigger,
  15. SelectValue,
  16. Separator,
  17. singleThemes,
  18. Theme,
  19. } from 'ui'
  20. import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
  21. import {
  22. PageSection,
  23. PageSectionContent,
  24. PageSectionDescription,
  25. PageSectionMeta,
  26. PageSectionSummary,
  27. PageSectionTitle,
  28. } from 'ui-patterns/PageSection'
  29. import { DEFAULT_SIDEBAR_BEHAVIOR } from '@/components/interfaces/Sidebar'
  30. import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
  31. import { BASE_PATH } from '@/lib/constants'
  32. export const ThemeSettings = () => {
  33. const [mounted, setMounted] = useState(false)
  34. const { theme, setTheme } = useTheme()
  35. const [sidebarBehaviour, setSidebarBehaviour] = useLocalStorageQuery(
  36. LOCAL_STORAGE_KEYS.SIDEBAR_BEHAVIOR,
  37. DEFAULT_SIDEBAR_BEHAVIOR
  38. )
  39. /**
  40. * Avoid Hydration Mismatch
  41. * https://github.com/pacocoursey/next-themes?tab=readme-ov-file#avoid-hydration-mismatch
  42. */
  43. // useEffect only runs on the client, so now we can safely show the UI
  44. useEffect(() => setMounted(true), [])
  45. if (!mounted) return null
  46. function SingleThemeSelection() {
  47. return (
  48. <RadioGroup
  49. name="theme"
  50. onValueChange={setTheme}
  51. aria-label="Choose a theme"
  52. defaultValue={theme}
  53. value={theme}
  54. className="grid grid-cols-2 gap-4"
  55. >
  56. {singleThemes.map((theme: Theme) => (
  57. <RadioGroupLargeItem
  58. className="p-3 w-full"
  59. key={theme.value}
  60. value={theme.value}
  61. label={theme.name}
  62. >
  63. <SVG src={`${BASE_PATH}/img/themes/${theme.value}.svg?v=2`} />
  64. </RadioGroupLargeItem>
  65. ))}
  66. </RadioGroup>
  67. )
  68. }
  69. return (
  70. <PageSection>
  71. <PageSectionMeta>
  72. <PageSectionSummary>
  73. <PageSectionTitle>Appearance</PageSectionTitle>
  74. <PageSectionDescription>
  75. Choose how Briven looks and behaves in the dashboard.
  76. </PageSectionDescription>
  77. </PageSectionSummary>
  78. </PageSectionMeta>
  79. <PageSectionContent>
  80. <Card>
  81. <CardContent className="grid grid-cols-12 gap-6">
  82. <div className="col-span-full md:col-span-4 flex flex-col gap-2">
  83. <Label htmlFor="theme" className="text-foreground">
  84. Theme mode
  85. </Label>
  86. <p className="text-sm text-foreground-light">
  87. Choose how Briven looks to you. Select a single theme, or sync with your system.
  88. </p>
  89. </div>
  90. <div className="col-span-full md:col-span-8 flex flex-col gap-4">
  91. <SingleThemeSelection />
  92. </div>
  93. </CardContent>
  94. <Separator />
  95. <CardContent>
  96. <FormItemLayout
  97. isReactForm={false}
  98. label="Sidebar behavior"
  99. layout="flex-row-reverse"
  100. description="Choose your preferred sidebar behavior: open, closed, or expand on hover."
  101. >
  102. <Select
  103. value={sidebarBehaviour}
  104. onValueChange={setSidebarBehaviour}
  105. aria-label="Select an option"
  106. >
  107. <SelectTrigger>
  108. <SelectValue placeholder="Choose an option" />
  109. </SelectTrigger>
  110. <SelectContent>
  111. <SelectItem value="open">Expanded</SelectItem>
  112. <SelectItem value="closed">Collapsed</SelectItem>
  113. <SelectItem value="expandable">Expand on hover</SelectItem>
  114. </SelectContent>
  115. </Select>
  116. </FormItemLayout>
  117. </CardContent>
  118. </Card>
  119. </PageSectionContent>
  120. </PageSection>
  121. )
  122. }