cartly 1.0.2
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 +167 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +59 -0
- package/dist/src/CartProvider.d.ts +13 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/storage.d.ts +13 -0
- package/dist/src/types.d.ts +22 -0
- package/dist/vite.config.d.ts +2 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# cartly π
|
|
2
|
+
|
|
3
|
+
**cartly** is a lightweight, type-safe React Cart Context library built with modern hooks.
|
|
4
|
+
Itβs designed to be **generic**, **framework-agnostic**, and easy to integrate into any React application β from eβcommerce stores to internal tools.
|
|
5
|
+
|
|
6
|
+
- No external state libraries.
|
|
7
|
+
- Strong TypeScript support.
|
|
8
|
+
- Optional persistence.
|
|
9
|
+
- Safe for SSR.
|
|
10
|
+
|
|
11
|
+
## β¨ Features
|
|
12
|
+
|
|
13
|
+
- β
Generic `createCartContext<T>()` API
|
|
14
|
+
- β
`CartProvider` + `useCart` hook
|
|
15
|
+
- β
Cart actions: add, update, remove, clear
|
|
16
|
+
- β
Derived state (total item count)
|
|
17
|
+
- β
Optional persistence (`localStorage`, `sessionStorage`, or disabled)
|
|
18
|
+
- β
SSR-safe (no `window` access at import time)
|
|
19
|
+
- β
Works with Material UI, Vite, Next.js, CRA
|
|
20
|
+
- β
React as peer dependency (no duplicate React issues)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## π¦ Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install cartly
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## π¦ Peer Dependencies
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install react react-dom
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## π Quick Start
|
|
37
|
+
|
|
38
|
+
### 1. Create a typed cart context (once per app)
|
|
39
|
+
|
|
40
|
+
```Typescript
|
|
41
|
+
import { createCartContext } from "cartly";
|
|
42
|
+
|
|
43
|
+
export type Product = {
|
|
44
|
+
id: string;
|
|
45
|
+
name: string;
|
|
46
|
+
price: number;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const { CartProvider, useCart } = createCartContext<Product>();
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
β
This should be done once and re-exported.
|
|
53
|
+
Do not call createCartContext in every component.
|
|
54
|
+
|
|
55
|
+
### 2. Wrap your application
|
|
56
|
+
|
|
57
|
+
```Typescript
|
|
58
|
+
import { StrictMode } from "react";
|
|
59
|
+
import { createRoot } from "react-dom/client";
|
|
60
|
+
import { CartProvider } from "./cart/cart";
|
|
61
|
+
import App from "./App.tsx";
|
|
62
|
+
|
|
63
|
+
createRoot(document.getElementById("root")!).render(
|
|
64
|
+
<StrictMode>
|
|
65
|
+
<CartProvider storageKey="cart" storage={localStorage}>
|
|
66
|
+
<App />
|
|
67
|
+
</CartProvider>
|
|
68
|
+
</StrictMode>,
|
|
69
|
+
);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 3. Use the cart anywhere
|
|
73
|
+
|
|
74
|
+
```Typescript
|
|
75
|
+
import { useCart } from "./cart/cart";
|
|
76
|
+
|
|
77
|
+
export function AddToCartButton({ product }: { product: Product }) {
|
|
78
|
+
const { addItemToCart, cartCount } = useCart();
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<button onClick={() => addItemToCart(product)}>
|
|
82
|
+
Add to cart ({cartCount})
|
|
83
|
+
</button>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## π§ API
|
|
89
|
+
|
|
90
|
+
### createCartContext<T>()
|
|
91
|
+
|
|
92
|
+
Creates a typed cart context for your application.
|
|
93
|
+
|
|
94
|
+
```TypeScript
|
|
95
|
+
const { CartProvider, useCart } = createCartContext<T>();
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Call this once per app, export the result, and reuse it everywhere.
|
|
99
|
+
|
|
100
|
+
### <CartProvider>
|
|
101
|
+
|
|
102
|
+
```TypeScript
|
|
103
|
+
<CartProvider storageKey="cart" storage={localStorage}>
|
|
104
|
+
{children}
|
|
105
|
+
</CartProvider>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### Props
|
|
109
|
+
|
|
110
|
+
| Prop | Type | Required | Description |
|
|
111
|
+
| ---------- | -------------- | -------- | ------------------------------------- |
|
|
112
|
+
| children | ReactNode | β
| App tree |
|
|
113
|
+
| storageKey | string | β | Storage key (default: "cart") |
|
|
114
|
+
| storage | Storage / null | β | localStorage, sessionStorage, or null |
|
|
115
|
+
|
|
116
|
+
π‘ Pass storage={null} to disable persistence (recommended for SSR/tests).
|
|
117
|
+
|
|
118
|
+
### useCart()
|
|
119
|
+
|
|
120
|
+
```TypeScript
|
|
121
|
+
const {
|
|
122
|
+
cartItems,
|
|
123
|
+
addItemToCart,
|
|
124
|
+
updateItemQuantity,
|
|
125
|
+
removeItemFromCart,
|
|
126
|
+
clearCart,
|
|
127
|
+
cartCount
|
|
128
|
+
} = useCart();
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### π§Ύ Cart Item Shape
|
|
132
|
+
|
|
133
|
+
```TypeScript
|
|
134
|
+
export type CartItem<T> = {
|
|
135
|
+
id: string | number;
|
|
136
|
+
item: T;
|
|
137
|
+
quantity: number;
|
|
138
|
+
};
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
This avoids collisions if your product model already contains a quantity field.
|
|
142
|
+
|
|
143
|
+
## π Repository Structure
|
|
144
|
+
|
|
145
|
+
```Plain Text
|
|
146
|
+
cartly/
|
|
147
|
+
ββ src/ # Library source
|
|
148
|
+
ββ samples/ # Example apps
|
|
149
|
+
β ββ react-vite-demo/
|
|
150
|
+
ββ dist/ # Build output (npm only)
|
|
151
|
+
ββ package.json
|
|
152
|
+
ββ vite.config.ts
|
|
153
|
+
ββ tsconfig.json
|
|
154
|
+
ββ README.md
|
|
155
|
+
ββ LICENSE
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## π License
|
|
159
|
+
|
|
160
|
+
MIT
|
|
161
|
+
|
|
162
|
+
## π€ Contributing
|
|
163
|
+
|
|
164
|
+
Issues and PRs are welcome.
|
|
165
|
+
The library intentionally keeps a small, focused API surface.
|
|
166
|
+
|
|
167
|
+
Built with React, TypeScript, and Vite.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const y=require("react/jsx-runtime"),i=require("react");function w(u,s,d){try{const t=u.getItem(s);return t?JSON.parse(t):d}catch(t){return console.log(t),u.removeItem(s),d}}function O(){const u=i.createContext(void 0);function s({children:t,storageKey:m="cart",storage:f=localStorage}){const[a,C]=i.useState(()=>w(f,m,[]));i.useEffect(()=>{f.setItem(m,JSON.stringify(a))},[a,f,m]);const x=(e,r=1)=>{C(n=>n.find(c=>c.id==e.id)?n.map(c=>c.id==e.id?{...c,quantity:c.quantity+r}:c):[...n,{...e,quantity:r}])},l=(e,r)=>{C(n=>n.map(o=>o.id==e?{...o,quantity:r}:o).filter(o=>o.quantity>0))},I=e=>{C(r=>r.filter(n=>n.id!==e))},S=()=>{C([])},v=i.useMemo(()=>a.reduce((e,r)=>e+r.quantity,0),[a]),q={cartItems:a,addItemToCart:x,updateItemQuantity:l,removeItemFromCart:I,clearCart:S,cartCount:v};return y.jsx(u.Provider,{value:q,children:t})}function d(){const t=i.useContext(u);if(!t)throw new Error("useCart must be used within a CartProvider.");return t}return{CartProvider:s,useCart:d}}exports.createCartContext=O;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { jsx as S } from "react/jsx-runtime";
|
|
2
|
+
import { createContext as w, useContext as h, useState as q, useEffect as y, useMemo as J } from "react";
|
|
3
|
+
function N(c, i, s) {
|
|
4
|
+
try {
|
|
5
|
+
const t = c.getItem(i);
|
|
6
|
+
return t ? JSON.parse(t) : s;
|
|
7
|
+
} catch (t) {
|
|
8
|
+
return console.log(t), c.removeItem(i), s;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function E() {
|
|
12
|
+
const c = w(void 0);
|
|
13
|
+
function i({
|
|
14
|
+
children: t,
|
|
15
|
+
storageKey: m = "cart",
|
|
16
|
+
storage: C = localStorage
|
|
17
|
+
}) {
|
|
18
|
+
const [u, d] = q(
|
|
19
|
+
() => N(C, m, [])
|
|
20
|
+
);
|
|
21
|
+
y(() => {
|
|
22
|
+
C.setItem(m, JSON.stringify(u));
|
|
23
|
+
}, [u, C, m]);
|
|
24
|
+
const f = (r, e = 1) => {
|
|
25
|
+
d((n) => n.find((a) => a.id == r.id) ? n.map(
|
|
26
|
+
(a) => a.id == r.id ? { ...a, quantity: a.quantity + e } : a
|
|
27
|
+
) : [...n, { ...r, quantity: e }]);
|
|
28
|
+
}, x = (r, e) => {
|
|
29
|
+
d(
|
|
30
|
+
(n) => n.map((o) => o.id == r ? { ...o, quantity: e } : o).filter((o) => o.quantity > 0)
|
|
31
|
+
);
|
|
32
|
+
}, I = (r) => {
|
|
33
|
+
d((e) => e.filter((n) => n.id !== r));
|
|
34
|
+
}, l = () => {
|
|
35
|
+
d([]);
|
|
36
|
+
}, p = J(
|
|
37
|
+
() => u.reduce((r, e) => r + e.quantity, 0),
|
|
38
|
+
[u]
|
|
39
|
+
), v = {
|
|
40
|
+
cartItems: u,
|
|
41
|
+
addItemToCart: f,
|
|
42
|
+
updateItemQuantity: x,
|
|
43
|
+
removeItemFromCart: I,
|
|
44
|
+
clearCart: l,
|
|
45
|
+
cartCount: p
|
|
46
|
+
};
|
|
47
|
+
return /* @__PURE__ */ S(c.Provider, { value: v, children: t });
|
|
48
|
+
}
|
|
49
|
+
function s() {
|
|
50
|
+
const t = h(c);
|
|
51
|
+
if (!t)
|
|
52
|
+
throw new Error("useCart must be used within a CartProvider.");
|
|
53
|
+
return t;
|
|
54
|
+
}
|
|
55
|
+
return { CartProvider: i, useCart: s };
|
|
56
|
+
}
|
|
57
|
+
export {
|
|
58
|
+
E as createCartContext
|
|
59
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CartContextType, CartStorage } from './types';
|
|
2
|
+
type Props = {
|
|
3
|
+
children: React.ReactNode;
|
|
4
|
+
storageKey?: string;
|
|
5
|
+
storage?: CartStorage;
|
|
6
|
+
};
|
|
7
|
+
export declare function createCartContext<T extends {
|
|
8
|
+
id: string | number;
|
|
9
|
+
}>(): {
|
|
10
|
+
CartProvider: ({ children, storageKey, storage, }: Props) => import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
useCart: () => CartContextType<T>;
|
|
12
|
+
};
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CartStorage } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Safely read JSON from storage, returning a fallback value if parsing fails or if the item does not exist
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* This function attempts to read a JSON string from the provided storage using the specified key.
|
|
7
|
+
*
|
|
8
|
+
* @param storage - The storage object to read from.
|
|
9
|
+
* @param key - The key to read the JSON string from.
|
|
10
|
+
* @param fallback - The fallback value to return if parsing fails or the item does not exist.
|
|
11
|
+
* @returns The parsed JSON value or the fallback value.
|
|
12
|
+
*/
|
|
13
|
+
export declare function safeReadJSON<T>(storage: CartStorage, key: string, fallback: T): T;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type CartItem<T extends {
|
|
2
|
+
id: string | number;
|
|
3
|
+
}> = T & {
|
|
4
|
+
quantity: number;
|
|
5
|
+
};
|
|
6
|
+
export type CartContextType<T extends {
|
|
7
|
+
id: string | number;
|
|
8
|
+
}> = {
|
|
9
|
+
cartItems: CartItem<T>[];
|
|
10
|
+
addItemToCart: (item: T, quantity?: number) => void;
|
|
11
|
+
updateItemQuantity: (id: string | number, quantity: number) => void;
|
|
12
|
+
removeItemFromCart: (id: string | number) => void;
|
|
13
|
+
clearCart: () => void;
|
|
14
|
+
cartCount: number;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* A simplified interface for storage objects, focusing on essential methods for cart management.
|
|
18
|
+
*
|
|
19
|
+
* @remarks
|
|
20
|
+
* This type includes only the `getItem`, `setItem`, and `removeItem` methods from the standard Storage interface.
|
|
21
|
+
*/
|
|
22
|
+
export type CartStorage = Pick<Storage, "getItem" | "setItem" | "removeItem">;
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cartly",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Generic, type-safe Cart Context & hooks for React",
|
|
5
|
+
"main": "dist/index.cjs",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "vite build",
|
|
13
|
+
"typecheck": "tsc -p tsconfig.build.json --noEmit",
|
|
14
|
+
"prepublishOnly": "npm run typecheck && npm run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [],
|
|
17
|
+
"author": "",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"type": "module",
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"react": ">=18",
|
|
22
|
+
"react-dom": ">=18"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^25.1.0",
|
|
26
|
+
"@types/react": "^19.2.10",
|
|
27
|
+
"@types/react-dom": "^19.2.3",
|
|
28
|
+
"@vitejs/plugin-react-swc": "^4.2.2",
|
|
29
|
+
"react": "^19.2.4",
|
|
30
|
+
"react-dom": "^19.2.4",
|
|
31
|
+
"typescript": "^5.9.3",
|
|
32
|
+
"vite": "^7.3.1",
|
|
33
|
+
"vite-plugin-dts": "^4.5.4"
|
|
34
|
+
}
|
|
35
|
+
}
|