syntra-ui 0.1.0 → 0.2.0

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.
@@ -2,7 +2,7 @@ import { CSSProperties } from 'react';
2
2
  type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'soft' | 'link';
3
3
  type ButtonSize = 'xs' | 'sm' | 'md' | 'lg';
4
4
  type ButtonColorScheme = 'primary' | 'neutral' | 'danger' | (string & {});
5
- declare const BUTTON_CSS = "\n.syntra-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n position: relative;\n white-space: nowrap;\n user-select: none;\n vertical-align: middle;\n cursor: pointer;\n font-family: inherit;\n font-weight: var(--_btn-fw);\n font-size: var(--_btn-fz);\n height: var(--_btn-h);\n padding-left: var(--_btn-px);\n padding-right: var(--_btn-px);\n gap: var(--_btn-gap);\n border-radius: var(--_btn-radius);\n background: var(--_btn-bg);\n color: var(--_btn-color);\n border: 1px solid var(--_btn-border, transparent);\n transition: background 150ms ease, border-color 150ms ease, color 150ms ease, opacity 150ms ease;\n text-decoration: none;\n outline: none;\n}\n.syntra-button:hover:not(:disabled):not([data-loading]) {\n background: var(--_btn-bg-hover);\n border-color: var(--_btn-border-hover, transparent);\n color: var(--_btn-color-hover, var(--_btn-color));\n}\n.syntra-button:active:not(:disabled):not([data-loading]) {\n background: var(--_btn-bg-active);\n}\n.syntra-button:focus-visible {\n outline: 2px solid var(--_btn-ring);\n outline-offset: 2px;\n}\n.syntra-button:disabled,\n.syntra-button[data-loading] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.syntra-button[data-loading] {\n cursor: wait;\n pointer-events: none;\n}\n.syntra-button[data-full-width] {\n width: 100%;\n}\n.syntra-button[data-variant=\"link\"] {\n height: auto;\n padding-left: 0;\n padding-right: 0;\n background: transparent;\n border-color: transparent;\n}\n.syntra-button[data-variant=\"link\"]:hover:not(:disabled) {\n text-decoration: underline;\n background: transparent;\n}\n.syntra-button-label[data-loading] {\n opacity: 0;\n}\n.syntra-button-spinner {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n";
5
+ declare const BUTTON_CSS = "\n.syntra-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n position: relative;\n white-space: nowrap;\n user-select: none;\n vertical-align: middle;\n cursor: pointer;\n font-family: inherit;\n font-weight: var(--_btn-fw);\n font-size: var(--_btn-fz);\n letter-spacing: -0.01em;\n height: var(--_btn-h);\n padding-left: var(--_btn-px);\n padding-right: var(--_btn-px);\n gap: var(--_btn-gap);\n border-radius: var(--_btn-radius);\n background: var(--_btn-bg);\n color: var(--_btn-color);\n border: 1px solid var(--_btn-border, transparent);\n transition: all 200ms cubic-bezier(0.25, 0.1, 0.25, 1);\n text-decoration: none;\n outline: none;\n -webkit-tap-highlight-color: transparent;\n}\n.syntra-button:hover:not(:disabled):not([data-loading]) {\n background: var(--_btn-bg-hover);\n border-color: var(--_btn-border-hover, transparent);\n color: var(--_btn-color-hover, var(--_btn-color));\n}\n.syntra-button:active:not(:disabled):not([data-loading]) {\n background: var(--_btn-bg-active);\n transform: scale(0.97);\n transition-duration: 80ms;\n}\n.syntra-button:focus-visible {\n outline: 2px solid var(--_btn-ring);\n outline-offset: 2px;\n}\n.syntra-button:disabled,\n.syntra-button[data-loading] {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.syntra-button[data-loading] {\n cursor: wait;\n pointer-events: none;\n}\n.syntra-button[data-full-width] {\n width: 100%;\n}\n.syntra-button[data-variant=\"link\"] {\n height: auto;\n padding-left: 0;\n padding-right: 0;\n background: transparent;\n border-color: transparent;\n}\n.syntra-button[data-variant=\"link\"]:hover:not(:disabled) {\n text-decoration: underline;\n background: transparent;\n}\n.syntra-button[data-variant=\"link\"]:active:not(:disabled) {\n transform: none;\n}\n.syntra-button-label[data-loading] {\n opacity: 0;\n}\n.syntra-button-spinner {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n";
6
6
  declare function getButtonSizeStyles(size: ButtonSize): CSSProperties;
7
7
  declare function getButtonVariantStyles(variant: ButtonVariant, colorScheme: ButtonColorScheme): CSSProperties;
8
8
  export { BUTTON_CSS, getButtonSizeStyles, getButtonVariantStyles };
@@ -4,3 +4,10 @@ export { Input } from './input';
4
4
  export type { InputOwnProps, InputProps, InputVariant, InputSize } from './input';
5
5
  export { Spinner } from './spinner';
6
6
  export type { SpinnerProps } from './spinner';
7
+ export { Select } from './select';
8
+ export type { SelectOwnProps, SelectOption, SelectVariant, SelectSize } from './select';
9
+ export { Tabs, TabList, Tab, TabPanel } from './tabs';
10
+ export type { TabsOwnProps, TabListOwnProps, TabOwnProps, TabPanelOwnProps, TabsVariant, TabsSize, TabsOrientation } from './tabs';
11
+ export { Toaster } from './toast';
12
+ export { toast } from './toast';
13
+ export type { ToasterProps, ToastType, ToastPosition, ToastOptions } from './toast';
@@ -1,7 +1,7 @@
1
1
  import { CSSProperties } from 'react';
2
2
  type InputVariant = 'outline' | 'filled' | 'unstyled';
3
3
  type InputSize = 'xs' | 'sm' | 'md' | 'lg';
4
- declare const INPUT_CSS = "\n.syntra-input-wrapper {\n display: inline-flex;\n align-items: center;\n position: relative;\n width: var(--_input-w, auto);\n height: var(--_input-h);\n padding-left: var(--_input-px);\n padding-right: var(--_input-px);\n gap: var(--_input-gap);\n border-radius: var(--_input-radius);\n background: var(--_input-bg);\n border: 1px solid var(--_input-border, transparent);\n font-family: inherit;\n font-size: var(--_input-fz);\n transition: border-color 150ms ease, box-shadow 150ms ease;\n}\n.syntra-input-wrapper:focus-within:not([data-disabled]):not([data-variant=\"unstyled\"]) {\n border-color: var(--_input-focus-border);\n box-shadow: 0 0 0 1px var(--_input-focus-border);\n}\n.syntra-input-wrapper[data-invalid]:not([data-disabled]) {\n border-color: var(--_input-invalid-border);\n}\n.syntra-input-wrapper[data-invalid]:focus-within:not([data-disabled]) {\n border-color: var(--_input-invalid-border);\n box-shadow: 0 0 0 1px var(--_input-invalid-border);\n}\n.syntra-input-wrapper[data-disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.syntra-input {\n flex: 1;\n min-width: 0;\n height: 100%;\n border: none;\n outline: none;\n background: transparent;\n color: inherit;\n font-family: inherit;\n font-size: inherit;\n padding: 0;\n}\n.syntra-input::placeholder {\n color: var(--syntra-color-foreground-muted);\n}\n.syntra-input:disabled {\n cursor: not-allowed;\n}\n.syntra-input-section {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n color: var(--syntra-color-foreground-muted);\n}\n";
4
+ declare const INPUT_CSS = "\n.syntra-input-wrapper {\n display: inline-flex;\n align-items: center;\n position: relative;\n width: var(--_input-w, auto);\n height: var(--_input-h);\n padding-left: var(--_input-px);\n padding-right: var(--_input-px);\n gap: var(--_input-gap);\n border-radius: var(--_input-radius);\n background: var(--_input-bg);\n border: 1px solid var(--_input-border, transparent);\n font-family: inherit;\n font-size: var(--_input-fz);\n transition: border-color 200ms cubic-bezier(0.25, 0.1, 0.25, 1),\n box-shadow 200ms cubic-bezier(0.25, 0.1, 0.25, 1),\n background 200ms cubic-bezier(0.25, 0.1, 0.25, 1);\n}\n.syntra-input-wrapper:focus-within:not([data-disabled]):not([data-variant=\"unstyled\"]) {\n border-color: var(--_input-focus-border);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--_input-focus-border) 16%, transparent);\n}\n.syntra-input-wrapper[data-invalid]:not([data-disabled]) {\n border-color: var(--_input-invalid-border);\n}\n.syntra-input-wrapper[data-invalid]:focus-within:not([data-disabled]) {\n border-color: var(--_input-invalid-border);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--_input-invalid-border) 16%, transparent);\n}\n.syntra-input-wrapper[data-disabled] {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.syntra-input {\n flex: 1;\n min-width: 0;\n height: 100%;\n border: none;\n outline: none;\n background: transparent;\n color: inherit;\n font-family: inherit;\n font-size: inherit;\n letter-spacing: -0.01em;\n padding: 0;\n}\n.syntra-input::placeholder {\n color: var(--syntra-color-foreground-subtle);\n}\n.syntra-input:disabled {\n cursor: not-allowed;\n}\n.syntra-input-section {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n color: var(--syntra-color-foreground-muted);\n}\n";
5
5
  declare function getInputSizeStyles(size: InputSize): CSSProperties;
6
6
  declare function getInputVariantStyles(variant: InputVariant): CSSProperties;
7
7
  export { INPUT_CSS, getInputSizeStyles, getInputVariantStyles };
@@ -0,0 +1,28 @@
1
+ import { CSSProperties } from 'react';
2
+ import { SyntraStyleProps } from '../../primitives/style-props';
3
+ import { SelectVariant, SelectSize } from './select.styles';
4
+ interface SelectOption {
5
+ value: string;
6
+ label: string;
7
+ disabled?: boolean;
8
+ }
9
+ interface SelectOwnProps extends SyntraStyleProps {
10
+ options: SelectOption[];
11
+ value?: string | string[];
12
+ defaultValue?: string | string[];
13
+ onChange?: (value: string | string[]) => void;
14
+ placeholder?: string;
15
+ searchable?: boolean;
16
+ multiple?: boolean;
17
+ clearable?: boolean;
18
+ variant?: SelectVariant;
19
+ size?: SelectSize;
20
+ disabled?: boolean;
21
+ invalid?: boolean;
22
+ maxDropdownHeight?: number;
23
+ className?: string;
24
+ style?: CSSProperties;
25
+ }
26
+ declare const Select: import('react').ForwardRefExoticComponent<SelectOwnProps & import('react').RefAttributes<HTMLDivElement>>;
27
+ export { Select };
28
+ export type { SelectOwnProps, SelectOption };
@@ -0,0 +1,3 @@
1
+ export { Select } from './Select';
2
+ export type { SelectOwnProps, SelectOption } from './Select';
3
+ export type { SelectVariant, SelectSize } from './select.styles';
@@ -0,0 +1,8 @@
1
+ import { CSSProperties } from 'react';
2
+ type SelectVariant = 'outline' | 'filled' | 'unstyled';
3
+ type SelectSize = 'xs' | 'sm' | 'md' | 'lg';
4
+ declare const SELECT_CSS = "\n.syntra-select-wrapper {\n display: inline-flex;\n position: relative;\n width: var(--_select-w, auto);\n}\n.syntra-select-trigger {\n display: inline-flex;\n align-items: center;\n width: 100%;\n min-height: var(--_select-h);\n padding-left: var(--_select-px);\n padding-right: var(--_select-px);\n gap: var(--_select-gap);\n border-radius: var(--_select-radius);\n background: var(--_select-bg);\n border: 1px solid var(--_select-border, transparent);\n font-family: inherit;\n font-size: var(--_select-fz);\n letter-spacing: -0.01em;\n color: inherit;\n cursor: pointer;\n outline: none;\n transition: border-color 200ms cubic-bezier(0.25, 0.1, 0.25, 1),\n box-shadow 200ms cubic-bezier(0.25, 0.1, 0.25, 1),\n background 200ms cubic-bezier(0.25, 0.1, 0.25, 1);\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n}\n.syntra-select-trigger:focus:not([data-disabled]):not([data-variant=\"unstyled\"]) {\n border-color: var(--_select-focus-border);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--_select-focus-border) 16%, transparent);\n}\n.syntra-select-trigger[data-invalid]:not([data-disabled]) {\n border-color: var(--_select-invalid-border);\n}\n.syntra-select-trigger[data-invalid]:focus:not([data-disabled]) {\n border-color: var(--_select-invalid-border);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--_select-invalid-border) 16%, transparent);\n}\n.syntra-select-trigger[data-disabled] {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.syntra-select-trigger[data-open] {\n border-color: var(--_select-focus-border);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--_select-focus-border) 16%, transparent);\n}\n.syntra-select-value {\n flex: 1;\n min-width: 0;\n text-align: left;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.syntra-select-placeholder {\n color: var(--syntra-color-foreground-subtle);\n}\n.syntra-select-tags {\n flex: 1;\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n padding: 4px 0;\n min-width: 0;\n}\n.syntra-select-tag {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n padding: 2px 8px;\n border-radius: 6px;\n background: var(--syntra-color-primary-50);\n color: var(--syntra-color-primary-600);\n font-size: 0.85em;\n font-weight: 500;\n line-height: 1.4;\n max-width: 160px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.syntra-select-tag-remove {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n border: none;\n background: none;\n padding: 0;\n cursor: pointer;\n border-radius: 4px;\n color: var(--syntra-color-primary-400);\n flex-shrink: 0;\n transition: color 100ms ease, background 100ms ease;\n}\n.syntra-select-tag-remove:hover {\n background: var(--syntra-color-primary-100);\n color: var(--syntra-color-primary-600);\n}\n.syntra-select-chevron {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n color: var(--syntra-color-foreground-subtle);\n transition: transform 250ms cubic-bezier(0.25, 0.1, 0.25, 1);\n}\n.syntra-select-chevron[data-open] {\n transform: rotate(180deg);\n}\n.syntra-select-clear {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 18px;\n height: 18px;\n border: none;\n background: none;\n padding: 0;\n cursor: pointer;\n color: var(--syntra-color-foreground-subtle);\n border-radius: 50%;\n transition: color 100ms ease, background 100ms ease;\n}\n.syntra-select-clear:hover {\n color: var(--syntra-color-foreground-default);\n background: var(--syntra-color-background-emphasis);\n}\n.syntra-select-dropdown {\n position: fixed;\n z-index: 9999;\n min-width: var(--_dropdown-min-w, 200px);\n max-height: var(--_dropdown-max-h, 250px);\n overflow-y: auto;\n overflow-x: hidden;\n background: color-mix(in srgb, var(--syntra-color-background-default) 85%, transparent);\n border: 1px solid var(--syntra-color-border-muted);\n border-radius: 12px;\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.06);\n padding: 4px;\n backdrop-filter: blur(24px) saturate(180%);\n -webkit-backdrop-filter: blur(24px) saturate(180%);\n animation: syntra-select-in 180ms cubic-bezier(0.2, 0, 0, 1);\n}\n@keyframes syntra-select-in {\n from { opacity: 0; transform: scale(0.96) translateY(-4px); }\n to { opacity: 1; transform: scale(1) translateY(0); }\n}\n.syntra-select-search {\n display: block;\n width: calc(100% - 8px);\n margin: 4px auto 4px;\n padding: 7px 10px;\n border: 1px solid var(--syntra-color-border-muted);\n border-radius: 8px;\n background: var(--syntra-color-background-default);\n font-family: inherit;\n font-size: inherit;\n color: inherit;\n outline: none;\n transition: border-color 150ms ease, box-shadow 150ms ease;\n}\n.syntra-select-search:focus {\n border-color: var(--syntra-color-primary-500);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--syntra-color-primary-500) 12%, transparent);\n}\n.syntra-select-search::placeholder {\n color: var(--syntra-color-foreground-subtle);\n}\n.syntra-select-option {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 7px 10px;\n border: none;\n background: none;\n font-family: inherit;\n font-size: inherit;\n color: inherit;\n cursor: pointer;\n border-radius: 8px;\n text-align: left;\n outline: none;\n transition: background 80ms ease;\n}\n.syntra-select-option:hover,\n.syntra-select-option[data-focused] {\n background: var(--syntra-color-primary-500);\n color: #fff;\n}\n.syntra-select-option[data-focused] .syntra-select-check {\n color: #fff;\n}\n.syntra-select-option[data-selected] {\n font-weight: 500;\n}\n.syntra-select-option[data-selected]:not(:hover):not([data-focused]) {\n color: var(--syntra-color-primary-500);\n}\n.syntra-select-option[data-disabled] {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.syntra-select-option[data-disabled]:hover {\n background: none;\n color: inherit;\n}\n.syntra-select-check {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n flex-shrink: 0;\n color: var(--syntra-color-primary-500);\n}\n.syntra-select-empty {\n padding: 16px 8px;\n text-align: center;\n color: var(--syntra-color-foreground-subtle);\n font-size: 0.9em;\n}\n";
5
+ declare function getSelectSizeStyles(size: SelectSize): CSSProperties;
6
+ declare function getSelectVariantStyles(variant: SelectVariant): CSSProperties;
7
+ export { SELECT_CSS, getSelectSizeStyles, getSelectVariantStyles };
8
+ export type { SelectVariant, SelectSize };
@@ -0,0 +1,38 @@
1
+ import { CSSProperties, ReactNode, HTMLAttributes } from 'react';
2
+ import { SyntraStyleProps } from '../../primitives/style-props';
3
+ import { TabsVariant, TabsSize, TabsOrientation } from './tabs.styles';
4
+ interface TabsOwnProps extends SyntraStyleProps {
5
+ value?: string;
6
+ defaultValue?: string;
7
+ onChange?: (value: string) => void;
8
+ variant?: TabsVariant;
9
+ size?: TabsSize;
10
+ orientation?: TabsOrientation;
11
+ children?: ReactNode;
12
+ className?: string;
13
+ style?: CSSProperties;
14
+ }
15
+ declare const Tabs: import('react').ForwardRefExoticComponent<TabsOwnProps & import('react').RefAttributes<HTMLDivElement>>;
16
+ interface TabListOwnProps {
17
+ children?: ReactNode;
18
+ className?: string;
19
+ style?: CSSProperties;
20
+ }
21
+ declare const TabList: import('react').ForwardRefExoticComponent<TabListOwnProps & Omit<HTMLAttributes<HTMLDivElement>, keyof TabListOwnProps> & import('react').RefAttributes<HTMLDivElement>>;
22
+ interface TabOwnProps {
23
+ value: string;
24
+ disabled?: boolean;
25
+ children?: ReactNode;
26
+ className?: string;
27
+ style?: CSSProperties;
28
+ }
29
+ declare const Tab: import('react').ForwardRefExoticComponent<TabOwnProps & Omit<HTMLAttributes<HTMLButtonElement>, keyof TabOwnProps> & import('react').RefAttributes<HTMLButtonElement>>;
30
+ interface TabPanelOwnProps {
31
+ value: string;
32
+ children?: ReactNode;
33
+ className?: string;
34
+ style?: CSSProperties;
35
+ }
36
+ declare const TabPanel: import('react').ForwardRefExoticComponent<TabPanelOwnProps & Omit<HTMLAttributes<HTMLDivElement>, keyof TabPanelOwnProps> & import('react').RefAttributes<HTMLDivElement>>;
37
+ export { Tabs, TabList, Tab, TabPanel };
38
+ export type { TabsOwnProps, TabListOwnProps, TabOwnProps, TabPanelOwnProps };
@@ -0,0 +1,3 @@
1
+ export { Tabs, TabList, Tab, TabPanel } from './Tabs';
2
+ export type { TabsOwnProps, TabListOwnProps, TabOwnProps, TabPanelOwnProps } from './Tabs';
3
+ export type { TabsVariant, TabsSize, TabsOrientation } from './tabs.styles';
@@ -0,0 +1,8 @@
1
+ import { CSSProperties } from 'react';
2
+ type TabsVariant = 'line' | 'pills' | 'enclosed';
3
+ type TabsSize = 'sm' | 'md' | 'lg';
4
+ type TabsOrientation = 'horizontal' | 'vertical';
5
+ declare const TABS_CSS = "\n.syntra-tabs {\n display: flex;\n flex-direction: column;\n width: 100%;\n}\n.syntra-tabs[data-orientation=\"vertical\"] {\n flex-direction: row;\n}\n\n/* \u2500\u2500 TabList \u2500\u2500 */\n.syntra-tab-list {\n display: flex;\n align-items: center;\n position: relative;\n gap: var(--_tabs-gap);\n}\n.syntra-tab-list[data-orientation=\"vertical\"] {\n flex-direction: column;\n align-items: stretch;\n}\n\n/* line variant */\n.syntra-tab-list[data-variant=\"line\"] {\n border-bottom: 1px solid var(--syntra-color-border-muted);\n}\n.syntra-tab-list[data-variant=\"line\"][data-orientation=\"vertical\"] {\n border-bottom: none;\n border-right: 1px solid var(--syntra-color-border-muted);\n}\n\n/* pills variant \u2014 Apple segmented control style */\n.syntra-tab-list[data-variant=\"pills\"] {\n background: var(--syntra-color-background-emphasis);\n border-radius: 10px;\n padding: 3px;\n gap: 2px;\n}\n\n/* enclosed variant */\n.syntra-tab-list[data-variant=\"enclosed\"] {\n border-bottom: 1px solid var(--syntra-color-border-muted);\n}\n.syntra-tab-list[data-variant=\"enclosed\"][data-orientation=\"vertical\"] {\n border-bottom: none;\n border-right: 1px solid var(--syntra-color-border-muted);\n}\n\n/* \u2500\u2500 Indicator (line variant) \u2500\u2500 */\n.syntra-tab-indicator {\n position: absolute;\n bottom: -1px;\n left: var(--_indicator-left, 0);\n width: var(--_indicator-width, 0);\n height: 2px;\n background: var(--syntra-color-primary-500);\n border-radius: 1px;\n transition: left 280ms cubic-bezier(0.4, 0, 0.2, 1),\n width 280ms cubic-bezier(0.4, 0, 0.2, 1),\n top 280ms cubic-bezier(0.4, 0, 0.2, 1),\n height 280ms cubic-bezier(0.4, 0, 0.2, 1);\n}\n.syntra-tab-list[data-orientation=\"vertical\"] .syntra-tab-indicator {\n bottom: auto;\n right: -1px;\n left: auto;\n top: var(--_indicator-top, 0);\n width: 2px;\n height: var(--_indicator-height, 0);\n}\n\n/* \u2500\u2500 Tab \u2500\u2500 */\n.syntra-tab {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n position: relative;\n white-space: nowrap;\n cursor: pointer;\n user-select: none;\n border: none;\n outline: none;\n font-family: inherit;\n font-weight: 500;\n font-size: var(--_tab-fz);\n letter-spacing: -0.01em;\n padding: var(--_tab-py) var(--_tab-px);\n color: var(--syntra-color-foreground-muted);\n background: transparent;\n transition: all 200ms cubic-bezier(0.25, 0.1, 0.25, 1);\n -webkit-tap-highlight-color: transparent;\n}\n.syntra-tab:hover:not(:disabled) {\n color: var(--syntra-color-foreground-default);\n}\n.syntra-tab:active:not(:disabled) {\n transform: scale(0.97);\n transition-duration: 80ms;\n}\n.syntra-tab:focus-visible {\n outline: 2px solid var(--syntra-color-primary-500);\n outline-offset: -2px;\n border-radius: 8px;\n}\n.syntra-tab:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n/* line variant tab */\n.syntra-tab[data-variant=\"line\"][data-selected] {\n color: var(--syntra-color-primary-500);\n}\n\n/* pills variant tab \u2014 Apple segmented control */\n.syntra-tab[data-variant=\"pills\"] {\n border-radius: 7px;\n padding: var(--_tab-py) var(--_tab-px);\n}\n.syntra-tab[data-variant=\"pills\"][data-selected] {\n background: var(--syntra-color-background-default);\n color: var(--syntra-color-foreground-default);\n font-weight: 600;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.04);\n}\n.syntra-tab[data-variant=\"pills\"]:active:not(:disabled) {\n transform: scale(0.96);\n}\n\n/* enclosed variant tab */\n.syntra-tab[data-variant=\"enclosed\"] {\n border: 1px solid transparent;\n border-bottom: none;\n border-radius: 8px 8px 0 0;\n margin-bottom: -1px;\n}\n.syntra-tab[data-variant=\"enclosed\"][data-selected] {\n background: var(--syntra-color-background-default);\n border-color: var(--syntra-color-border-muted);\n color: var(--syntra-color-foreground-default);\n font-weight: 600;\n}\n.syntra-tab[data-variant=\"enclosed\"][data-orientation=\"vertical\"] {\n border-bottom: 1px solid transparent;\n border-right: none;\n border-radius: 8px 0 0 8px;\n margin-bottom: 0;\n margin-right: -1px;\n}\n.syntra-tab[data-variant=\"enclosed\"][data-orientation=\"vertical\"][data-selected] {\n border-color: var(--syntra-color-border-muted);\n border-right-color: var(--syntra-color-background-default);\n}\n\n/* \u2500\u2500 TabPanel \u2500\u2500 */\n.syntra-tab-panel {\n padding: var(--_tabs-panel-py) 0;\n outline: none;\n}\n.syntra-tab-panel:focus-visible {\n outline: 2px solid var(--syntra-color-primary-500);\n outline-offset: 2px;\n border-radius: 8px;\n}\n";
6
+ declare function getTabsSizeStyles(size: TabsSize): CSSProperties;
7
+ export { TABS_CSS, getTabsSizeStyles };
8
+ export type { TabsVariant, TabsSize, TabsOrientation };
@@ -0,0 +1,11 @@
1
+ import { ToastType } from './toast.styles';
2
+ interface ToastProps {
3
+ id: string;
4
+ message: string;
5
+ type: ToastType;
6
+ exiting: boolean;
7
+ onDismiss: (id: string) => void;
8
+ }
9
+ declare function ToastItem({ id, message, type, exiting, onDismiss }: ToastProps): import("react/jsx-runtime").JSX.Element;
10
+ export { ToastItem };
11
+ export type { ToastProps };
@@ -0,0 +1,10 @@
1
+ import { ToastPosition } from './toast.styles';
2
+ interface ToasterProps {
3
+ position?: ToastPosition;
4
+ }
5
+ declare function Toaster({ position }: ToasterProps): import('react').ReactPortal | null;
6
+ declare namespace Toaster {
7
+ var displayName: string;
8
+ }
9
+ export { Toaster };
10
+ export type { ToasterProps };
@@ -0,0 +1,5 @@
1
+ export { Toaster } from './Toaster';
2
+ export type { ToasterProps } from './Toaster';
3
+ export { toast } from './toast-manager';
4
+ export type { ToastOptions } from './toast-manager';
5
+ export type { ToastType, ToastPosition } from './toast.styles';
@@ -0,0 +1,35 @@
1
+ import { ToastType } from './toast.styles';
2
+ interface ToastOptions {
3
+ duration?: number;
4
+ type?: ToastType;
5
+ }
6
+ interface ToastItem {
7
+ id: string;
8
+ message: string;
9
+ type: ToastType;
10
+ duration: number;
11
+ exiting: boolean;
12
+ }
13
+ type ToastListener = (toasts: ToastItem[]) => void;
14
+ declare class ToastManager {
15
+ private toasts;
16
+ private listeners;
17
+ subscribe(listener: ToastListener): () => void;
18
+ private notify;
19
+ add(message: string, options?: ToastOptions): string;
20
+ dismiss(id: string): void;
21
+ clear(): void;
22
+ getToasts(): ToastItem[];
23
+ }
24
+ declare const manager: ToastManager;
25
+ declare function toast(message: string, options?: ToastOptions): string;
26
+ declare namespace toast {
27
+ var success: (message: string, options?: Omit<ToastOptions, "type">) => string;
28
+ var error: (message: string, options?: Omit<ToastOptions, "type">) => string;
29
+ var warning: (message: string, options?: Omit<ToastOptions, "type">) => string;
30
+ var info: (message: string, options?: Omit<ToastOptions, "type">) => string;
31
+ var dismiss: (id: string) => void;
32
+ var clear: () => void;
33
+ }
34
+ export { manager, toast };
35
+ export type { ToastItem, ToastOptions, ToastListener };
@@ -0,0 +1,5 @@
1
+ type ToastType = 'default' | 'success' | 'error' | 'warning' | 'info';
2
+ type ToastPosition = 'top-right' | 'top-left' | 'top-center' | 'bottom-right' | 'bottom-left' | 'bottom-center';
3
+ declare const TOAST_CSS = "\n.syntra-toaster {\n position: fixed;\n z-index: 99999;\n display: flex;\n flex-direction: column;\n gap: 10px;\n pointer-events: none;\n padding: 20px;\n max-height: 100vh;\n overflow: hidden;\n}\n.syntra-toaster[data-position=\"top-right\"] {\n top: 0; right: 0;\n align-items: flex-end;\n}\n.syntra-toaster[data-position=\"top-left\"] {\n top: 0; left: 0;\n align-items: flex-start;\n}\n.syntra-toaster[data-position=\"top-center\"] {\n top: 0; left: 50%;\n transform: translateX(-50%);\n align-items: center;\n}\n.syntra-toaster[data-position=\"bottom-right\"] {\n bottom: 0; right: 0;\n align-items: flex-end;\n flex-direction: column-reverse;\n}\n.syntra-toaster[data-position=\"bottom-left\"] {\n bottom: 0; left: 0;\n align-items: flex-start;\n flex-direction: column-reverse;\n}\n.syntra-toaster[data-position=\"bottom-center\"] {\n bottom: 0; left: 50%;\n transform: translateX(-50%);\n align-items: center;\n flex-direction: column-reverse;\n}\n\n/* \u2500\u2500 Toast \u2500\u2500 */\n.syntra-toast {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n min-width: 320px;\n max-width: 480px;\n padding: 14px 16px;\n border-radius: 14px;\n background: color-mix(in srgb, var(--syntra-color-background-default) 80%, transparent);\n border: 1px solid var(--syntra-color-border-muted);\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.06);\n backdrop-filter: blur(24px) saturate(180%);\n -webkit-backdrop-filter: blur(24px) saturate(180%);\n pointer-events: auto;\n font-family: inherit;\n font-size: var(--syntra-font-fontSize-sm);\n letter-spacing: -0.01em;\n color: var(--syntra-color-foreground-default);\n animation: syntra-toast-in 300ms cubic-bezier(0.2, 0, 0, 1) forwards;\n}\n.syntra-toast[data-exiting] {\n animation: syntra-toast-out 200ms cubic-bezier(0.4, 0, 1, 1) forwards;\n}\n\n/* Slide directions */\n@keyframes syntra-toast-in {\n from { opacity: 0; transform: translateX(calc(100% + 20px)); }\n to { opacity: 1; transform: translateX(0); }\n}\n@keyframes syntra-toast-out {\n from { opacity: 1; transform: translateX(0); }\n to { opacity: 0; transform: translateX(calc(100% + 20px)); }\n}\n.syntra-toaster[data-position=\"top-left\"] .syntra-toast,\n.syntra-toaster[data-position=\"bottom-left\"] .syntra-toast {\n animation-name: syntra-toast-in-left;\n}\n.syntra-toaster[data-position=\"top-left\"] .syntra-toast[data-exiting],\n.syntra-toaster[data-position=\"bottom-left\"] .syntra-toast[data-exiting] {\n animation-name: syntra-toast-out-left;\n}\n.syntra-toaster[data-position=\"top-center\"] .syntra-toast {\n animation-name: syntra-toast-in-top;\n}\n.syntra-toaster[data-position=\"top-center\"] .syntra-toast[data-exiting] {\n animation-name: syntra-toast-out-top;\n}\n.syntra-toaster[data-position=\"bottom-center\"] .syntra-toast {\n animation-name: syntra-toast-in-bottom;\n}\n.syntra-toaster[data-position=\"bottom-center\"] .syntra-toast[data-exiting] {\n animation-name: syntra-toast-out-bottom;\n}\n\n@keyframes syntra-toast-in-left {\n from { opacity: 0; transform: translateX(calc(-100% - 20px)); }\n to { opacity: 1; transform: translateX(0); }\n}\n@keyframes syntra-toast-out-left {\n from { opacity: 1; transform: translateX(0); }\n to { opacity: 0; transform: translateX(calc(-100% - 20px)); }\n}\n@keyframes syntra-toast-in-top {\n from { opacity: 0; transform: translateY(-100%); }\n to { opacity: 1; transform: translateY(0); }\n}\n@keyframes syntra-toast-out-top {\n from { opacity: 1; transform: translateY(0); }\n to { opacity: 0; transform: translateY(-100%); }\n}\n@keyframes syntra-toast-in-bottom {\n from { opacity: 0; transform: translateY(100%); }\n to { opacity: 1; transform: translateY(0); }\n}\n@keyframes syntra-toast-out-bottom {\n from { opacity: 1; transform: translateY(0); }\n to { opacity: 0; transform: translateY(100%); }\n}\n\n/* \u2500\u2500 Icon \u2500\u2500 */\n.syntra-toast-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 20px;\n height: 20px;\n margin-top: 1px;\n}\n.syntra-toast-icon[data-type=\"success\"] { color: var(--syntra-color-green-500); }\n.syntra-toast-icon[data-type=\"error\"] { color: var(--syntra-color-red-500); }\n.syntra-toast-icon[data-type=\"warning\"] { color: var(--syntra-color-orange-500); }\n.syntra-toast-icon[data-type=\"info\"] { color: var(--syntra-color-blue-500); }\n\n/* \u2500\u2500 Content \u2500\u2500 */\n.syntra-toast-content {\n flex: 1;\n min-width: 0;\n line-height: 1.5;\n padding-top: 1px;\n}\n\n/* \u2500\u2500 Close \u2500\u2500 */\n.syntra-toast-close {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 24px;\n height: 24px;\n border: none;\n background: none;\n padding: 0;\n cursor: pointer;\n color: var(--syntra-color-foreground-subtle);\n border-radius: 6px;\n margin-top: -1px;\n transition: background 100ms ease, color 100ms ease;\n}\n.syntra-toast-close:hover {\n background: var(--syntra-color-background-emphasis);\n color: var(--syntra-color-foreground-default);\n}\n.syntra-toast-close:active {\n transform: scale(0.9);\n}\n";
4
+ export { TOAST_CSS };
5
+ export type { ToastType, ToastPosition };
@@ -0,0 +1,2 @@
1
+ import { RefObject } from 'react';
2
+ export declare function useClickOutside(ref: RefObject<Element | null>, handler: () => void, enabled?: boolean): void;
@@ -0,0 +1 @@
1
+ export declare function useControllableState<T>(controlledValue: T | undefined, defaultValue: T, onChange?: (value: T) => void): [T, (value: T | ((prev: T) => T)) => void];
package/dist/index.d.ts CHANGED
@@ -4,8 +4,8 @@ export { useSyntra } from './hooks/useSyntra';
4
4
  export { useSyntraMode } from './hooks/useSyntraMode';
5
5
  export { Box, Text, Heading, Flex, Stack, Grid, Divider, VisuallyHidden, resolveStyleProps, extractStyleProps, } from './primitives';
6
6
  export type { BoxOwnProps, TextOwnProps, HeadingOwnProps, HeadingLevel, FlexOwnProps, StackOwnProps, GridOwnProps, DividerOwnProps, VisuallyHiddenOwnProps, SyntraStyleProps, SpaceProp, RadiusProp, ShadowProp, PolymorphicProps, PolymorphicPropsWithRef, PolymorphicComponent, } from './primitives';
7
- export { Button, Input, Spinner } from './components';
8
- export type { ButtonOwnProps, ButtonVariant, ButtonSize, ButtonColorScheme, InputOwnProps, InputProps, InputVariant, InputSize, SpinnerProps, } from './components';
7
+ export { Button, Input, Spinner, Select, Tabs, TabList, Tab, TabPanel, Toaster, toast } from './components';
8
+ export type { ButtonOwnProps, ButtonVariant, ButtonSize, ButtonColorScheme, InputOwnProps, InputProps, InputVariant, InputSize, SpinnerProps, SelectOwnProps, SelectOption, SelectVariant, SelectSize, TabsOwnProps, TabListOwnProps, TabOwnProps, TabPanelOwnProps, TabsVariant, TabsSize, TabsOrientation, ToasterProps, ToastType, ToastPosition, ToastOptions, } from './components';
9
9
  export { cn } from './utils/cn';
10
10
  export { createSyntraTheme } from './utils/createSyntraTheme';
11
11
  export { generateColorScale } from './utils/colorScale';