@geomak/ui 6.17.2 → 6.19.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
@@ -1,8 +1,8 @@
1
1
  # @geomak/ui
2
2
 
3
- Oxygen Design System — reusable UI primitives for React applications.
3
+ **Oxygen Design System**a production-grade React component library for enterprise apps: internal dashboards, CRM tools, and landing pages.
4
4
 
5
- Built with **React 19**, **Radix UI** (accessibility & behaviour), and **Tailwind CSS v3**. Ships as ESM + CJS + TypeScript declarations.
5
+ Built with **React 19**, **Radix UI** (accessibility & behaviour), **Tailwind CSS v3**, and **Framer Motion**. Fully themeable through a CSS-variable design-token layer, with first-class light/dark support. Ships as ESM + CJS + TypeScript declarations.
6
6
 
7
7
  ---
8
8
 
@@ -20,10 +20,10 @@ Import the stylesheet once at your app root:
20
20
  import '@geomak/ui/styles'
21
21
  ```
22
22
 
23
- Wrap your app with the required providers:
23
+ Wrap your app with the providers for the features you use:
24
24
 
25
25
  ```tsx
26
- import { TooltipProvider, NotificationProvider } from '@geomak/ui'
26
+ import { NotificationProvider, TooltipProvider } from '@geomak/ui'
27
27
 
28
28
  function App() {
29
29
  return (
@@ -36,67 +36,94 @@ function App() {
36
36
  }
37
37
  ```
38
38
 
39
+ Optional providers: `ThemeProvider` (scoped theming / dark mode), `CartProvider` (e-commerce cart state).
40
+
39
41
  ---
40
42
 
41
43
  ## Components
42
44
 
43
- ### Inputs
44
- `Button` · `TextInput` · `NumberInput` · `Password` · `SearchInput` · `Checkbox` · `Switch` · `Dropdown` · `AutoComplete` · `TreeSelect` · `FileInput` · `Temporal.DatePicker` · `Temporal.TemporalPicker` · `DropdownPill`
45
-
46
- ### Core
47
- `Modal` · `Drawer` · `Tooltip` · `Tabs` · `Tree` · `ToggleButton` · `Table` · `List` · `MenuBar` · `ContextMenu` · `Wizard` · `Catalog` · `CatalogGrid` · `CatalogCarousel` · `GridCard` · `OpaqueGridCard` · `ScalableContainer` · `LoadingSpinner` · `FadingBase` · `Notification` · `ThemeSwitch` · `IconButton`
45
+ 60+ components across the following groups. Browse the full, interactive catalog with live controls and guides in **Storybook** (`yarn storybook`, or the deployed build on Netlify).
48
46
 
49
- ### Icons
50
- `Icon.XClose` · `Icon.ChevronRight` · `Icon.Dashboard` · `Icon.FleetIcon` · … (50+ icons)
47
+ | Group | Components |
48
+ |---|---|
49
+ | **Layout** | AppShell · Box · Flex · Grid · Portal · ScalableContainer · FadingBase |
50
+ | **Navigation** | TopBar · Sidebar · Breadcrumbs · ContextMenu · MegaMenu |
51
+ | **Buttons** | Button · IconButton · FAB |
52
+ | **Inputs** | TextInput · NumberInput · Password · SearchInput · TextArea · Checkbox · Switch · RadioGroup · SegmentedControl · Dropdown · AutoComplete · TreeSelect · TagsInput · Slider · Rating · OtpInput · FileInput · ColorPicker · DatePicker · DateRangePicker · TimePicker |
53
+ | **Forms** | Form (`useForm` API) · CreditCardForm |
54
+ | **Data Display** | Table · List · Tree · Tabs · Accordion · Card · CardCarousel · Statistic · Avatar · Badge · Kbd · Calendar · Typography |
55
+ | **Feedback** | Modal · Drawer · Tooltip · Notification · PopConfirm · Wizard |
56
+ | **Progress** | LoadingSpinner · Skeleton |
57
+ | **E-Commerce** | Cart · CartProvider / `useCart` · CartButton · EmptyCart · Checkout |
58
+ | **Theming** | ThemeProvider · ThemeSwitch |
51
59
 
52
60
  ---
53
61
 
54
- ## Development
62
+ ## Design tokens
55
63
 
56
- ```bash
57
- # Install dependencies
58
- yarn
64
+ Every visual decision is driven by a CSS-variable token layer (colours, radius, shadows, typography, density, motion, z-index) — swap any of it at runtime with a single override. Tokens are also exported as JS for canvas / email / SSR contexts:
59
65
 
60
- # Start Storybook
61
- yarn storybook
66
+ ```tsx
67
+ import { semanticTokens, vars, palette } from '@geomak/ui/tokens'
62
68
 
63
- # Build the library (ESM + CJS + .d.ts)
64
- yarn build
69
+ // CSS-var references (respond to light/dark automatically)
70
+ <div style={{ background: vars.color.surface, borderRadius: vars.radius.lg }} />
71
+
72
+ // Resolved hex/px values
73
+ semanticTokens.dark.accent // '#2d88ff'
74
+ ```
65
75
 
66
- # Type-check
67
- yarn typecheck
76
+ Override globally after importing the stylesheet:
68
77
 
69
- # Lint
70
- yarn lint
78
+ ```css
79
+ :root { --color-accent: #7c3aed; }
71
80
  ```
72
81
 
82
+ See the **Tokens**, **Palette**, and **Parameterization** guides in Storybook.
83
+
73
84
  ---
74
85
 
75
86
  ## Tailwind setup (consuming app)
76
87
 
77
- The package ships a colour palette. Add to your `tailwind.config.js`:
88
+ To use the same tokens and utilities in your own markup, extend your Tailwind config with the shipped brand palette. The library's compiled `styles` already cover the components themselves — this step is only needed for your own classes.
78
89
 
79
90
  ```js
80
91
  const PALETTE = require('@geomak/ui/src/utils/palette.json')
81
92
 
82
93
  module.exports = {
83
- content: [
84
- './src/**/*.{ts,tsx}',
85
- './node_modules/@geomak/ui/dist/**/*.js',
86
- ],
94
+ content: ['./src/**/*.{ts,tsx}', './node_modules/@geomak/ui/dist/**/*.js'],
87
95
  darkMode: 'class',
88
96
  theme: {
89
- colors: PALETTE,
90
- // ... your extensions
97
+ extend: {
98
+ colors: PALETTE,
99
+ // semantic utilities map to the CSS vars, e.g.:
100
+ // background: 'var(--color-background)', surface: 'var(--color-surface)', …
101
+ },
91
102
  },
92
103
  }
93
104
  ```
94
105
 
106
+ The library's own config also restores the standard **gray / slate / zinc** ramps and **black** alongside the brand palette, so the basic neutrals are always available.
107
+
108
+ ---
109
+
110
+ ## Development
111
+
112
+ ```bash
113
+ yarn # install dependencies
114
+ yarn storybook # start Storybook
115
+ yarn build # build the library (ESM + CJS + .d.ts + styles)
116
+ yarn typecheck # type-check
117
+ yarn lint # lint
118
+ yarn test # run unit tests (Vitest)
119
+ yarn ci # typecheck + lint + test
120
+ ```
121
+
95
122
  ---
96
123
 
97
124
  ## Releases
98
125
 
99
- This package uses [semantic-release](https://github.com/semantic-release/semantic-release) with [Conventional Commits](https://www.conventionalcommits.org/).
126
+ Uses [semantic-release](https://github.com/semantic-release/semantic-release) with [Conventional Commits](https://www.conventionalcommits.org/).
100
127
 
101
128
  | Commit prefix | Version bump |
102
129
  |---|---|
@@ -104,7 +131,7 @@ This package uses [semantic-release](https://github.com/semantic-release/semanti
104
131
  | `feat:` | minor (0.x.0) |
105
132
  | `feat!:` / `BREAKING CHANGE:` | major (x.0.0) |
106
133
 
107
- Merging to `main` automatically lints, type-checks, publishes to npm, and deploys Storybook to Netlify.
134
+ Merging to `main` automatically lints, type-checks, tests, publishes to npm, and deploys Storybook to Netlify.
108
135
 
109
136
  ---
110
137
 
package/dist/index.cjs CHANGED
@@ -1774,7 +1774,25 @@ Card.Body = CardBody;
1774
1774
  Card.Footer = CardFooter;
1775
1775
  var Card_default = Card;
1776
1776
  var Arrow2 = ({ dir }) => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, "aria-hidden": "true", className: "h-5 w-5", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: dir === "left" ? "M15 19l-7-7 7-7" : "M9 5l7 7-7 7" }) });
1777
- function CardCarousel({
1777
+ var arrowBtn = "absolute top-1/2 -translate-y-1/2 z-30 flex h-9 w-9 items-center justify-center rounded-full border border-border bg-surface text-foreground-secondary shadow-md transition hover:text-foreground hover:bg-surface-raised disabled:opacity-0 disabled:pointer-events-none focus:outline-none focus-visible:ring-2 focus-visible:ring-accent";
1778
+ var Dots = ({ count, active, onSelect }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 flex items-center justify-center gap-1.5", children: Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx(
1779
+ "button",
1780
+ {
1781
+ type: "button",
1782
+ "aria-label": `Go to slide ${i + 1}`,
1783
+ "aria-current": i === active,
1784
+ onClick: () => onSelect(i),
1785
+ className: [
1786
+ "h-1.5 rounded-full transition-all duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
1787
+ i === active ? "w-5 bg-accent" : "w-1.5 bg-border hover:bg-foreground-muted"
1788
+ ].join(" ")
1789
+ },
1790
+ i
1791
+ )) });
1792
+ function CardCarousel(props) {
1793
+ return props.variant === "rotating" ? /* @__PURE__ */ jsxRuntime.jsx(RotatingCarousel, { ...props }) : /* @__PURE__ */ jsxRuntime.jsx(FlatCarousel, { ...props });
1794
+ }
1795
+ function FlatCarousel({
1778
1796
  children,
1779
1797
  itemWidth = 280,
1780
1798
  gap = 16,
@@ -1793,7 +1811,6 @@ function CardCarousel({
1793
1811
  const update = React24.useCallback(() => {
1794
1812
  const el = scrollerRef.current;
1795
1813
  if (!el) return;
1796
- el.clientWidth;
1797
1814
  setAtStart(el.scrollLeft <= 1);
1798
1815
  setAtEnd(el.scrollLeft + el.clientWidth >= el.scrollWidth - 1);
1799
1816
  const first = el.firstElementChild;
@@ -1811,49 +1828,109 @@ function CardCarousel({
1811
1828
  window.removeEventListener("resize", update);
1812
1829
  };
1813
1830
  }, [update]);
1814
- const scrollByDir = (dir) => {
1831
+ const slideStep = (dir) => {
1815
1832
  const el = scrollerRef.current;
1816
1833
  if (!el) return;
1817
1834
  const first = el.firstElementChild;
1818
1835
  const slideW = first ? first.getBoundingClientRect().width + gap : el.clientWidth;
1819
1836
  el.scrollBy({ left: dir * slideW, behavior: "smooth" });
1820
1837
  };
1821
- const scrollTo = (i) => {
1838
+ const scrollToIndex = (i) => {
1822
1839
  const el = scrollerRef.current;
1823
1840
  if (!el) return;
1824
1841
  const first = el.firstElementChild;
1825
1842
  const slideW = first ? first.getBoundingClientRect().width + gap : el.clientWidth;
1826
1843
  el.scrollTo({ left: i * slideW, behavior: "smooth" });
1827
1844
  };
1828
- const arrowBtn = "absolute top-1/2 -translate-y-1/2 z-10 flex h-9 w-9 items-center justify-center rounded-full border border-border bg-surface text-foreground-secondary shadow-md transition hover:text-foreground hover:bg-surface-raised disabled:opacity-0 disabled:pointer-events-none focus:outline-none focus-visible:ring-2 focus-visible:ring-accent";
1829
- return /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-label": ariaLabel, className: ["relative", className].filter(Boolean).join(" "), style, children: [
1830
- showArrows && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Previous", onClick: () => scrollByDir(-1), disabled: atStart, className: `${arrowBtn} left-1`, children: /* @__PURE__ */ jsxRuntime.jsx(Arrow2, { dir: "left" }) }),
1831
- /* @__PURE__ */ jsxRuntime.jsx(
1832
- "div",
1833
- {
1834
- ref: scrollerRef,
1835
- className: "flex overflow-x-auto snap-x snap-mandatory hidden-scrollbar scroll-smooth",
1836
- style: { gap },
1837
- children: slides.map((slide, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "snap-start flex-shrink-0", style: { width }, children: slide }, i))
1838
- }
1839
- ),
1840
- showArrows && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Next", onClick: () => scrollByDir(1), disabled: atEnd, className: `${arrowBtn} right-1`, children: /* @__PURE__ */ jsxRuntime.jsx(Arrow2, { dir: "right" }) }),
1841
- showDots && slides.length > 1 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 flex items-center justify-center gap-1.5", children: slides.map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(
1842
- "button",
1843
- {
1844
- type: "button",
1845
- "aria-label": `Go to slide ${i + 1}`,
1846
- "aria-current": i === active,
1847
- onClick: () => scrollTo(i),
1848
- className: [
1849
- "h-1.5 rounded-full transition-all duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
1850
- i === active ? "w-5 bg-accent" : "w-1.5 bg-border hover:bg-foreground-muted"
1851
- ].join(" ")
1852
- },
1853
- i
1854
- )) })
1845
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-label": ariaLabel, "aria-roledescription": "carousel", className: ["relative", className].filter(Boolean).join(" "), style, children: [
1846
+ showArrows && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Previous", onClick: () => slideStep(-1), disabled: atStart, className: `${arrowBtn} left-1`, children: /* @__PURE__ */ jsxRuntime.jsx(Arrow2, { dir: "left" }) }),
1847
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: scrollerRef, className: "flex overflow-x-auto snap-x snap-mandatory hidden-scrollbar scroll-smooth", style: { gap }, children: slides.map((slide, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "snap-start flex-shrink-0", style: { width }, children: slide }, i)) }),
1848
+ showArrows && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Next", onClick: () => slideStep(1), disabled: atEnd, className: `${arrowBtn} right-1`, children: /* @__PURE__ */ jsxRuntime.jsx(Arrow2, { dir: "right" }) }),
1849
+ showDots && slides.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(Dots, { count: slides.length, active, onSelect: scrollToIndex })
1855
1850
  ] });
1856
1851
  }
1852
+ function RotatingCarousel({
1853
+ children,
1854
+ itemWidth = 280,
1855
+ showArrows = true,
1856
+ showDots = false,
1857
+ "aria-label": ariaLabel = "Carousel",
1858
+ className = "",
1859
+ style
1860
+ }) {
1861
+ const slides = React24__default.default.Children.toArray(children);
1862
+ const count = slides.length;
1863
+ const [active, setActive] = React24.useState(0);
1864
+ const reduced = framerMotion.useReducedMotion();
1865
+ const idx = Math.min(Math.max(active, 0), Math.max(0, count - 1));
1866
+ const atStart = idx <= 0;
1867
+ const atEnd = idx >= count - 1;
1868
+ const step = (dir) => setActive((a) => Math.min(Math.max(a + dir, 0), count - 1));
1869
+ const w = typeof itemWidth === "number" ? itemWidth : parseInt(String(itemWidth), 10) || 320;
1870
+ const widthCss = typeof itemWidth === "number" ? `${itemWidth}px` : itemWidth;
1871
+ const SPACING = w * 0.6;
1872
+ const SIDE_SCALE = 0.82;
1873
+ const SIDE_OPACITY = 0.5;
1874
+ const transition = reduced ? { duration: 0 } : { type: "spring", stiffness: 280, damping: 32, mass: 0.9 };
1875
+ const onKeyDown = (e) => {
1876
+ if (e.key === "ArrowLeft") {
1877
+ e.preventDefault();
1878
+ step(-1);
1879
+ } else if (e.key === "ArrowRight") {
1880
+ e.preventDefault();
1881
+ step(1);
1882
+ }
1883
+ };
1884
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1885
+ "section",
1886
+ {
1887
+ "aria-label": ariaLabel,
1888
+ "aria-roledescription": "carousel",
1889
+ className: ["relative", className].filter(Boolean).join(" "),
1890
+ style,
1891
+ onKeyDown,
1892
+ children: [
1893
+ showArrows && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Previous", onClick: () => step(-1), disabled: atStart, className: `${arrowBtn} left-1`, children: /* @__PURE__ */ jsxRuntime.jsx(Arrow2, { dir: "left" }) }),
1894
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative mx-auto overflow-hidden py-6", children: [
1895
+ count > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "invisible mx-auto", style: { width: widthCss }, "aria-hidden": "true", children: slides[idx] }),
1896
+ slides.map((slide, i) => {
1897
+ const offset = i - idx;
1898
+ const abs = Math.abs(offset);
1899
+ if (abs > 2) return null;
1900
+ const isCenter = offset === 0;
1901
+ const isPeek = abs === 1;
1902
+ return /* @__PURE__ */ jsxRuntime.jsx(
1903
+ "div",
1904
+ {
1905
+ className: "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2",
1906
+ style: { width: widthCss, zIndex: 30 - abs * 10, pointerEvents: isCenter || isPeek ? "auto" : "none" },
1907
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1908
+ framerMotion.motion.div,
1909
+ {
1910
+ initial: false,
1911
+ animate: {
1912
+ x: offset * (abs <= 1 ? SPACING : SPACING * 1.7),
1913
+ scale: isCenter ? 1 : SIDE_SCALE,
1914
+ opacity: abs <= 1 ? isCenter ? 1 : SIDE_OPACITY : 0
1915
+ },
1916
+ transition,
1917
+ className: isCenter ? void 0 : "cursor-pointer",
1918
+ onClick: isCenter ? void 0 : () => setActive(i),
1919
+ "aria-hidden": isCenter ? void 0 : true,
1920
+ children: slide
1921
+ }
1922
+ )
1923
+ },
1924
+ i
1925
+ );
1926
+ })
1927
+ ] }),
1928
+ showArrows && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Next", onClick: () => step(1), disabled: atEnd, className: `${arrowBtn} right-1`, children: /* @__PURE__ */ jsxRuntime.jsx(Arrow2, { dir: "right" }) }),
1929
+ showDots && count > 1 && /* @__PURE__ */ jsxRuntime.jsx(Dots, { count, active: idx, onSelect: setActive })
1930
+ ]
1931
+ }
1932
+ );
1933
+ }
1857
1934
  var VALUE_SIZE = {
1858
1935
  sm: "text-xl",
1859
1936
  md: "text-3xl",