tw-react-components 0.0.139 → 0.0.140

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/index.cjs.js CHANGED
@@ -123,6 +123,19 @@ function getDisplayDate(date, format, locale) {
123
123
  return (locale ? dayjs(date).locale(locale) : dayjs(date)).format(format);
124
124
  }
125
125
 
126
+ function getValueFromCookie(key, _default) {
127
+ var _a, _b;
128
+ const transformers = {
129
+ string: String,
130
+ boolean: (value) => value === 'true',
131
+ };
132
+ return typeof window !== 'undefined'
133
+ ? transformers[typeof _default]((_b = (_a = document.cookie
134
+ .split('; ')
135
+ .find((row) => row.startsWith(key))) === null || _a === void 0 ? void 0 : _a.split('=')[1]) !== null && _b !== void 0 ? _b : _default)
136
+ : _default;
137
+ }
138
+
126
139
  function isEmpty(s) {
127
140
  if (Array.isArray(s))
128
141
  return s.length === 0;
@@ -586,10 +599,10 @@ const sizeClassNames = {
586
599
  },
587
600
  };
588
601
  const Button = react.forwardRef((_a, ref) => {
589
- var { children, className, size = 'medium', color = 'slate', variant = 'filled', rounded, prefixIcon: PrefixIcon, suffixIcon: SuffixIcon } = _a, props = __rest(_a, ["children", "className", "size", "color", "variant", "rounded", "prefixIcon", "suffixIcon"]);
602
+ var { children, className, size = 'medium', color = 'slate', variant = 'filled', rounded, prefixIcon: PrefixIcon, suffixIcon: SuffixIcon, unstyled } = _a, props = __rest(_a, ["children", "className", "size", "color", "variant", "rounded", "prefixIcon", "suffixIcon", "unstyled"]);
590
603
  return (jsxRuntime.jsxs("button", Object.assign({ className: cn('relative flex aspect-square items-center font-medium duration-200', sizeClassNames[size].base, variantClassNames[variant][color].base, rounded ? 'rounded-full' : 'rounded-md', props.disabled
591
604
  ? 'cursor-not-allowed opacity-50'
592
- : props.onClick
605
+ : !unstyled
593
606
  ? `${variantClassNames[variant][color].hover} ${variantClassNames[variant][color].focus} ${variantClassNames[variant][color].active}`
594
607
  : 'cursor-default', children ? `${sizeClassNames[size].withChildren} aspect-[initial]` : 'justify-center', className), type: "button" }, props, { ref: ref, children: [PrefixIcon && (jsxRuntime.jsx(PrefixIcon, { className: children ? sizeClassNames[size].icon.withChildren : sizeClassNames[size].icon.base })), children, SuffixIcon && (jsxRuntime.jsx(SuffixIcon, { className: children ? sizeClassNames[size].icon.withChildren : sizeClassNames[size].icon.base }))] })));
595
608
  });
@@ -1794,15 +1807,16 @@ const SidebarContextProvider = react.forwardRef((_a, ref) => {
1794
1807
  const screenRef = react.useRef(document.documentElement);
1795
1808
  // This is the internal state of the sidebar.
1796
1809
  // We use openProp and setOpenProp for control from outside the component.
1797
- const [_open, _setOpen] = react.useState(defaultOpen);
1810
+ const [_open, _setOpen] = react.useState(getValueFromCookie(SIDEBAR_COOKIE_NAME, defaultOpen));
1798
1811
  const open = openProp !== null && openProp !== void 0 ? openProp : _open;
1799
1812
  const setOpen = react.useCallback((value) => {
1800
1813
  if (setOpenProp) {
1801
1814
  return setOpenProp === null || setOpenProp === void 0 ? void 0 : setOpenProp(typeof value === 'function' ? value(open) : value);
1802
1815
  }
1803
- _setOpen(value);
1816
+ const newValue = typeof value === 'function' ? value(open) : value;
1817
+ _setOpen(newValue);
1804
1818
  // This sets the cookie to keep the sidebar state.
1805
- document.cookie = `${SIDEBAR_COOKIE_NAME}=${open}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
1819
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${newValue}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
1806
1820
  }, [setOpenProp, open]);
1807
1821
  // Helper to toggle the sidebar.
1808
1822
  const toggleSidebar = react.useCallback(() => {
@@ -1861,7 +1875,7 @@ $Sidebar.displayName = 'Sidebar';
1861
1875
  const SidebarTrigger = react.forwardRef((_a, ref) => {
1862
1876
  var { className, onClick } = _a, props = __rest(_a, ["className", "onClick"]);
1863
1877
  const { toggleSidebar } = useSidebar();
1864
- return (jsxRuntime.jsx(Button, Object.assign({ ref: ref, "data-sidebar": "trigger", variant: "text", suffixIcon: lucideReact.PanelLeft, className: cn('h-7 w-7', className), onClick: (event) => {
1878
+ return (jsxRuntime.jsx(Button, Object.assign({ ref: ref, "data-sidebar": "trigger", variant: "text", suffixIcon: lucideReact.PanelLeft, className: className, onClick: (event) => {
1865
1879
  onClick === null || onClick === void 0 ? void 0 : onClick(event);
1866
1880
  toggleSidebar();
1867
1881
  } }, props)));
@@ -2035,7 +2049,7 @@ const Sidebar = Object.assign($Sidebar, {
2035
2049
  Trigger: SidebarTrigger,
2036
2050
  });
2037
2051
 
2038
- const Navbar = ({ className, leftSlot, rightSlot }) => (jsxRuntime.jsx(Block, { className: cn('border-b p-3 dark:border-slate-700', className), fullWidth: true, children: jsxRuntime.jsxs(Flex, { align: "center", justify: "between", children: [jsxRuntime.jsxs(Flex, { align: "center", children: [jsxRuntime.jsx(Sidebar.Trigger, {}), leftSlot] }), rightSlot] }) }));
2052
+ const Navbar = ({ className, sidebarTriggerClassName, leftSlot, rightSlot, }) => (jsxRuntime.jsx(Block, { className: cn('border-b p-3 dark:border-slate-700', className), fullWidth: true, children: jsxRuntime.jsxs(Flex, { align: "center", justify: "between", children: [jsxRuntime.jsxs(Flex, { align: "center", children: [jsxRuntime.jsx(Sidebar.Trigger, { className: sidebarTriggerClassName }), leftSlot] }), rightSlot] }) }));
2039
2053
 
2040
2054
  const Layout = (_a) => {
2041
2055
  var { children, className } = _a, _b = _a.sidebarProps, { basePath, smallLogo, fullLogo, items } = _b, sidebarProps = __rest(_b, ["basePath", "smallLogo", "fullLogo", "items"]), { navbarProps } = _a;
@@ -2095,26 +2109,37 @@ const Tabs = Object.assign($Tabs, {
2095
2109
  });
2096
2110
 
2097
2111
  const LayoutContext = react.createContext(undefined);
2112
+ const THEME_MEDIA_QUERY = '(prefers-color-scheme: dark)';
2113
+ const THEME_COOKIE_NAME = 'theme:state';
2114
+ const THEME_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
2098
2115
  const LayoutContextProvider = ({ children }) => {
2099
- const [theme, setTheme] = react.useState(getValueFromLocalStorage(THEME_KEY, 'light'));
2116
+ const [theme, _setTheme] = react.useState(getValueFromCookie(THEME_COOKIE_NAME, 'system'));
2117
+ const [resolvedTheme, setResolvedTheme] = react.useState(() => theme === 'system' ? getSystemTheme() : theme);
2100
2118
  react.useEffect(() => {
2101
- if (theme) {
2102
- if (theme === 'dark') {
2103
- document.documentElement.classList.add('dark');
2104
- }
2105
- else {
2106
- document.documentElement.classList.remove('dark');
2107
- }
2119
+ if (resolvedTheme === 'dark') {
2120
+ document.documentElement.classList.add('dark');
2121
+ }
2122
+ else {
2123
+ document.documentElement.classList.remove('dark');
2124
+ }
2125
+ }, [resolvedTheme]);
2126
+ react.useEffect(() => {
2127
+ if (theme !== 'system') {
2128
+ setResolvedTheme(theme);
2129
+ return;
2108
2130
  }
2131
+ setResolvedTheme(getSystemTheme());
2132
+ const handleMediaQuery = (e) => setResolvedTheme(getSystemTheme(e));
2133
+ const mediaQuery = window.matchMedia(THEME_MEDIA_QUERY);
2134
+ mediaQuery.addEventListener('change', handleMediaQuery);
2135
+ return () => mediaQuery.removeEventListener('change', handleMediaQuery);
2109
2136
  }, [theme]);
2110
- const toggleTheme = () => {
2111
- setTheme((theme) => {
2112
- const newValue = theme === 'dark' ? 'light' : 'dark';
2113
- window.localStorage.setItem(THEME_KEY, newValue);
2114
- return newValue;
2115
- });
2137
+ const setTheme = (theme) => {
2138
+ _setTheme(theme);
2139
+ // This sets the cookie to keep the theme state.
2140
+ document.cookie = `${THEME_COOKIE_NAME}=${theme}; path=/; max-age=${THEME_COOKIE_MAX_AGE}`;
2116
2141
  };
2117
- return jsxRuntime.jsx(LayoutContext.Provider, { value: { theme, toggleTheme }, children: children });
2142
+ return (jsxRuntime.jsx(LayoutContext.Provider, { value: { theme, resolvedTheme, setTheme }, children: children }));
2118
2143
  };
2119
2144
  function useLayoutContext() {
2120
2145
  const context = react.useContext(LayoutContext);
@@ -2123,24 +2148,15 @@ function useLayoutContext() {
2123
2148
  }
2124
2149
  return context;
2125
2150
  }
2126
- const THEME_KEY = 'tw-react-components__theme';
2127
- function getValueFromLocalStorage(key, _default) {
2128
- var _a;
2129
- const transformers = {
2130
- string: String,
2131
- boolean: (value) => value === 'true',
2132
- };
2133
- return typeof window !== 'undefined'
2134
- ? transformers[typeof _default]((_a = window.localStorage.getItem(key)) !== null && _a !== void 0 ? _a : _default)
2135
- : _default;
2151
+ function getSystemTheme(e) {
2152
+ if (!e)
2153
+ e = window.matchMedia(THEME_MEDIA_QUERY);
2154
+ return e.matches ? 'dark' : 'light';
2136
2155
  }
2137
2156
 
2138
- const ThemeSwitcher = ({ className }) => {
2139
- const { theme, toggleTheme } = useLayoutContext();
2140
- const darkMode = theme === 'dark';
2141
- return (jsxRuntime.jsx(Switch, { className: className, checked: darkMode, onCheckedChange: toggleTheme, thumbProps: {
2142
- children: darkMode ? (jsxRuntime.jsx(lucideReact.MoonIcon, { className: "h-6 w-6 rounded-full bg-slate-900 p-1 text-white" })) : (jsxRuntime.jsx(lucideReact.SunIcon, { className: "h-6 w-6 rounded-full bg-white p-1 text-black" })),
2143
- } }));
2157
+ const ThemeSelector = ({ className }) => {
2158
+ const { resolvedTheme, setTheme } = useLayoutContext();
2159
+ return (jsxRuntime.jsxs(DropdownMenu, { children: [jsxRuntime.jsx(DropdownMenu.Trigger, { asChild: true, children: jsxRuntime.jsx(Button, { prefixIcon: resolvedTheme === 'dark' ? lucideReact.MoonIcon : lucideReact.SunIcon, className: className, variant: "text" }) }), jsxRuntime.jsxs(DropdownMenu.Content, { children: [jsxRuntime.jsxs(DropdownMenu.Item, { onClick: () => setTheme('light'), children: [jsxRuntime.jsx(DropdownMenu.Icon, { icon: lucideReact.SunIcon }), "Light"] }), jsxRuntime.jsxs(DropdownMenu.Item, { onClick: () => setTheme('dark'), children: [jsxRuntime.jsx(DropdownMenu.Icon, { icon: lucideReact.MoonIcon }), "Dark"] }), jsxRuntime.jsxs(DropdownMenu.Item, { onClick: () => setTheme('system'), children: [jsxRuntime.jsx(DropdownMenu.Icon, { icon: lucideReact.MonitorIcon }), "System"] })] })] }));
2144
2160
  };
2145
2161
 
2146
2162
  exports.Badge = Badge;
@@ -2175,6 +2191,12 @@ exports.Pagination = Pagination;
2175
2191
  exports.PasswordInput = PasswordInput;
2176
2192
  exports.PdfViewerDialog = PdfViewerDialog;
2177
2193
  exports.Popover = Popover;
2194
+ exports.SIDEBAR_COOKIE_MAX_AGE = SIDEBAR_COOKIE_MAX_AGE;
2195
+ exports.SIDEBAR_COOKIE_NAME = SIDEBAR_COOKIE_NAME;
2196
+ exports.SIDEBAR_KEYBOARD_SHORTCUT = SIDEBAR_KEYBOARD_SHORTCUT;
2197
+ exports.SIDEBAR_WIDTH = SIDEBAR_WIDTH;
2198
+ exports.SIDEBAR_WIDTH_ICON = SIDEBAR_WIDTH_ICON;
2199
+ exports.SIDEBAR_WIDTH_MOBILE = SIDEBAR_WIDTH_MOBILE;
2178
2200
  exports.SelectInput = SelectInput;
2179
2201
  exports.Separator = Separator;
2180
2202
  exports.Sheet = Sheet;
@@ -2184,17 +2206,20 @@ exports.SidebarContextProvider = SidebarContextProvider;
2184
2206
  exports.Skeleton = Skeleton;
2185
2207
  exports.Spinner = Spinner;
2186
2208
  exports.Switch = Switch;
2187
- exports.THEME_KEY = THEME_KEY;
2209
+ exports.THEME_COOKIE_MAX_AGE = THEME_COOKIE_MAX_AGE;
2210
+ exports.THEME_COOKIE_NAME = THEME_COOKIE_NAME;
2211
+ exports.THEME_MEDIA_QUERY = THEME_MEDIA_QUERY;
2188
2212
  exports.Table = Table;
2189
2213
  exports.Tabs = Tabs;
2190
2214
  exports.TextInput = TextInput;
2191
2215
  exports.TextareaInput = TextareaInput;
2192
- exports.ThemeSwitcher = ThemeSwitcher;
2216
+ exports.ThemeSelector = ThemeSelector;
2193
2217
  exports.Tooltip = Tooltip;
2194
2218
  exports.cn = cn;
2195
2219
  exports.compareDates = compareDates;
2196
2220
  exports.generalComparator = generalComparator;
2197
2221
  exports.getDisplayDate = getDisplayDate;
2222
+ exports.getValueFromCookie = getValueFromCookie;
2198
2223
  exports.isEmpty = isEmpty;
2199
2224
  exports.mergeRefs = mergeRefs;
2200
2225
  exports.resolveTargetObject = resolveTargetObject;
package/index.esm.js CHANGED
@@ -4,7 +4,7 @@ import { clsx } from 'clsx';
4
4
  import { twMerge } from 'tailwind-merge';
5
5
  import dayjs from 'dayjs';
6
6
  import advancedFormat from 'dayjs/plugin/advancedFormat';
7
- import { HelpCircle, XIcon, AtSignIcon, EyeIcon, EyeOffIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, ChevronDownIcon, CalendarIcon, ClockIcon, CheckIcon, CircleIcon, CloudUploadIcon, ChevronsLeftIcon, ChevronsRightIcon, ChevronsDownUpIcon, ChevronsUpDownIcon, ArrowUpDownIcon, SortAscIcon, SortDescIcon, MinusIcon, PlusIcon, PanelLeft, MoonIcon, SunIcon } from 'lucide-react';
7
+ import { HelpCircle, XIcon, AtSignIcon, EyeIcon, EyeOffIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, ChevronDownIcon, CalendarIcon, ClockIcon, CheckIcon, CircleIcon, CloudUploadIcon, ChevronsLeftIcon, ChevronsRightIcon, ChevronsDownUpIcon, ChevronsUpDownIcon, ArrowUpDownIcon, SortAscIcon, SortDescIcon, MinusIcon, PlusIcon, PanelLeft, MoonIcon, SunIcon, MonitorIcon } from 'lucide-react';
8
8
  import localeData from 'dayjs/plugin/localeData';
9
9
  import * as TooltipPrimitive from '@radix-ui/react-tooltip';
10
10
  import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
@@ -96,6 +96,19 @@ function getDisplayDate(date, format, locale) {
96
96
  return (locale ? dayjs(date).locale(locale) : dayjs(date)).format(format);
97
97
  }
98
98
 
99
+ function getValueFromCookie(key, _default) {
100
+ var _a, _b;
101
+ const transformers = {
102
+ string: String,
103
+ boolean: (value) => value === 'true',
104
+ };
105
+ return typeof window !== 'undefined'
106
+ ? transformers[typeof _default]((_b = (_a = document.cookie
107
+ .split('; ')
108
+ .find((row) => row.startsWith(key))) === null || _a === void 0 ? void 0 : _a.split('=')[1]) !== null && _b !== void 0 ? _b : _default)
109
+ : _default;
110
+ }
111
+
99
112
  function isEmpty(s) {
100
113
  if (Array.isArray(s))
101
114
  return s.length === 0;
@@ -559,10 +572,10 @@ const sizeClassNames = {
559
572
  },
560
573
  };
561
574
  const Button = forwardRef((_a, ref) => {
562
- var { children, className, size = 'medium', color = 'slate', variant = 'filled', rounded, prefixIcon: PrefixIcon, suffixIcon: SuffixIcon } = _a, props = __rest(_a, ["children", "className", "size", "color", "variant", "rounded", "prefixIcon", "suffixIcon"]);
575
+ var { children, className, size = 'medium', color = 'slate', variant = 'filled', rounded, prefixIcon: PrefixIcon, suffixIcon: SuffixIcon, unstyled } = _a, props = __rest(_a, ["children", "className", "size", "color", "variant", "rounded", "prefixIcon", "suffixIcon", "unstyled"]);
563
576
  return (jsxs("button", Object.assign({ className: cn('relative flex aspect-square items-center font-medium duration-200', sizeClassNames[size].base, variantClassNames[variant][color].base, rounded ? 'rounded-full' : 'rounded-md', props.disabled
564
577
  ? 'cursor-not-allowed opacity-50'
565
- : props.onClick
578
+ : !unstyled
566
579
  ? `${variantClassNames[variant][color].hover} ${variantClassNames[variant][color].focus} ${variantClassNames[variant][color].active}`
567
580
  : 'cursor-default', children ? `${sizeClassNames[size].withChildren} aspect-[initial]` : 'justify-center', className), type: "button" }, props, { ref: ref, children: [PrefixIcon && (jsx(PrefixIcon, { className: children ? sizeClassNames[size].icon.withChildren : sizeClassNames[size].icon.base })), children, SuffixIcon && (jsx(SuffixIcon, { className: children ? sizeClassNames[size].icon.withChildren : sizeClassNames[size].icon.base }))] })));
568
581
  });
@@ -1767,15 +1780,16 @@ const SidebarContextProvider = forwardRef((_a, ref) => {
1767
1780
  const screenRef = useRef(document.documentElement);
1768
1781
  // This is the internal state of the sidebar.
1769
1782
  // We use openProp and setOpenProp for control from outside the component.
1770
- const [_open, _setOpen] = useState(defaultOpen);
1783
+ const [_open, _setOpen] = useState(getValueFromCookie(SIDEBAR_COOKIE_NAME, defaultOpen));
1771
1784
  const open = openProp !== null && openProp !== void 0 ? openProp : _open;
1772
1785
  const setOpen = useCallback((value) => {
1773
1786
  if (setOpenProp) {
1774
1787
  return setOpenProp === null || setOpenProp === void 0 ? void 0 : setOpenProp(typeof value === 'function' ? value(open) : value);
1775
1788
  }
1776
- _setOpen(value);
1789
+ const newValue = typeof value === 'function' ? value(open) : value;
1790
+ _setOpen(newValue);
1777
1791
  // This sets the cookie to keep the sidebar state.
1778
- document.cookie = `${SIDEBAR_COOKIE_NAME}=${open}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
1792
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${newValue}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
1779
1793
  }, [setOpenProp, open]);
1780
1794
  // Helper to toggle the sidebar.
1781
1795
  const toggleSidebar = useCallback(() => {
@@ -1834,7 +1848,7 @@ $Sidebar.displayName = 'Sidebar';
1834
1848
  const SidebarTrigger = forwardRef((_a, ref) => {
1835
1849
  var { className, onClick } = _a, props = __rest(_a, ["className", "onClick"]);
1836
1850
  const { toggleSidebar } = useSidebar();
1837
- return (jsx(Button, Object.assign({ ref: ref, "data-sidebar": "trigger", variant: "text", suffixIcon: PanelLeft, className: cn('h-7 w-7', className), onClick: (event) => {
1851
+ return (jsx(Button, Object.assign({ ref: ref, "data-sidebar": "trigger", variant: "text", suffixIcon: PanelLeft, className: className, onClick: (event) => {
1838
1852
  onClick === null || onClick === void 0 ? void 0 : onClick(event);
1839
1853
  toggleSidebar();
1840
1854
  } }, props)));
@@ -2008,7 +2022,7 @@ const Sidebar = Object.assign($Sidebar, {
2008
2022
  Trigger: SidebarTrigger,
2009
2023
  });
2010
2024
 
2011
- const Navbar = ({ className, leftSlot, rightSlot }) => (jsx(Block, { className: cn('border-b p-3 dark:border-slate-700', className), fullWidth: true, children: jsxs(Flex, { align: "center", justify: "between", children: [jsxs(Flex, { align: "center", children: [jsx(Sidebar.Trigger, {}), leftSlot] }), rightSlot] }) }));
2025
+ const Navbar = ({ className, sidebarTriggerClassName, leftSlot, rightSlot, }) => (jsx(Block, { className: cn('border-b p-3 dark:border-slate-700', className), fullWidth: true, children: jsxs(Flex, { align: "center", justify: "between", children: [jsxs(Flex, { align: "center", children: [jsx(Sidebar.Trigger, { className: sidebarTriggerClassName }), leftSlot] }), rightSlot] }) }));
2012
2026
 
2013
2027
  const Layout = (_a) => {
2014
2028
  var { children, className } = _a, _b = _a.sidebarProps, { basePath, smallLogo, fullLogo, items } = _b, sidebarProps = __rest(_b, ["basePath", "smallLogo", "fullLogo", "items"]), { navbarProps } = _a;
@@ -2068,26 +2082,37 @@ const Tabs = Object.assign($Tabs, {
2068
2082
  });
2069
2083
 
2070
2084
  const LayoutContext = createContext(undefined);
2085
+ const THEME_MEDIA_QUERY = '(prefers-color-scheme: dark)';
2086
+ const THEME_COOKIE_NAME = 'theme:state';
2087
+ const THEME_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
2071
2088
  const LayoutContextProvider = ({ children }) => {
2072
- const [theme, setTheme] = useState(getValueFromLocalStorage(THEME_KEY, 'light'));
2089
+ const [theme, _setTheme] = useState(getValueFromCookie(THEME_COOKIE_NAME, 'system'));
2090
+ const [resolvedTheme, setResolvedTheme] = useState(() => theme === 'system' ? getSystemTheme() : theme);
2073
2091
  useEffect(() => {
2074
- if (theme) {
2075
- if (theme === 'dark') {
2076
- document.documentElement.classList.add('dark');
2077
- }
2078
- else {
2079
- document.documentElement.classList.remove('dark');
2080
- }
2092
+ if (resolvedTheme === 'dark') {
2093
+ document.documentElement.classList.add('dark');
2094
+ }
2095
+ else {
2096
+ document.documentElement.classList.remove('dark');
2097
+ }
2098
+ }, [resolvedTheme]);
2099
+ useEffect(() => {
2100
+ if (theme !== 'system') {
2101
+ setResolvedTheme(theme);
2102
+ return;
2081
2103
  }
2104
+ setResolvedTheme(getSystemTheme());
2105
+ const handleMediaQuery = (e) => setResolvedTheme(getSystemTheme(e));
2106
+ const mediaQuery = window.matchMedia(THEME_MEDIA_QUERY);
2107
+ mediaQuery.addEventListener('change', handleMediaQuery);
2108
+ return () => mediaQuery.removeEventListener('change', handleMediaQuery);
2082
2109
  }, [theme]);
2083
- const toggleTheme = () => {
2084
- setTheme((theme) => {
2085
- const newValue = theme === 'dark' ? 'light' : 'dark';
2086
- window.localStorage.setItem(THEME_KEY, newValue);
2087
- return newValue;
2088
- });
2110
+ const setTheme = (theme) => {
2111
+ _setTheme(theme);
2112
+ // This sets the cookie to keep the theme state.
2113
+ document.cookie = `${THEME_COOKIE_NAME}=${theme}; path=/; max-age=${THEME_COOKIE_MAX_AGE}`;
2089
2114
  };
2090
- return jsx(LayoutContext.Provider, { value: { theme, toggleTheme }, children: children });
2115
+ return (jsx(LayoutContext.Provider, { value: { theme, resolvedTheme, setTheme }, children: children }));
2091
2116
  };
2092
2117
  function useLayoutContext() {
2093
2118
  const context = useContext(LayoutContext);
@@ -2096,24 +2121,15 @@ function useLayoutContext() {
2096
2121
  }
2097
2122
  return context;
2098
2123
  }
2099
- const THEME_KEY = 'tw-react-components__theme';
2100
- function getValueFromLocalStorage(key, _default) {
2101
- var _a;
2102
- const transformers = {
2103
- string: String,
2104
- boolean: (value) => value === 'true',
2105
- };
2106
- return typeof window !== 'undefined'
2107
- ? transformers[typeof _default]((_a = window.localStorage.getItem(key)) !== null && _a !== void 0 ? _a : _default)
2108
- : _default;
2124
+ function getSystemTheme(e) {
2125
+ if (!e)
2126
+ e = window.matchMedia(THEME_MEDIA_QUERY);
2127
+ return e.matches ? 'dark' : 'light';
2109
2128
  }
2110
2129
 
2111
- const ThemeSwitcher = ({ className }) => {
2112
- const { theme, toggleTheme } = useLayoutContext();
2113
- const darkMode = theme === 'dark';
2114
- return (jsx(Switch, { className: className, checked: darkMode, onCheckedChange: toggleTheme, thumbProps: {
2115
- children: darkMode ? (jsx(MoonIcon, { className: "h-6 w-6 rounded-full bg-slate-900 p-1 text-white" })) : (jsx(SunIcon, { className: "h-6 w-6 rounded-full bg-white p-1 text-black" })),
2116
- } }));
2130
+ const ThemeSelector = ({ className }) => {
2131
+ const { resolvedTheme, setTheme } = useLayoutContext();
2132
+ return (jsxs(DropdownMenu, { children: [jsx(DropdownMenu.Trigger, { asChild: true, children: jsx(Button, { prefixIcon: resolvedTheme === 'dark' ? MoonIcon : SunIcon, className: className, variant: "text" }) }), jsxs(DropdownMenu.Content, { children: [jsxs(DropdownMenu.Item, { onClick: () => setTheme('light'), children: [jsx(DropdownMenu.Icon, { icon: SunIcon }), "Light"] }), jsxs(DropdownMenu.Item, { onClick: () => setTheme('dark'), children: [jsx(DropdownMenu.Icon, { icon: MoonIcon }), "Dark"] }), jsxs(DropdownMenu.Item, { onClick: () => setTheme('system'), children: [jsx(DropdownMenu.Icon, { icon: MonitorIcon }), "System"] })] })] }));
2117
2133
  };
2118
2134
 
2119
- export { Badge, BasicInput, BasicInputExtension, Block, Button, Card, CheckboxInput, ConfirmDialog, DataTable, DateTimeInput, Dialog, DropdownMenu, EmailInput, FileInput, Flex, FormDialog, FormGroup, FormInputs, Hint, Label, Layout, LayoutContext, LayoutContextProvider, List, ListSorter, ListSorterDialog, Navbar, NumberInput, Pagination, PasswordInput, PdfViewerDialog, Popover, SelectInput, Separator, Sheet, Sidebar, SidebarContext, SidebarContextProvider, Skeleton, Spinner, Switch, THEME_KEY, Table, Tabs, TextInput, TextareaInput, ThemeSwitcher, Tooltip, cn, compareDates, generalComparator, getDisplayDate, isEmpty, mergeRefs, resolveTargetObject, useDays, useIsMobile, useLayoutContext, useLongPress, useMonths, useOnSwipe, useOutsideClick, usePagination, useSidebar };
2135
+ export { Badge, BasicInput, BasicInputExtension, Block, Button, Card, CheckboxInput, ConfirmDialog, DataTable, DateTimeInput, Dialog, DropdownMenu, EmailInput, FileInput, Flex, FormDialog, FormGroup, FormInputs, Hint, Label, Layout, LayoutContext, LayoutContextProvider, List, ListSorter, ListSorterDialog, Navbar, NumberInput, Pagination, PasswordInput, PdfViewerDialog, Popover, SIDEBAR_COOKIE_MAX_AGE, SIDEBAR_COOKIE_NAME, SIDEBAR_KEYBOARD_SHORTCUT, SIDEBAR_WIDTH, SIDEBAR_WIDTH_ICON, SIDEBAR_WIDTH_MOBILE, SelectInput, Separator, Sheet, Sidebar, SidebarContext, SidebarContextProvider, Skeleton, Spinner, Switch, THEME_COOKIE_MAX_AGE, THEME_COOKIE_NAME, THEME_MEDIA_QUERY, Table, Tabs, TextInput, TextareaInput, ThemeSelector, Tooltip, cn, compareDates, generalComparator, getDisplayDate, getValueFromCookie, isEmpty, mergeRefs, resolveTargetObject, useDays, useIsMobile, useLayoutContext, useLongPress, useMonths, useOnSwipe, useOutsideClick, usePagination, useSidebar };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tw-react-components",
3
3
  "description": "A set of React components build with TailwindCSS to make a nice dashboard.",
4
- "version": "0.0.139",
4
+ "version": "0.0.140",
5
5
  "license": "MIT",
6
6
  "homepage": "https://bacali95.github.io/tw-react-components",
7
7
  "author": {
@@ -9,6 +9,7 @@ export type ButtonProps = PropsWithoutRef<ComponentProps<'button'>> & {
9
9
  rounded?: boolean;
10
10
  prefixIcon?: LucideIcon;
11
11
  suffixIcon?: LucideIcon;
12
+ unstyled?: boolean;
12
13
  };
13
14
  export declare const Button: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & {
14
15
  size?: Size;
@@ -17,4 +18,5 @@ export declare const Button: import("react").ForwardRefExoticComponent<Omit<impo
17
18
  rounded?: boolean;
18
19
  prefixIcon?: LucideIcon;
19
20
  suffixIcon?: LucideIcon;
21
+ unstyled?: boolean;
20
22
  } & import("react").RefAttributes<HTMLButtonElement>>;
@@ -1,6 +1,7 @@
1
1
  import { FC, ReactNode } from 'react';
2
2
  export type NavbarProps = {
3
3
  className?: string;
4
+ sidebarTriggerClassName?: string;
4
5
  leftSlot?: ReactNode;
5
6
  rightSlot?: ReactNode;
6
7
  };
@@ -1,6 +1,12 @@
1
1
  import { type VariantProps } from 'class-variance-authority';
2
2
  import { ComponentProps } from 'react';
3
3
  import { Tooltip } from '../Tooltip';
4
+ export declare const SIDEBAR_COOKIE_NAME = "sidebar:state";
5
+ export declare const SIDEBAR_COOKIE_MAX_AGE: number;
6
+ export declare const SIDEBAR_WIDTH = "16rem";
7
+ export declare const SIDEBAR_WIDTH_MOBILE = "18rem";
8
+ export declare const SIDEBAR_WIDTH_ICON = "3rem";
9
+ export declare const SIDEBAR_KEYBOARD_SHORTCUT = "b";
4
10
  export type SidebarContext = {
5
11
  state: 'expanded' | 'collapsed';
6
12
  open: boolean;
@@ -81,5 +87,6 @@ export declare const Sidebar: import("react").ForwardRefExoticComponent<Omit<imp
81
87
  rounded?: boolean;
82
88
  prefixIcon?: import("lucide-react").LucideIcon;
83
89
  suffixIcon?: import("lucide-react").LucideIcon;
90
+ unstyled?: boolean;
84
91
  } & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
85
92
  };
@@ -2,5 +2,5 @@ import { FC } from 'react';
2
2
  type Props = {
3
3
  className?: string;
4
4
  };
5
- export declare const ThemeSwitcher: FC<Props>;
5
+ export declare const ThemeSelector: FC<Props>;
6
6
  export {};
@@ -22,6 +22,6 @@ export * from './Spinner';
22
22
  export * from './Switch';
23
23
  export * from './Table';
24
24
  export * from './Tabs';
25
- export * from './ThemeSwitcher';
25
+ export * from './ThemeSelector';
26
26
  export * from './Tooltip';
27
27
  export * from './types';
@@ -1,10 +1,13 @@
1
1
  import { FC, PropsWithChildren } from 'react';
2
+ export type ThemeState = 'dark' | 'light' | 'system';
2
3
  export type LayoutContext = {
3
4
  theme: ThemeState;
4
- toggleTheme: () => void;
5
+ resolvedTheme: Exclude<ThemeState, 'system'>;
6
+ setTheme: (theme: ThemeState) => void;
5
7
  };
6
8
  export declare const LayoutContext: import("react").Context<LayoutContext | undefined>;
9
+ export declare const THEME_MEDIA_QUERY = "(prefers-color-scheme: dark)";
10
+ export declare const THEME_COOKIE_NAME = "theme:state";
11
+ export declare const THEME_COOKIE_MAX_AGE: number;
7
12
  export declare const LayoutContextProvider: FC<PropsWithChildren>;
8
13
  export declare function useLayoutContext(): LayoutContext;
9
- export type ThemeState = 'dark' | 'light';
10
- export declare const THEME_KEY = "tw-react-components__theme";
@@ -0,0 +1 @@
1
+ export declare function getValueFromCookie<T extends string | boolean>(key: string, _default: T): T;
@@ -2,6 +2,7 @@ export * from './cn';
2
2
  export * from './compareDates';
3
3
  export * from './generalComparator';
4
4
  export * from './getDisplayDate';
5
+ export * from './getValueFromCookie';
5
6
  export * from './isEmpty';
6
7
  export * from './mergeRefs';
7
8
  export * from './resolveTargetObject';