@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 +59 -32
- package/dist/index.cjs +108 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -7
- package/dist/index.d.ts +20 -7
- package/dist/index.js +108 -31
- package/dist/index.js.map +1 -1
- package/dist/styles.css +5 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @geomak/ui
|
|
2
2
|
|
|
3
|
-
Oxygen Design System —
|
|
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),
|
|
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
|
|
23
|
+
Wrap your app with the providers for the features you use:
|
|
24
24
|
|
|
25
25
|
```tsx
|
|
26
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
##
|
|
62
|
+
## Design tokens
|
|
55
63
|
|
|
56
|
-
|
|
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
|
-
|
|
61
|
-
|
|
66
|
+
```tsx
|
|
67
|
+
import { semanticTokens, vars, palette } from '@geomak/ui/tokens'
|
|
62
68
|
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
67
|
-
yarn typecheck
|
|
76
|
+
Override globally after importing the stylesheet:
|
|
68
77
|
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1832
|
-
|
|
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",
|