@extrachill/components 0.2.0 → 1.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/package.json CHANGED
@@ -1,62 +1,50 @@
1
1
  {
2
2
  "name": "@extrachill/components",
3
- "version": "0.2.0",
4
- "description": "Shared React components for the Extra Chill Platform.",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
3
+ "version": "1.0.0",
4
+ "description": "Shared React components for the Extra Chill Platform",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
8
  "exports": {
9
- "./package.json": "./package.json",
10
9
  ".": {
11
10
  "types": "./dist/index.d.ts",
12
- "default": "./dist/index.js"
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
13
  },
14
- "./styles/components.scss": "./styles/components.scss",
15
- "./styles/*": "./styles/*",
16
- "./src": {
17
- "types": "./src/index.tsx",
18
- "default": "./src/index.tsx"
19
- },
20
- "./src/*": {
21
- "types": "./src/*.tsx",
22
- "default": "./src/*.tsx"
23
- }
14
+ "./styles/components.scss": "./src/styles/components.scss",
15
+ "./styles/*": "./src/styles/*"
24
16
  },
25
17
  "files": [
26
- "src",
27
18
  "dist",
28
- "styles",
29
- "README.md",
30
- "CHANGELOG.md"
19
+ "src/styles"
31
20
  ],
32
21
  "scripts": {
33
- "build": "rm -rf dist *.tsbuildinfo && tsc",
22
+ "build": "tsup src/index.ts --format cjs,esm --dts --external react --external @wordpress/components --external @wordpress/element",
34
23
  "typecheck": "tsc --noEmit",
35
24
  "prepublishOnly": "npm run build"
36
25
  },
37
- "keywords": [
38
- "extrachill",
39
- "react",
40
- "components",
41
- "wordpress",
42
- "design-system"
43
- ],
44
26
  "author": "Chris Huber <hello@chubes.net>",
45
27
  "license": "GPL-2.0+",
46
28
  "repository": {
47
29
  "type": "git",
48
30
  "url": "https://github.com/Extra-Chill/extrachill-components.git"
49
31
  },
50
- "homepage": "https://github.com/Extra-Chill/extrachill-components",
51
- "bugs": "https://github.com/Extra-Chill/extrachill-components/issues",
52
- "publishConfig": {
53
- "access": "public"
54
- },
32
+ "keywords": [
33
+ "extrachill",
34
+ "react",
35
+ "components",
36
+ "wordpress"
37
+ ],
55
38
  "peerDependencies": {
39
+ "@wordpress/components": ">=28.0.0",
40
+ "@wordpress/element": ">=6.0.0",
56
41
  "react": ">=18.0.0"
57
42
  },
58
43
  "devDependencies": {
59
44
  "@types/react": "^18.0.0",
60
- "typescript": "^5.7.0"
45
+ "@wordpress/components": "^28.0.0",
46
+ "@wordpress/element": "^6.0.0",
47
+ "tsup": "^8.0.0",
48
+ "typescript": "^5.0.0"
61
49
  }
62
50
  }
@@ -74,60 +74,6 @@
74
74
  }
75
75
  }
76
76
 
77
- // Tabs — default styles for classPrefix="ec-tabs"
78
- // Consumers with custom classPrefix (e.g. "ec-am") duplicate
79
- // these rules under their own namespace in their block SCSS.
80
- .ec-tabs__tabs {
81
- display: inline-flex;
82
- gap: var(--spacing-sm, 0.5rem);
83
- flex-wrap: wrap;
84
- }
85
-
86
- .ec-tabs__tab {
87
- display: inline-flex;
88
- align-items: center;
89
- gap: var(--spacing-xs, 0.25rem);
90
- border: 1px solid var(--border-color, #ddd);
91
- background: var(--background-color, #fff);
92
- padding: var(--spacing-sm, 0.5rem) var(--spacing-md, 1rem);
93
- border-radius: var(--border-radius-sm, 4px);
94
- cursor: pointer;
95
- color: var(--text-color, #111);
96
- font: inherit;
97
- font-size: var(--font-size-base, 1rem);
98
- font-weight: 600;
99
- transition: background-color 0.2s, color 0.2s, border-color 0.2s;
100
-
101
- &:hover {
102
- background: var(--card-background, #f1f5f9);
103
- }
104
-
105
- &.is-active {
106
- border-color: var(--header-background, #1a1a1a);
107
- background: var(--header-background, #1a1a1a);
108
- color: var(--header-text-color, #fff);
109
- }
110
- }
111
-
112
- .ec-tabs__tab-badge {
113
- display: inline-flex;
114
- align-items: center;
115
- justify-content: center;
116
- min-width: 20px;
117
- height: 20px;
118
- padding: 0 6px;
119
- border-radius: 10px;
120
- background: var(--accent, #53940b);
121
- color: var(--background-color, #fff);
122
- font-size: var(--font-size-xs, 0.625rem);
123
- font-weight: 600;
124
-
125
- .ec-tabs__tab.is-active & {
126
- background: var(--background-color, #fff);
127
- color: var(--header-background, #1a1a1a);
128
- }
129
- }
130
-
131
77
  // Badges (commonly used with tables)
132
78
  .ec-badge {
133
79
  display: inline-block;
package/CHANGELOG.md DELETED
@@ -1,14 +0,0 @@
1
- # Changelog
2
-
3
- ## [0.2.0] - 2026-03-25
4
-
5
- ### Changed
6
- - Convert all components from JSX to TypeScript with exported prop interfaces
7
- - Remove @wordpress/components and @wordpress/element peer dependencies — use plain React
8
- - Add proper npm package config (exports, tsconfig, homeboy.json)
9
- - Add DataTable, Pagination, SearchBox, Modal, Tabs components
10
-
11
- ## [0.1.0] - 2026-03-02
12
-
13
- ### Added
14
- - Initial release with DataTable, Pagination, SearchBox, Modal components
@@ -1,19 +0,0 @@
1
- import type { ReactNode } from 'react';
2
- export interface DataTableColumn<T = Record<string, unknown>> {
3
- key: string;
4
- label: string;
5
- width?: string;
6
- render?: (value: unknown, row: T) => ReactNode;
7
- }
8
- export interface DataTableProps<T = Record<string, unknown>> {
9
- columns: DataTableColumn<T>[];
10
- data: T[];
11
- isLoading?: boolean;
12
- selectable?: boolean;
13
- selectedIds?: Array<string | number>;
14
- onSelectChange?: (ids: Array<string | number>) => void;
15
- emptyMessage?: string;
16
- rowKey?: string;
17
- }
18
- export declare function DataTable<T extends Record<string, unknown>>({ columns, data, isLoading, selectable, selectedIds, onSelectChange, emptyMessage, rowKey, }: DataTableProps<T>): import("react/jsx-runtime").JSX.Element;
19
- //# sourceMappingURL=DataTable.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"DataTable.d.ts","sourceRoot":"","sources":["../src/DataTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,WAAW,eAAe,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC3D,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,SAAS,CAAC;CAC/C;AAED,MAAM,WAAW,cAAc,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC1D,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9B,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IACrC,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,KAAK,IAAI,CAAC;IACvD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC5D,OAAO,EACP,IAAI,EACJ,SAAiB,EACjB,UAAkB,EAClB,WAAgB,EAChB,cAAyB,EACzB,YAA+B,EAC/B,MAAa,GACb,EAAE,cAAc,CAAC,CAAC,CAAC,2CA+EnB"}
package/dist/DataTable.js DELETED
@@ -1,27 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- export function DataTable({ columns, data, isLoading = false, selectable = false, selectedIds = [], onSelectChange = () => { }, emptyMessage = 'No data found.', rowKey = 'id', }) {
3
- const allSelected = data.length > 0 && selectedIds.length === data.length;
4
- const handleSelectAll = (e) => {
5
- if (e.target.checked) {
6
- onSelectChange(data.map((row) => row[rowKey]));
7
- }
8
- else {
9
- onSelectChange([]);
10
- }
11
- };
12
- const handleSelectRow = (id, checked) => {
13
- if (checked) {
14
- onSelectChange([...selectedIds, id]);
15
- }
16
- else {
17
- onSelectChange(selectedIds.filter((sid) => sid !== id));
18
- }
19
- };
20
- if (isLoading) {
21
- return (_jsx("div", { className: "ec-data-table__loading", children: _jsx("span", { children: "Loading..." }) }));
22
- }
23
- if (data.length === 0) {
24
- return (_jsx("div", { className: "ec-data-table__empty", children: _jsx("p", { children: emptyMessage }) }));
25
- }
26
- return (_jsxs("table", { className: "ec-data-table wp-list-table widefat fixed striped", children: [_jsx("thead", { children: _jsxs("tr", { children: [selectable && (_jsx("th", { className: "ec-data-table__check-column", children: _jsx("input", { type: "checkbox", checked: allSelected, onChange: handleSelectAll }) })), columns.map((col) => (_jsx("th", { style: col.width ? { width: col.width } : undefined, children: col.label }, col.key)))] }) }), _jsx("tbody", { children: data.map((row) => (_jsxs("tr", { children: [selectable && (_jsx("td", { className: "ec-data-table__check-column", children: _jsx("input", { type: "checkbox", checked: selectedIds.includes(row[rowKey]), onChange: (e) => handleSelectRow(row[rowKey], e.target.checked) }) })), columns.map((col) => (_jsx("td", { children: col.render ? col.render(row[col.key], row) : row[col.key] }, col.key)))] }, row[rowKey]))) })] }));
27
- }
package/dist/Modal.d.ts DELETED
@@ -1,10 +0,0 @@
1
- import { type ReactNode } from 'react';
2
- export interface ModalProps {
3
- title: string;
4
- isOpen: boolean;
5
- onClose: () => void;
6
- children: ReactNode;
7
- className?: string;
8
- }
9
- export declare function Modal({ title, isOpen, onClose, children, className, }: ModalProps): import("react/jsx-runtime").JSX.Element | null;
10
- //# sourceMappingURL=Modal.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../src/Modal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE/D,MAAM,WAAW,UAAU;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,KAAK,CAAC,EACrB,KAAK,EACL,MAAM,EACN,OAAO,EACP,QAAQ,EACR,SAAc,GACd,EAAE,UAAU,kDAmCZ"}
package/dist/Modal.js DELETED
@@ -1,19 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useCallback } from 'react';
3
- export function Modal({ title, isOpen, onClose, children, className = '', }) {
4
- const handleKeyDown = useCallback((e) => {
5
- if (e.key === 'Escape') {
6
- onClose();
7
- }
8
- }, [onClose]);
9
- useEffect(() => {
10
- if (isOpen) {
11
- document.addEventListener('keydown', handleKeyDown);
12
- return () => document.removeEventListener('keydown', handleKeyDown);
13
- }
14
- }, [isOpen, handleKeyDown]);
15
- if (!isOpen) {
16
- return null;
17
- }
18
- return (_jsxs("div", { className: `ec-modal ${className}`, role: "dialog", "aria-label": title, children: [_jsx("div", { className: "ec-modal__backdrop", onClick: onClose, "aria-hidden": "true" }), _jsxs("div", { className: "ec-modal__content", children: [_jsxs("div", { className: "ec-modal__header", children: [_jsx("h2", { children: title }), _jsx("button", { type: "button", onClick: onClose, "aria-label": "Close", children: "\u00D7" })] }), _jsx("div", { className: "ec-modal__body", children: children })] })] }));
19
- }
@@ -1,8 +0,0 @@
1
- export interface PaginationProps {
2
- currentPage: number;
3
- totalPages: number;
4
- totalItems: number;
5
- onPageChange: (page: number) => void;
6
- }
7
- export declare function Pagination({ currentPage, totalPages, totalItems, onPageChange, }: PaginationProps): import("react/jsx-runtime").JSX.Element | null;
8
- //# sourceMappingURL=Pagination.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Pagination.d.ts","sourceRoot":"","sources":["../src/Pagination.tsx"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC;AAED,wBAAgB,UAAU,CAAC,EAC1B,WAAW,EACX,UAAU,EACV,UAAU,EACV,YAAY,GACZ,EAAE,eAAe,kDA4BjB"}
@@ -1,7 +0,0 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- export function Pagination({ currentPage, totalPages, totalItems, onPageChange, }) {
3
- if (totalPages <= 1) {
4
- return null;
5
- }
6
- return (_jsxs("div", { className: "ec-pagination", children: [_jsxs("span", { className: "ec-pagination__info", children: ["Page ", currentPage, " of ", totalPages, " (", totalItems, " items)"] }), _jsxs("div", { className: "ec-pagination__buttons", children: [_jsx("button", { type: "button", disabled: currentPage <= 1, onClick: () => onPageChange(currentPage - 1), children: "Previous" }), _jsx("button", { type: "button", disabled: currentPage >= totalPages, onClick: () => onPageChange(currentPage + 1), children: "Next" })] })] }));
7
- }
@@ -1,8 +0,0 @@
1
- export interface SearchBoxProps {
2
- value?: string;
3
- onSearch: (value: string) => void;
4
- placeholder?: string;
5
- onClear?: () => void;
6
- }
7
- export declare function SearchBox({ value, onSearch, placeholder, onClear, }: SearchBoxProps): import("react/jsx-runtime").JSX.Element;
8
- //# sourceMappingURL=SearchBox.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SearchBox.d.ts","sourceRoot":"","sources":["../src/SearchBox.tsx"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,SAAS,CAAC,EACzB,KAAU,EACV,QAAQ,EACR,WAAyB,EACzB,OAAO,GACP,EAAE,cAAc,2CAyChB"}
package/dist/SearchBox.js DELETED
@@ -1,23 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from 'react';
3
- export function SearchBox({ value = '', onSearch, placeholder = 'Search...', onClear, }) {
4
- const [inputValue, setInputValue] = useState(value);
5
- const handleSearch = () => {
6
- onSearch(inputValue);
7
- };
8
- const handleClear = () => {
9
- setInputValue('');
10
- if (onClear) {
11
- onClear();
12
- }
13
- else {
14
- onSearch('');
15
- }
16
- };
17
- const handleKeyDown = (e) => {
18
- if (e.key === 'Enter') {
19
- handleSearch();
20
- }
21
- };
22
- return (_jsxs("div", { className: "ec-search-box", children: [_jsx("input", { type: "text", value: inputValue, onChange: (e) => setInputValue(e.target.value), placeholder: placeholder, onKeyDown: handleKeyDown }), _jsx("button", { type: "button", onClick: handleSearch, children: "Search" }), inputValue && (_jsx("button", { type: "button", onClick: handleClear, children: "Clear" }))] }));
23
- }
package/dist/Tabs.d.ts DELETED
@@ -1,14 +0,0 @@
1
- export interface TabItem {
2
- id: string;
3
- label: string;
4
- badge?: number;
5
- }
6
- export interface TabsProps {
7
- tabs: TabItem[];
8
- active: string;
9
- onChange: (id: string) => void;
10
- classPrefix?: string;
11
- className?: string;
12
- }
13
- export declare function Tabs({ tabs, active, onChange, classPrefix, className, }: TabsProps): import("react/jsx-runtime").JSX.Element | null;
14
- //# sourceMappingURL=Tabs.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Tabs.d.ts","sourceRoot":"","sources":["../src/Tabs.tsx"],"names":[],"mappings":"AAAA,MAAM,WAAW,OAAO;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,IAAI,CAAC,EACpB,IAAS,EACT,MAAM,EACN,QAAQ,EACR,WAAuB,EACvB,SAAc,GACd,EAAE,SAAS,kDA8BX"}
package/dist/Tabs.js DELETED
@@ -1,11 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- export function Tabs({ tabs = [], active, onChange, classPrefix = 'ec-tabs', className = '', }) {
3
- if (tabs.length === 0) {
4
- return null;
5
- }
6
- const rootClass = [`${classPrefix}__tabs`, className].filter(Boolean).join(' ');
7
- return (_jsx("div", { className: rootClass, role: "tablist", "aria-orientation": "horizontal", children: tabs.map((tab) => {
8
- const isActive = active === tab.id;
9
- return (_jsxs("button", { type: "button", role: "tab", "aria-selected": isActive, className: `${classPrefix}__tab${isActive ? ' is-active' : ''}`, onClick: () => onChange(tab.id), children: [tab.label, tab.badge != null && tab.badge > 0 && (_jsx("span", { className: `${classPrefix}__tab-badge`, children: tab.badge }))] }, tab.id));
10
- }) }));
11
- }
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvF,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,KAAK,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,KAAK,SAAS,EAAE,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC"}
package/src/DataTable.tsx DELETED
@@ -1,109 +0,0 @@
1
- import type { ReactNode } from 'react';
2
-
3
- export interface DataTableColumn<T = Record<string, unknown>> {
4
- key: string;
5
- label: string;
6
- width?: string;
7
- render?: (value: unknown, row: T) => ReactNode;
8
- }
9
-
10
- export interface DataTableProps<T = Record<string, unknown>> {
11
- columns: DataTableColumn<T>[];
12
- data: T[];
13
- isLoading?: boolean;
14
- selectable?: boolean;
15
- selectedIds?: Array<string | number>;
16
- onSelectChange?: (ids: Array<string | number>) => void;
17
- emptyMessage?: string;
18
- rowKey?: string;
19
- }
20
-
21
- export function DataTable<T extends Record<string, unknown>>({
22
- columns,
23
- data,
24
- isLoading = false,
25
- selectable = false,
26
- selectedIds = [],
27
- onSelectChange = () => {},
28
- emptyMessage = 'No data found.',
29
- rowKey = 'id',
30
- }: DataTableProps<T>) {
31
- const allSelected = data.length > 0 && selectedIds.length === data.length;
32
-
33
- const handleSelectAll = (e: React.ChangeEvent<HTMLInputElement>) => {
34
- if (e.target.checked) {
35
- onSelectChange(data.map((row) => row[rowKey] as string | number));
36
- } else {
37
- onSelectChange([]);
38
- }
39
- };
40
-
41
- const handleSelectRow = (id: string | number, checked: boolean) => {
42
- if (checked) {
43
- onSelectChange([...selectedIds, id]);
44
- } else {
45
- onSelectChange(selectedIds.filter((sid) => sid !== id));
46
- }
47
- };
48
-
49
- if (isLoading) {
50
- return (
51
- <div className="ec-data-table__loading">
52
- <span>Loading...</span>
53
- </div>
54
- );
55
- }
56
-
57
- if (data.length === 0) {
58
- return (
59
- <div className="ec-data-table__empty">
60
- <p>{emptyMessage}</p>
61
- </div>
62
- );
63
- }
64
-
65
- return (
66
- <table className="ec-data-table wp-list-table widefat fixed striped">
67
- <thead>
68
- <tr>
69
- {selectable && (
70
- <th className="ec-data-table__check-column">
71
- <input
72
- type="checkbox"
73
- checked={allSelected}
74
- onChange={handleSelectAll}
75
- />
76
- </th>
77
- )}
78
- {columns.map((col) => (
79
- <th key={col.key} style={col.width ? { width: col.width } : undefined}>
80
- {col.label}
81
- </th>
82
- ))}
83
- </tr>
84
- </thead>
85
- <tbody>
86
- {data.map((row) => (
87
- <tr key={row[rowKey] as string | number}>
88
- {selectable && (
89
- <td className="ec-data-table__check-column">
90
- <input
91
- type="checkbox"
92
- checked={selectedIds.includes(row[rowKey] as string | number)}
93
- onChange={(e) =>
94
- handleSelectRow(row[rowKey] as string | number, e.target.checked)
95
- }
96
- />
97
- </td>
98
- )}
99
- {columns.map((col) => (
100
- <td key={col.key}>
101
- {col.render ? col.render(row[col.key], row) : (row[col.key] as ReactNode)}
102
- </td>
103
- ))}
104
- </tr>
105
- ))}
106
- </tbody>
107
- </table>
108
- );
109
- }
package/src/Modal.tsx DELETED
@@ -1,52 +0,0 @@
1
- import { useEffect, useCallback, type ReactNode } from 'react';
2
-
3
- export interface ModalProps {
4
- title: string;
5
- isOpen: boolean;
6
- onClose: () => void;
7
- children: ReactNode;
8
- className?: string;
9
- }
10
-
11
- export function Modal({
12
- title,
13
- isOpen,
14
- onClose,
15
- children,
16
- className = '',
17
- }: ModalProps) {
18
- const handleKeyDown = useCallback(
19
- (e: KeyboardEvent) => {
20
- if (e.key === 'Escape') {
21
- onClose();
22
- }
23
- },
24
- [onClose]
25
- );
26
-
27
- useEffect(() => {
28
- if (isOpen) {
29
- document.addEventListener('keydown', handleKeyDown);
30
- return () => document.removeEventListener('keydown', handleKeyDown);
31
- }
32
- }, [isOpen, handleKeyDown]);
33
-
34
- if (!isOpen) {
35
- return null;
36
- }
37
-
38
- return (
39
- <div className={`ec-modal ${className}`} role="dialog" aria-label={title}>
40
- <div className="ec-modal__backdrop" onClick={onClose} aria-hidden="true" />
41
- <div className="ec-modal__content">
42
- <div className="ec-modal__header">
43
- <h2>{title}</h2>
44
- <button type="button" onClick={onClose} aria-label="Close">
45
- &times;
46
- </button>
47
- </div>
48
- <div className="ec-modal__body">{children}</div>
49
- </div>
50
- </div>
51
- );
52
- }
@@ -1,41 +0,0 @@
1
- export interface PaginationProps {
2
- currentPage: number;
3
- totalPages: number;
4
- totalItems: number;
5
- onPageChange: (page: number) => void;
6
- }
7
-
8
- export function Pagination({
9
- currentPage,
10
- totalPages,
11
- totalItems,
12
- onPageChange,
13
- }: PaginationProps) {
14
- if (totalPages <= 1) {
15
- return null;
16
- }
17
-
18
- return (
19
- <div className="ec-pagination">
20
- <span className="ec-pagination__info">
21
- Page {currentPage} of {totalPages} ({totalItems} items)
22
- </span>
23
- <div className="ec-pagination__buttons">
24
- <button
25
- type="button"
26
- disabled={currentPage <= 1}
27
- onClick={() => onPageChange(currentPage - 1)}
28
- >
29
- Previous
30
- </button>
31
- <button
32
- type="button"
33
- disabled={currentPage >= totalPages}
34
- onClick={() => onPageChange(currentPage + 1)}
35
- >
36
- Next
37
- </button>
38
- </div>
39
- </div>
40
- );
41
- }
package/src/SearchBox.tsx DELETED
@@ -1,56 +0,0 @@
1
- import { useState, type KeyboardEvent } from 'react';
2
-
3
- export interface SearchBoxProps {
4
- value?: string;
5
- onSearch: (value: string) => void;
6
- placeholder?: string;
7
- onClear?: () => void;
8
- }
9
-
10
- export function SearchBox({
11
- value = '',
12
- onSearch,
13
- placeholder = 'Search...',
14
- onClear,
15
- }: SearchBoxProps) {
16
- const [inputValue, setInputValue] = useState(value);
17
-
18
- const handleSearch = () => {
19
- onSearch(inputValue);
20
- };
21
-
22
- const handleClear = () => {
23
- setInputValue('');
24
- if (onClear) {
25
- onClear();
26
- } else {
27
- onSearch('');
28
- }
29
- };
30
-
31
- const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
32
- if (e.key === 'Enter') {
33
- handleSearch();
34
- }
35
- };
36
-
37
- return (
38
- <div className="ec-search-box">
39
- <input
40
- type="text"
41
- value={inputValue}
42
- onChange={(e) => setInputValue(e.target.value)}
43
- placeholder={placeholder}
44
- onKeyDown={handleKeyDown}
45
- />
46
- <button type="button" onClick={handleSearch}>
47
- Search
48
- </button>
49
- {inputValue && (
50
- <button type="button" onClick={handleClear}>
51
- Clear
52
- </button>
53
- )}
54
- </div>
55
- );
56
- }
package/src/Tabs.tsx DELETED
@@ -1,51 +0,0 @@
1
- export interface TabItem {
2
- id: string;
3
- label: string;
4
- badge?: number;
5
- }
6
-
7
- export interface TabsProps {
8
- tabs: TabItem[];
9
- active: string;
10
- onChange: (id: string) => void;
11
- classPrefix?: string;
12
- className?: string;
13
- }
14
-
15
- export function Tabs({
16
- tabs = [],
17
- active,
18
- onChange,
19
- classPrefix = 'ec-tabs',
20
- className = '',
21
- }: TabsProps) {
22
- if (tabs.length === 0) {
23
- return null;
24
- }
25
-
26
- const rootClass = [`${classPrefix}__tabs`, className].filter(Boolean).join(' ');
27
-
28
- return (
29
- <div className={rootClass} role="tablist" aria-orientation="horizontal">
30
- {tabs.map((tab) => {
31
- const isActive = active === tab.id;
32
-
33
- return (
34
- <button
35
- key={tab.id}
36
- type="button"
37
- role="tab"
38
- aria-selected={isActive}
39
- className={`${classPrefix}__tab${isActive ? ' is-active' : ''}`}
40
- onClick={() => onChange(tab.id)}
41
- >
42
- {tab.label}
43
- {tab.badge != null && tab.badge > 0 && (
44
- <span className={`${classPrefix}__tab-badge`}>{tab.badge}</span>
45
- )}
46
- </button>
47
- );
48
- })}
49
- </div>
50
- );
51
- }