@raxrai/stylelab-ui 0.3.0 → 0.3.2

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/README.md CHANGED
@@ -49,6 +49,8 @@ export default function RootLayout({ children }) {
49
49
  }
50
50
  ```
51
51
 
52
+ Optional props: `storageKey` (default `"stylelab-theme"`) for the localStorage key; `persistTheme` (default `true`). When `persistTheme={false}`, the provider does not read or write localStorage (theme is fixed to `defaultTheme` unless you change it in memory).
53
+
52
54
  ### 3. Use components
53
55
 
54
56
  ```tsx
@@ -191,6 +193,7 @@ Implements focus trap and scroll lock when open.
191
193
  | `items` | `{ value, label, disabled? }[]` | — | Options |
192
194
  | `value` / `defaultValue` | `string` | — | Controlled / initial |
193
195
  | `onValueChange`| `(value: string) => void` | — | Selection callback |
196
+ | `disabled` | `boolean` | `false` | When true, does not open; trigger is non-interactive |
194
197
 
195
198
  Keyboard: Enter/Space to open, Arrows to move, Enter to select, Escape to close.
196
199
 
@@ -212,6 +215,14 @@ import { cn, useFocusTrap, useClickOutside, useKeyboardNavigation, getNextListIn
212
215
 
213
216
  ## Changelog
214
217
 
218
+ ### [0.3.2]
219
+
220
+ - **ThemeProvider** — Optional `persistTheme` prop (default `true`). When `persistTheme={false}`, the provider does not read from or write to localStorage: theme stays at `defaultTheme` unless changed in memory. Use for fixed-theme pages (e.g. landing, docs) or nested providers where only the inner tree should persist. Optional `storageKey` (default `"stylelab-theme"`) still applies when `persistTheme` is true.
221
+
222
+ ### [0.3.1]
223
+
224
+ - **Dropdown** — `disabled` prop: when true, the dropdown does not open and the trigger is non-interactive (opacity, cursor, no popover). Use for empty or loading state.
225
+
215
226
  ### [0.2.0]
216
227
 
217
228
  - **Navbar** — Optional responsive API: pass `logo`, `items` (`{ href, label }[]`), and `right` (e.g. theme switcher). On small screens nav links become a hamburger menu that opens as an overlay (no layout shift), with the theme’s navbar background; glass and clay use an opaque panel so content doesn’t show through. Desktop shows full nav row. Menu closes on link click or outside click. You can still pass `children` for a custom layout. Exported types: `NavbarItem`, `NavbarProps`.
package/dist/index.cjs CHANGED
@@ -341,12 +341,14 @@ function getStoredTheme(storageKey, defaultTheme) {
341
341
  function ThemeProvider({
342
342
  children,
343
343
  defaultTheme = "minimal",
344
- storageKey = "stylelab-theme"
344
+ storageKey = "stylelab-theme",
345
+ persistTheme = true
345
346
  }) {
346
347
  const [theme, setThemeState] = (0, import_react.useState)(defaultTheme);
347
348
  (0, import_react.useEffect)(() => {
349
+ if (!persistTheme) return;
348
350
  setThemeState((prev) => getStoredTheme(storageKey, prev));
349
- }, [storageKey]);
351
+ }, [storageKey, persistTheme]);
350
352
  (0, import_react.useEffect)(() => {
351
353
  if (typeof document !== "undefined") {
352
354
  try {
@@ -361,12 +363,12 @@ function ThemeProvider({
361
363
  if (typeof document !== "undefined") {
362
364
  try {
363
365
  document.documentElement.dataset.stylelabTheme = next;
364
- localStorage.setItem(storageKey, next);
366
+ if (persistTheme) localStorage.setItem(storageKey, next);
365
367
  } catch (_) {
366
368
  }
367
369
  }
368
370
  },
369
- [storageKey]
371
+ [storageKey, persistTheme]
370
372
  );
371
373
  const value = (0, import_react.useMemo)(
372
374
  () => ({ theme, setTheme }),
@@ -4993,6 +4995,7 @@ function Dropdown({
4993
4995
  value: controlledValue,
4994
4996
  defaultValue,
4995
4997
  onValueChange,
4998
+ disabled = false,
4996
4999
  className,
4997
5000
  style,
4998
5001
  theme: themeProp,
@@ -5028,10 +5031,13 @@ function Dropdown({
5028
5031
  });
5029
5032
  }, []);
5030
5033
  (0, import_react15.useLayoutEffect)(() => {
5031
- if (open) {
5034
+ if (open && !disabled) {
5032
5035
  updatePosition();
5033
5036
  }
5034
- }, [open, updatePosition, items.length]);
5037
+ }, [open, disabled, updatePosition, items.length]);
5038
+ (0, import_react15.useEffect)(() => {
5039
+ if (disabled && open) setOpen(false);
5040
+ }, [disabled, open]);
5035
5041
  (0, import_react15.useEffect)(() => {
5036
5042
  if (!open) return;
5037
5043
  const onScrollOrResize = () => updatePosition();
@@ -5074,6 +5080,7 @@ function Dropdown({
5074
5080
  }, [open, displayValue, items]);
5075
5081
  const handleKeyDown = (0, import_react15.useCallback)(
5076
5082
  (e) => {
5083
+ if (disabled) return;
5077
5084
  if (!open) {
5078
5085
  if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") {
5079
5086
  e.preventDefault();
@@ -5106,15 +5113,17 @@ function Dropdown({
5106
5113
  selectIndex(highlightedIndex);
5107
5114
  }
5108
5115
  },
5109
- [open, highlightedIndex, items.length, selectIndex]
5116
+ [disabled, open, highlightedIndex, items.length, selectIndex]
5110
5117
  );
5111
5118
  const selectedLabel = displayValue ? items.find((i) => i.value === displayValue)?.label ?? displayValue : placeholder;
5112
5119
  const defaultTrigger = /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
5113
5120
  "span",
5114
5121
  {
5115
5122
  className: cn(
5116
- "inline-flex min-w-[8rem] cursor-pointer items-center justify-between rounded-lg border border-transparent px-3 py-2 text-sm font-medium transition-colors",
5117
- itemHighlightClass,
5123
+ "inline-flex min-w-[8rem] items-center justify-between rounded-lg border border-transparent px-3 py-2 text-sm font-medium transition-colors",
5124
+ !disabled && "cursor-pointer",
5125
+ !disabled && itemHighlightClass,
5126
+ disabled && "cursor-not-allowed opacity-60",
5118
5127
  classNames.trigger
5119
5128
  ),
5120
5129
  children: [
@@ -5124,7 +5133,7 @@ function Dropdown({
5124
5133
  }
5125
5134
  );
5126
5135
  const trigger = triggerProp ?? defaultTrigger;
5127
- const popover = open ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5136
+ const popover = open && !disabled ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5128
5137
  "ul",
5129
5138
  {
5130
5139
  ref: listRef,
@@ -5170,11 +5179,13 @@ function Dropdown({
5170
5179
  {
5171
5180
  ref: triggerRef,
5172
5181
  role: "button",
5173
- tabIndex: 0,
5182
+ tabIndex: disabled ? -1 : 0,
5174
5183
  "aria-haspopup": "listbox",
5175
5184
  "aria-expanded": open,
5185
+ "aria-disabled": disabled,
5176
5186
  "aria-activedescendant": open && items[highlightedIndex] ? `dropdown-option-${items[highlightedIndex].value}` : void 0,
5177
- onClick: () => setOpen((o) => !o),
5187
+ onClick: () => !disabled && setOpen((o) => !o),
5188
+ className: disabled ? "cursor-not-allowed" : void 0,
5178
5189
  children: trigger
5179
5190
  }
5180
5191
  ),