@andreyfedkovich/cozy-ui 0.6.2 → 0.7.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/CHANGELOG.md CHANGED
@@ -3,6 +3,13 @@
3
3
  Формат основан на [Keep a Changelog](https://keepachangelog.com/).
4
4
  Версии соответствуют [Semantic Versioning](https://semver.org/) и git-тегам `v*`.
5
5
 
6
+ ## 0.7.0 - 2026-05-26
7
+
8
+ - **feat:** `SettingsView` — composition-first layout for settings pages (`SettingsView.Section`, `.Group`, `.Item`, `.Divider`), declarative `sections`, variants `classic` / `elevated`, densities `comfortable` / `compact`, collapsible sections, left icon badges, row badges (e.g. New/Beta), link rows (`href`, `external`), danger styling, and `render` for full row customization.
9
+ - **feat:** `Switch` — iOS-style toggle (green by default, white thumb with shadow), controlled / uncontrolled, sizes `sm` / `md`, optional `blue` color.
10
+ - **feat:** `ImageSegmented` — premium segmented control with image previews (Agent/Editor-style); `image` accepts any `ReactNode`.
11
+ - **feat:** `SideNav` — `transparent` variant (seamless panel, compact rows, no chrome).
12
+
6
13
  ## 0.6.0 - 2026-05-21
7
14
 
8
15
  - **feat:** `SideNav` — vertical navigation with `classic` and `aurora` variants, optional collapse, declarative `sections`, composition API (`SideNav.Section`, `SideNav.Item`, `SideNav.Divider`, `SideNav.Custom`), user block, and nested submenu items.
package/README.md CHANGED
@@ -35,7 +35,7 @@ npm i @andreyfedkovich/cozy-ui
35
35
  - [Navigation](#navigation) — `Tabs`, `TabsRounded`, `Stepper`
36
36
  - [Overlays](#overlays) — `Popover`, `TooltipDark`, `TooltipLight`
37
37
  - [Utility](#utility) — `Tag`, `CopyTextTrigger`
38
- - [Workflow](#workflow) — `ApprovalRoute`, `CommentFeed`, `DetailView`, `SideNav`
38
+ - [Workflow](#workflow) — `ApprovalRoute`, `CommentFeed`, `DetailView`, `SideNav`, `SettingsView`, `Switch`, `ImageSegmented`
39
39
  - [Hooks & helpers](#hooks--helpers)
40
40
  - [Icons](#icons)
41
41
  - [TypeScript](#typescript)
@@ -806,7 +806,7 @@ Edit mode:
806
806
 
807
807
  #### `SideNav`
808
808
 
809
- Premium vertical navigation panel with a user block on top, configurable sections, optional collapse and two visual variants — `classic` (light, reference-style) and `aurora` (deep gradient with glass and glow accents). Composition-first like `DetailView`.
809
+ Premium vertical navigation panel with a user block on top, configurable sections, optional collapse and three visual variants — `classic` (light, reference-style panel), `transparent` (seamless, no panel chrome, compact rows — feels like part of the page) and `aurora` (deep gradient with glass and glow accents). Composition-first like `DetailView`.
810
810
 
811
811
  | Prop | Type | Default | Description |
812
812
  | ------------------- | ------------------------------------- | ----------- | -------------------------------------------------------------------- |
@@ -814,7 +814,7 @@ Premium vertical navigation panel with a user block on top, configurable section
814
814
  | `userSlot` | `ReactNode` | — | Fully custom header content (overrides `user`). |
815
815
  | `sections` | `SideNavSection[]` | — | Declarative sections, each with `title` and `items`. |
816
816
  | `children` | `ReactNode` | — | Composition: `SideNav.Section`, `SideNav.Item`, `SideNav.Divider`, `SideNav.Custom`. |
817
- | `variant` | `"classic" \| "aurora"` | `"classic"` | Visual style switch. |
817
+ | `variant` | `"classic" \| "transparent" \| "aurora"` | `"classic"` | Visual style switch. |
818
818
  | `activeId` / `defaultActiveId` | `string` | — | Controlled / uncontrolled active item. |
819
819
  | `onActiveChange` | `(id: string) => void` | — | Fires when an item is selected. |
820
820
  | `collapsible` | `boolean` | `false` | Shows a collapse toggle in the header. |
@@ -864,6 +864,158 @@ Composition-first (like `DetailView`):
864
864
 
865
865
  ---
866
866
 
867
+ #### `SettingsView`
868
+
869
+ Composition-first layout for settings pages (like `DetailView`). Supports declarative `sections` or JSX via `SettingsView.Section`, `SettingsView.Group`, `SettingsView.Item`, and `SettingsView.Divider`. Two visual variants (`classic` / `elevated`), two densities (`comfortable` / `compact`), collapsible sections, left icon badges, row badges (e.g. New/Beta), link rows (`href`, `external`), danger styling, and `render` for full row customization.
870
+
871
+ | Prop | Type | Default | Description |
872
+ | ------------- | ----------------------------------------- | --------------- | -------------------------------------------------------- |
873
+ | `sections` | `SettingsSection[]` | — | Declarative sections with items and optional groups. |
874
+ | `children` | `ReactNode` | — | Composition API subcomponents. |
875
+ | `variant` | `"classic" \| "elevated"` | `"classic"` | Visual style. |
876
+ | `density` | `"comfortable" \| "compact"` | `"comfortable"` | Row spacing. |
877
+ | `layout` | `"card" \| "plain"` | `"card"` | Section wrapper style. |
878
+ | `loading` | `boolean` | — | Shows a spinner instead of content. |
879
+ | `emptyState` | `ReactNode` | — | Custom empty state when there is no content. |
880
+
881
+ Each `SettingsItem` (in `sections` or `<SettingsView.Item />`) supports `icon`, `label`, `description`, `control`, `badge`, `hint`, `href`, `external`, `danger`, `disabled`, `onClick`, and `render` for full row customization.
882
+
883
+ ```tsx
884
+ import { SettingsView, Switch } from "@andreyfedkovich/cozy-ui";
885
+
886
+ <SettingsView
887
+ variant="classic"
888
+ density="comfortable"
889
+ sections={[
890
+ {
891
+ id: "general",
892
+ title: "General",
893
+ items: [
894
+ {
895
+ id: "notifications",
896
+ label: "Notifications",
897
+ description: "Email and push alerts",
898
+ control: <Switch defaultChecked ariaLabel="Notifications" />,
899
+ },
900
+ {
901
+ id: "import",
902
+ label: "Import from VS Code",
903
+ description: "Settings, extensions, and keybindings",
904
+ badge: "New",
905
+ },
906
+ {
907
+ id: "docs",
908
+ label: "Documentation",
909
+ href: "https://example.com/docs",
910
+ external: true,
911
+ },
912
+ {
913
+ id: "delete",
914
+ label: "Delete account",
915
+ description: "Permanently remove your data",
916
+ danger: true,
917
+ onClick: () => confirm("Delete account?"),
918
+ },
919
+ ],
920
+ },
921
+ ]}
922
+ />;
923
+ ```
924
+
925
+ Composition-first:
926
+
927
+ ```tsx
928
+ <SettingsView variant="elevated">
929
+ <SettingsView.Section title="Privacy" collapsible defaultOpen>
930
+ <SettingsView.Item label="Analytics" control={<Switch ariaLabel="Analytics" />} />
931
+ <SettingsView.Divider />
932
+ <SettingsView.Group title="Advanced">
933
+ <SettingsView.Item label="Debug mode" control={<Switch size="sm" ariaLabel="Debug" />} />
934
+ </SettingsView.Group>
935
+ </SettingsView.Section>
936
+ </SettingsView>
937
+ ```
938
+
939
+ ---
940
+
941
+ #### `Switch`
942
+
943
+ iOS-style toggle with a green track by default, white thumb and shadow. Controlled or uncontrolled; sizes `sm` / `md`; optional `blue` accent.
944
+
945
+ | Prop | Type | Default | Description |
946
+ | ----------------- | --------------------------- | --------- | ------------------------------ |
947
+ | `checked` | `boolean` | — | Controlled value. |
948
+ | `defaultChecked` | `boolean` | — | Initial value (uncontrolled). |
949
+ | `onChange` | `(next: boolean) => void` | — | Fires when toggled. |
950
+ | `size` | `"sm" \| "md"` | `"md"` | Track and thumb size. |
951
+ | `color` | `"green" \| "blue"` | `"green"` | Active track color. |
952
+ | `disabled` | `boolean` | — | Disables interaction. |
953
+ | `ariaLabel` | `string` | — | Accessible name. |
954
+
955
+ ```tsx
956
+ import { useState } from "react";
957
+ import { Switch } from "@andreyfedkovich/cozy-ui";
958
+
959
+ // Uncontrolled
960
+ <Switch defaultChecked ariaLabel="Dark mode" onChange={(v) => console.log(v)} />
961
+
962
+ // Controlled
963
+ const [on, setOn] = useState(true);
964
+ <Switch checked={on} onChange={setOn} size="sm" color="blue" ariaLabel="Sync" />
965
+ ```
966
+
967
+ ---
968
+
969
+ #### `ImageSegmented`
970
+
971
+ Premium segmented control with image previews (Agent/Editor-style). Each option accepts any `ReactNode` in `image` — SVG, screenshot, or custom markup.
972
+
973
+ | Prop | Type | Default | Description |
974
+ | ---------- | ----------------------------------------- | ------- | ------------------------------------ |
975
+ | `value` | `T extends string` | — | Selected option value (controlled). |
976
+ | `onChange` | `(next: T) => void` | — | Fires when selection changes. |
977
+ | `options` | `ImageSegmentedOption<T>[]` | — | `value`, `label`, `image`, `disabled?`. |
978
+ | `size` | `"sm" \| "md"` | `"md"` | Option size. |
979
+ | `ariaLabel`| `string` | — | Accessible name for the radiogroup. |
980
+
981
+ ```tsx
982
+ import { useState } from "react";
983
+ import { ImageSegmented } from "@andreyfedkovich/cozy-ui";
984
+
985
+ const [layout, setLayout] = useState<"agent" | "editor">("agent");
986
+
987
+ <ImageSegmented
988
+ ariaLabel="Window layout"
989
+ value={layout}
990
+ onChange={setLayout}
991
+ options={[
992
+ {
993
+ value: "agent",
994
+ label: "Agent",
995
+ image: (
996
+ <svg viewBox="0 0 48 32" aria-hidden>
997
+ <rect width="48" height="32" rx="3" fill="#eef3fb" />
998
+ <rect x="3" y="3" width="18" height="26" rx="2" fill="#4573d9" opacity="0.85" />
999
+ <rect x="23" y="3" width="22" height="26" rx="2" fill="#fff" stroke="#dde5f5" />
1000
+ </svg>
1001
+ ),
1002
+ },
1003
+ {
1004
+ value: "editor",
1005
+ label: "Editor",
1006
+ image: (
1007
+ <svg viewBox="0 0 48 32" aria-hidden>
1008
+ <rect width="48" height="32" rx="3" fill="#eef3fb" />
1009
+ <rect x="3" y="3" width="42" height="26" rx="2" fill="#fff" stroke="#dde5f5" />
1010
+ </svg>
1011
+ ),
1012
+ },
1013
+ ]}
1014
+ />;
1015
+ ```
1016
+
1017
+ ---
1018
+
867
1019
  ## Hooks & helpers
868
1020
 
869
1021
  ### `useMeasureElement`
@@ -414,7 +414,6 @@ declare type CustomSelectProps<T, S> = {
414
414
  onClose?: () => void;
415
415
  portalTarget?: Element;
416
416
  error?: string | null;
417
- fixedHeight?: boolean;
418
417
  template?: "list" | "table";
419
418
  columns?: SelectColumn<T, S>[];
420
419
  total?: number;
@@ -524,6 +523,10 @@ declare const DividerEl_2: FC<{
524
523
  className?: string;
525
524
  }>;
526
525
 
526
+ declare const DividerEl_3: FC<{
527
+ className?: string;
528
+ }>;
529
+
527
530
  export { DoneIcon }
528
531
 
529
532
  export { DownloadIcon }
@@ -570,6 +573,8 @@ export { GraduateIcon }
570
573
 
571
574
  export { GridIcon }
572
575
 
576
+ declare const GroupBlock: FC<SettingsGroup>;
577
+
573
578
  export { HeartIcon }
574
579
 
575
580
  export { HelpIcon }
@@ -578,6 +583,24 @@ export { HistoryBlue }
578
583
 
579
584
  export { HomeIcon }
580
585
 
586
+ export declare function ImageSegmented<T extends string = string>({ value, onChange, options, size, ariaLabel, className, }: ImageSegmentedProps<T>): JSX.Element;
587
+
588
+ export declare interface ImageSegmentedOption<T extends string = string> {
589
+ value: T;
590
+ label: ReactNode;
591
+ image: ReactNode;
592
+ disabled?: boolean;
593
+ }
594
+
595
+ export declare interface ImageSegmentedProps<T extends string = string> {
596
+ value: T;
597
+ onChange: (next: T) => void;
598
+ options: ImageSegmentedOption<T>[];
599
+ size?: "sm" | "md";
600
+ ariaLabel?: string;
601
+ className?: string;
602
+ }
603
+
581
604
  export { InfoIcon }
582
605
 
583
606
  export declare const Input: ForwardRefExoticComponent<InputProps & RefAttributes<HTMLInputElement>>;
@@ -605,10 +628,15 @@ export declare interface InputProps extends InputHTMLAttributes<HTMLInputElement
605
628
 
606
629
  export { IslandIcon }
607
630
 
631
+ declare interface ItemComponentProps extends SettingsItem {
632
+ }
633
+
608
634
  declare interface ItemProps extends SideNavItem {
609
635
  level?: number;
610
636
  }
611
637
 
638
+ declare const ItemRow: FC<ItemComponentProps>;
639
+
612
640
  export declare const Label: ({ htmlFor, children, className }: LabelProps) => JSX.Element;
613
641
 
614
642
  declare interface LabelProps {
@@ -757,6 +785,8 @@ export { SearchIcon }
757
785
 
758
786
  declare const SectionBlock: FC<SectionComponentProps>;
759
787
 
788
+ declare const SectionBlock_2: FC<SettingsSection>;
789
+
760
790
  declare interface SectionComponentProps extends Omit<DetailSection, "fields"> {
761
791
  fields?: DetailField[];
762
792
  children?: ReactNode;
@@ -765,7 +795,7 @@ declare interface SectionComponentProps extends Omit<DetailSection, "fields"> {
765
795
  declare interface SectionProps extends SideNavSection {
766
796
  }
767
797
 
768
- export declare const Select: <T, S extends string | number>({ options, value, mode, placeholder, onChange, dropdownRender, optionRender, selectedOptionRender, dropdownIcon, tagRender, dropDownClassName, optionClassName, inputClassName, deleteIconClassName, onDelete, onClear, label, tooltipContent, tooltipPopperClassName, onSearch, searchClassName, searchPlaceholder, isLoading, disabled, onClose, portalTarget, error, fixedHeight, template, columns, total, }: CustomSelectProps<T, S>) => JSX.Element;
798
+ export declare const Select: <T, S extends string | number>({ options, value, mode, placeholder, onChange, dropdownRender, optionRender, selectedOptionRender, dropdownIcon, tagRender, dropDownClassName, optionClassName, inputClassName, deleteIconClassName, onDelete, onClear, label, tooltipContent, tooltipPopperClassName, onSearch, searchClassName, searchPlaceholder, isLoading, disabled, onClose, portalTarget, error, template, columns, total, }: CustomSelectProps<T, S>) => JSX.Element;
769
799
 
770
800
  export declare type SelectColumn<T, S> = {
771
801
  key: string;
@@ -774,8 +804,77 @@ export declare type SelectColumn<T, S> = {
774
804
  render: (option: CustomOption<T, S>) => ReactNode;
775
805
  };
776
806
 
807
+ export declare type SettingsDensity = "comfortable" | "compact";
808
+
809
+ export declare interface SettingsGroup {
810
+ id?: string;
811
+ title?: ReactNode;
812
+ items?: SettingsItem[];
813
+ children?: ReactNode;
814
+ className?: string;
815
+ }
816
+
777
817
  export { SettingsIcon }
778
818
 
819
+ export declare interface SettingsItem {
820
+ id?: string;
821
+ icon?: ReactNode;
822
+ label: ReactNode;
823
+ description?: ReactNode;
824
+ control?: ReactNode;
825
+ badge?: ReactNode;
826
+ hint?: ReactNode;
827
+ href?: string;
828
+ external?: boolean;
829
+ onClick?: (e: MouseEvent_2<HTMLElement>) => void;
830
+ disabled?: boolean;
831
+ danger?: boolean;
832
+ hidden?: boolean;
833
+ align?: "center" | "start";
834
+ className?: string;
835
+ render?: (ctx: {
836
+ label: ReactNode;
837
+ control: ReactNode;
838
+ }) => ReactNode;
839
+ }
840
+
841
+ export declare type SettingsLayout = "card" | "plain";
842
+
843
+ export declare interface SettingsSection {
844
+ id?: string;
845
+ title?: ReactNode;
846
+ description?: ReactNode;
847
+ items?: SettingsItem[];
848
+ groups?: SettingsGroup[];
849
+ children?: ReactNode;
850
+ collapsible?: boolean;
851
+ defaultOpen?: boolean;
852
+ className?: string;
853
+ }
854
+
855
+ export declare type SettingsVariant = "classic" | "elevated";
856
+
857
+ export declare const SettingsView: SettingsViewComponent;
858
+
859
+ declare type SettingsViewComponent = FC<SettingsViewProps> & {
860
+ Section: typeof SectionBlock_2;
861
+ Group: typeof GroupBlock;
862
+ Item: typeof ItemRow;
863
+ Divider: typeof DividerEl_3;
864
+ };
865
+
866
+ export declare interface SettingsViewProps {
867
+ sections?: SettingsSection[];
868
+ children?: ReactNode;
869
+ layout?: SettingsLayout;
870
+ density?: SettingsDensity;
871
+ variant?: SettingsVariant;
872
+ loading?: boolean;
873
+ emptyState?: ReactNode;
874
+ className?: string;
875
+ id?: string;
876
+ }
877
+
779
878
  export declare const SideNav: SideNavComponent;
780
879
 
781
880
  declare type SideNavComponent = FC<SideNavProps> & {
@@ -837,7 +936,7 @@ export declare interface SideNavUser {
837
936
  onClick?: () => void;
838
937
  }
839
938
 
840
- export declare type SideNavVariant = "classic" | "aurora";
939
+ export declare type SideNavVariant = "classic" | "aurora" | "transparent";
841
940
 
842
941
  export declare const Spinner: React_2.FC<Props>;
843
942
 
@@ -860,6 +959,20 @@ export declare interface StepperProps {
860
959
  className?: string;
861
960
  }
862
961
 
962
+ export declare const Switch: ForwardRefExoticComponent<SwitchProps & RefAttributes<HTMLButtonElement>>;
963
+
964
+ export declare interface SwitchProps {
965
+ checked?: boolean;
966
+ defaultChecked?: boolean;
967
+ onChange?: (next: boolean) => void;
968
+ disabled?: boolean;
969
+ size?: "sm" | "md";
970
+ color?: "green" | "blue";
971
+ ariaLabel?: string;
972
+ id?: string;
973
+ className?: string;
974
+ }
975
+
863
976
  export declare type TabItem = TabItemLegacy_2 | {
864
977
  value: string | number;
865
978
  label: string;