@stackloop/ui 1.0.10 → 2.0.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.
package/README.md CHANGED
@@ -451,6 +451,96 @@ You can add dark mode variants:
451
451
  <Dropdown options={[{value:'a',label:'A'}]} value={val} onChange={setVal} searchable />
452
452
  ```
453
453
 
454
+ **Select**:
455
+ - **Description:** Form-optimized select component with label, error, hint, and validation support. Built for forms with proper semantics and accessibility. Based on Dropdown but includes form-specific features like `required` prop, hint text, and better integration with form libraries.
456
+ - **Props:**
457
+ - **`options`**: `{ value: string; label: string; icon?: ReactNode; disabled?: boolean }[]` — required. Array of selectable options with optional icons and disabled state.
458
+ - **`value`**: `string` — optional. Currently selected value.
459
+ - **`onChange`**: `(value: string) => void` — required. Callback fired when selection changes.
460
+ - **`placeholder`**: `string` — default: `'Select an option'`. Shown when no value is selected.
461
+ - **`label`**: `string` — optional. Label displayed above the select.
462
+ - **`error`**: `string` — optional. Error message displayed below select with error styling.
463
+ - **`hint`**: `string` — optional. Helper text displayed below select when no error is present.
464
+ - **`searchable`**: `boolean` — default: `false`. Enables search input to filter options.
465
+ - **`clearable`**: `boolean` — default: `true`. Shows clear button when value is selected.
466
+ - **`required`**: `boolean` — optional. Displays asterisk (*) next to label and sets aria-required.
467
+ - **`disabled`**: `boolean` — optional. Disables the select.
468
+ - **`className`**: `string` — optional. Additional CSS classes for the wrapper.
469
+ - **Features:**
470
+ - **Form Integration:** Works seamlessly with form libraries (React Hook Form, Formik, etc.)
471
+ - **Validation Support:** Built-in error and hint display with animated transitions
472
+ - **Accessibility:** Full ARIA attributes, keyboard navigation, and screen reader support
473
+ - **Search:** Optional searchable mode with real-time filtering
474
+ - **Icons:** Support for icons in options for better visual recognition
475
+ - **Disabled Options:** Individual options can be disabled while keeping select active
476
+ - **Clearable:** Optional clear button to reset selection
477
+ - **Touch-Friendly:** Optimized for mobile with proper touch targets
478
+ - **Usage:**
479
+
480
+ ```jsx
481
+ import { Select } from '@stackloop/ui'
482
+ import { User, Settings, Bell } from 'lucide-react'
483
+
484
+ // Basic usage
485
+ <Select
486
+ label="Country"
487
+ options={[
488
+ { value: 'us', label: 'United States' },
489
+ { value: 'uk', label: 'United Kingdom' },
490
+ { value: 'ca', label: 'Canada' }
491
+ ]}
492
+ value={country}
493
+ onChange={setCountry}
494
+ required
495
+ />
496
+
497
+ // With icons and search
498
+ <Select
499
+ label="Navigation"
500
+ options={[
501
+ { value: 'profile', label: 'Profile', icon: <User /> },
502
+ { value: 'settings', label: 'Settings', icon: <Settings /> },
503
+ { value: 'notifications', label: 'Notifications', icon: <Bell />, disabled: true }
504
+ ]}
505
+ value={selected}
506
+ onChange={setSelected}
507
+ searchable
508
+ placeholder="Choose a page"
509
+ />
510
+
511
+ // With error and hint
512
+ <Select
513
+ label="Payment Method"
514
+ options={paymentMethods}
515
+ value={payment}
516
+ onChange={setPayment}
517
+ error={errors.payment}
518
+ hint="Select your preferred payment method"
519
+ required
520
+ />
521
+
522
+ // With React Hook Form
523
+ import { useForm, Controller } from 'react-hook-form'
524
+
525
+ const { control, formState: { errors } } = useForm()
526
+
527
+ <Controller
528
+ name="category"
529
+ control={control}
530
+ rules={{ required: 'Category is required' }}
531
+ render={({ field }) => (
532
+ <Select
533
+ label="Category"
534
+ options={categories}
535
+ value={field.value}
536
+ onChange={field.onChange}
537
+ error={errors.category?.message}
538
+ required
539
+ />
540
+ )}
541
+ />
542
+ ```
543
+
454
544
  **BottomSheet**:
455
545
  - **Description:** Mobile bottom sheet with header and optional close button.
456
546
  - **Props:**
@@ -528,6 +618,129 @@ You can add dark mode variants:
528
618
  <Badge variant="primary">New</Badge>
529
619
  ```
530
620
 
621
+ **Spinner**:
622
+ - **Description:** Animated loading spinner with size variants and optional label.
623
+ - **Props:**
624
+ - **`size`**: `'sm' | 'md' | 'lg' | 'xl'` — default: `'md'`. Controls spinner dimensions (sm=16px, md=32px, lg=48px, xl=64px).
625
+ - **`variant`**: `'primary' | 'secondary' | 'white'` — default: `'primary'`. Color variant of the spinner.
626
+ - **`label`**: `string` — optional. Text displayed below spinner.
627
+ - **`className`**: `string` — optional. Additional CSS classes for the wrapper.
628
+ - **Usage:**
629
+
630
+ ```jsx
631
+ import { Spinner } from '@stackloop/ui'
632
+
633
+ // Basic spinner
634
+ <Spinner />
635
+
636
+ // With label
637
+ <Spinner label="Loading..." />
638
+
639
+ // Custom size and variant
640
+ <Spinner size="lg" variant="white" />
641
+
642
+ // In a button
643
+ <Button disabled>
644
+ <Spinner size="sm" variant="white" label="Processing..." />
645
+ </Button>
646
+ ```
647
+
648
+ **Toast**:
649
+ - **Description:** Context-based toast notification system with variants, actions, and customizable positioning. Provides non-intrusive feedback messages that auto-dismiss.
650
+ - **Components:**
651
+ - **`ToastProvider`**: Wrapper component that manages toast state and rendering. Must wrap your app or components that use toasts.
652
+ - **`useToast`**: Hook to trigger toasts from any component within the provider.
653
+ - **ToastProvider Props:**
654
+ - **`children`**: `ReactNode` — required. Your app content.
655
+ - **`position`**: `'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'` — default: `'top-right'`. Where toasts appear on screen.
656
+ - **`maxToasts`**: `number` — default: `5`. Maximum number of toasts shown at once.
657
+ - **useToast() Returns:**
658
+ - **`addToast(toast)`**: Function to create a new toast notification.
659
+ - **`removeToast(id)`**: Function to manually dismiss a toast.
660
+ - **`toasts`**: Array of currently active toasts.
661
+ - **Toast Object:**
662
+ - **`message`**: `string` — required. Toast content text.
663
+ - **`variant`**: `'success' | 'error' | 'warning' | 'info' | 'default'` — optional (default: `'default'`). Visual style with corresponding icon.
664
+ - **`duration`**: `number` — optional (default: `5000`ms). Auto-dismiss time in milliseconds. Set to `0` for persistent toast.
665
+ - **`action`**: `{ label: string; onClick: () => void }` — optional. Action button within the toast.
666
+ - **Features:**
667
+ - **Auto-dismiss**: Toasts automatically disappear after duration
668
+ - **Manual dismiss**: Click X button or call `removeToast(id)`
669
+ - **Variants with icons**: Visual feedback with CheckCircle, AlertCircle, AlertTriangle, Info icons
670
+ - **Action buttons**: Add clickable actions to toasts
671
+ - **Animations**: Smooth enter/exit animations with Framer Motion
672
+ - **Position control**: Place toasts anywhere on screen
673
+ - **Max limit**: Prevents toast overflow
674
+ - **Responsive**: Adapts to mobile screens
675
+ - **Usage:**
676
+
677
+ ```jsx
678
+ import { ToastProvider, useToast } from '@stackloop/ui'
679
+
680
+ // 1. Wrap your app with ToastProvider
681
+ function App() {
682
+ return (
683
+ <ToastProvider position="top-right" maxToasts={5}>
684
+ <YourApp />
685
+ </ToastProvider>
686
+ )
687
+ }
688
+
689
+ // 2. Use toasts in any component
690
+ function MyComponent() {
691
+ const { addToast } = useToast()
692
+
693
+ const handleSuccess = () => {
694
+ addToast({
695
+ message: 'Profile updated successfully!',
696
+ variant: 'success',
697
+ duration: 3000
698
+ })
699
+ }
700
+
701
+ const handleError = () => {
702
+ addToast({
703
+ message: 'Failed to save changes',
704
+ variant: 'error',
705
+ duration: 5000
706
+ })
707
+ }
708
+
709
+ const handleWithAction = () => {
710
+ addToast({
711
+ message: 'New message received',
712
+ variant: 'info',
713
+ duration: 0, // Persistent until dismissed
714
+ action: {
715
+ label: 'View',
716
+ onClick: () => navigate('/messages')
717
+ }
718
+ })
719
+ }
720
+
721
+ const handleWarning = () => {
722
+ addToast({
723
+ message: 'Your session will expire in 5 minutes',
724
+ variant: 'warning'
725
+ })
726
+ }
727
+
728
+ return (
729
+ <div>
730
+ <Button onClick={handleSuccess}>Save</Button>
731
+ <Button onClick={handleError}>Trigger Error</Button>
732
+ <Button onClick={handleWithAction}>Show Notification</Button>
733
+ <Button onClick={handleWarning}>Show Warning</Button>
734
+ </div>
735
+ )
736
+ }
737
+
738
+ // 3. Bottom-centered positioning
739
+ <ToastProvider position="bottom-center">
740
+ <App />
741
+ </ToastProvider>
742
+ ```
743
+
531
744
  **FloatingActionButton (FAB)**:
532
745
  - **Description:** Floating action button with expanded action list support.
533
746
  - **Props:**
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ export interface SelectOption {
3
+ value: string;
4
+ label: string;
5
+ icon?: React.ReactNode;
6
+ disabled?: boolean;
7
+ }
8
+ export interface SelectProps extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, 'onChange' | 'size'> {
9
+ options: SelectOption[];
10
+ value?: string;
11
+ onChange: (value: string) => void;
12
+ placeholder?: string;
13
+ label?: string;
14
+ error?: string;
15
+ hint?: string;
16
+ searchable?: boolean;
17
+ clearable?: boolean;
18
+ className?: string;
19
+ }
20
+ export declare const Select: React.ForwardRefExoticComponent<SelectProps & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ export interface SpinnerProps {
3
+ size?: 'sm' | 'md' | 'lg' | 'xl';
4
+ variant?: 'primary' | 'secondary' | 'white';
5
+ className?: string;
6
+ label?: string;
7
+ }
8
+ export declare const Spinner: React.FC<SpinnerProps>;
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+ export type ToastVariant = 'success' | 'error' | 'warning' | 'info' | 'default';
3
+ export type ToastPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
4
+ export interface Toast {
5
+ id: string;
6
+ message: string;
7
+ variant?: ToastVariant;
8
+ duration?: number;
9
+ action?: {
10
+ label: string;
11
+ onClick: () => void;
12
+ };
13
+ }
14
+ interface ToastContextType {
15
+ toasts: Toast[];
16
+ addToast: (toast: Omit<Toast, 'id'>) => void;
17
+ removeToast: (id: string) => void;
18
+ }
19
+ export declare const useToast: () => ToastContextType;
20
+ interface ToastProviderProps {
21
+ children: React.ReactNode;
22
+ position?: ToastPosition;
23
+ maxToasts?: number;
24
+ }
25
+ export declare const ToastProvider: React.FC<ToastProviderProps>;
26
+ export {};