/**
* NativeSelect: styled native `<select>`. Tier-1 class helpers. Uses
* `appearance: none` to hide the platform chevron and overlays an SVG
* chevron on top. Best mobile UX (native picker), full keyboard support,
* form submission works natively. No JS.
*
* shadcn parity:
* NativeSelect → nativeSelectClass()
* NativeSelect wrapper → nativeSelectWrapperClass()
* NativeSelect chevron → nativeSelectIconClass()
* NativeSelect option → bare <option> (or nativeSelectOptionClass()
* for explicit overrides; the installed
* stylesheet sets Canvas / CanvasText on
* every <option> automatically)
*
* Importing this module installs a stylesheet that forces Canvas /
* CanvasText on every <option> inside the wrapper so the dropdown reads
* in both light and dark themes regardless of OS preference; advanced
* overrides use `nativeSelectOptionClass()` / `nativeSelectOptGroupClass()`.
*
* Design tokens used: --input, --background, --primary, --primary-foreground,
* --muted-foreground, --ring, --destructive.
*
* @example
* ```html
* <div class=${nativeSelectWrapperClass()}>
* <select class=${nativeSelectClass()} name="plan">
* <option>Basic</option>
* <option>Pro</option>
* </select>
* <!-- chevron icon, decorative -->
* <svg class="${nativeSelectIconClass()}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
* </div>
* ```
*/
import { cn } from '../lib/utils.ts';
export type NativeSelectSize = 'default' | 'sm';
// Auto-apply Canvas/CanvasText to every <option> on the page. Without
// this, an <option> with no explicit bg paints transparent on top of
// the browser-popup background; in dark mode (when color-scheme: dark
// is set on <html>) Chrome's popup is dark, the option's transparent
// bg lets the popup colour through, and the inherited text colour
// from the <select> matches that dark popup: the option disappears,
// only the focused/selected one stays visible because the browser
// overlays its own highlight on it.
//
// Original selector required the option to be inside a
// `.group/native-select` wrapper, on the assumption every user would
// follow the documented Usage block above. But it's easy to write a
// bare <select class=${nativeSelectClass()}> without the wrapper
// (legitimate when you don't need the chevron icon: the popover and
// hover-card docs examples both do this), in which case the rule
// never matched and the dropdown reverted to invisible-options.
// Broadening to `select option, select optgroup` makes the fix work
// everywhere the user has imported native-select, with no required
// wrapper. The Canvas/CanvasText pair is a safe default: they ARE
// the system colours the browser would have painted anyway when no
// rule applied; we just stop relying on inheritance to pull through.
// Selector specificity is 0,0,2 (two elements), so any user who
// genuinely needs custom <option> colours can override with a single
// class anywhere in their cascade (e.g. `.my-select option { ... }`
// at 0,1,2 wins).
//
// `nativeSelectOptionClass()` and `nativeSelectOptGroupClass()` stay
// exported for users who want to opt into the same colours via the
// class helper instead of the global rule. They emit the same
// `bg-[Canvas] text-[CanvasText]` Tailwind utilities: redundant if
// this stylesheet is installed, but harmless and matches the broader
// shadcn convention of "every part has a class helper".
const STYLES = `
select option,
select optgroup {
background-color: Canvas;
color: CanvasText;
}
`;
let installed = false;
export function installNativeSelectStyles(): void {
if (installed || typeof document === 'undefined') return;
if (document.getElementById('ui-native-select-styles')) {
installed = true;
return;
}
const style = document.createElement('style');
style.id = 'ui-native-select-styles';
style.textContent = STYLES;
document.head.appendChild(style);
installed = true;
}
if (typeof document !== 'undefined') installNativeSelectStyles();
export const nativeSelectWrapperClass = (): string =>
'group/native-select relative w-fit has-[select:disabled]:opacity-50';
export function nativeSelectClass(): string {
return cn(
'h-9 w-full min-w-0 appearance-none rounded-md border border-input bg-transparent px-3 py-2 pr-9 text-sm shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed data-[size=sm]:h-8 data-[size=sm]:py-1 dark:bg-input/30 dark:hover:bg-input/50',
'focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50',
'aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40',
);
}
export const nativeSelectIconClass = (): string =>
'pointer-events-none absolute top-1/2 right-3.5 size-4 -translate-y-1/2 text-muted-foreground opacity-50 select-none';
/** Option / optgroup styling: forces themed background even in dark mode. */
export const nativeSelectOptionClass = (): string => 'bg-[Canvas] text-[CanvasText]';
export const nativeSelectOptGroupClass = (): string => 'bg-[Canvas] text-[CanvasText]';