@leduchai/vmeta-ui-core 0.1.3

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 ADDED
@@ -0,0 +1,3 @@
1
+ # @leduchai/vmeta-ui-core
2
+
3
+ Primitive React dung chung cho page header, button, card, form, table va system state. Package khong chua API, route, Redux hoac permission cua tung san pham.
package/dist/index.cjs ADDED
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.tsx
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ VmetaButton: () => VmetaButton,
24
+ VmetaCard: () => VmetaCard,
25
+ VmetaDataTable: () => VmetaDataTable,
26
+ VmetaFormItem: () => VmetaFormItem,
27
+ VmetaFormPage: () => VmetaFormPage,
28
+ VmetaFormSection: () => VmetaFormSection,
29
+ VmetaInput: () => VmetaInput,
30
+ VmetaPageHeader: () => VmetaPageHeader,
31
+ VmetaSelect: () => VmetaSelect,
32
+ VmetaSystemState: () => VmetaSystemState,
33
+ VmetaTextArea: () => VmetaTextArea
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+ var import_jsx_runtime = require("react/jsx-runtime");
37
+ function joinClassNames(...values) {
38
+ return values.filter(Boolean).join(" ");
39
+ }
40
+ function VmetaButton({
41
+ tone = "primary",
42
+ variant = "solid",
43
+ loading = false,
44
+ disabled,
45
+ className,
46
+ children,
47
+ type = "button",
48
+ ...props
49
+ }) {
50
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
51
+ "button",
52
+ {
53
+ type,
54
+ className: joinClassNames("vmeta-button", `is-${tone}`, `is-${variant}`, className),
55
+ disabled: disabled || loading,
56
+ "aria-busy": loading || void 0,
57
+ ...props,
58
+ children: [
59
+ loading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "vmeta-button__spinner", "aria-hidden": "true" }),
60
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children })
61
+ ]
62
+ }
63
+ );
64
+ }
65
+ function VmetaCard({ as: Component = "section", className, ...props }) {
66
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Component, { className: joinClassNames("vmeta-card", className), ...props });
67
+ }
68
+ function VmetaPageHeader({
69
+ eyebrow,
70
+ title,
71
+ description,
72
+ actions,
73
+ breadcrumb,
74
+ className,
75
+ ...props
76
+ }) {
77
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: joinClassNames("vmeta-page-header", className), ...props, children: [
78
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vmeta-page-header__copy", children: [
79
+ breadcrumb && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "vmeta-page-header__breadcrumb", children: breadcrumb }),
80
+ eyebrow && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "vmeta-page-header__eyebrow", children: eyebrow }),
81
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { children: title }),
82
+ description && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "vmeta-page-header__description", children: description })
83
+ ] }),
84
+ actions && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "vmeta-page-header__actions", children: actions })
85
+ ] });
86
+ }
87
+ function VmetaSystemState({
88
+ tone = "empty",
89
+ title,
90
+ description,
91
+ action,
92
+ icon,
93
+ className,
94
+ ...props
95
+ }) {
96
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
97
+ "section",
98
+ {
99
+ className: joinClassNames("vmeta-system-state", `is-${tone}`, className),
100
+ role: tone === "error" ? "alert" : "status",
101
+ ...props,
102
+ children: [
103
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "vmeta-system-state__icon", "aria-hidden": "true", children: icon ?? (tone === "loading" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "vmeta-system-state__spinner" }) : "V") }),
104
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: title }),
105
+ description && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: description }),
106
+ action && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "vmeta-system-state__action", children: action })
107
+ ]
108
+ }
109
+ );
110
+ }
111
+ function VmetaFormPage({ actions, className, children, ...props }) {
112
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { className: joinClassNames("vmeta-form-page", className), ...props, children: [
113
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "vmeta-form-page__body", children }),
114
+ actions && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "vmeta-form-actions", children: actions })
115
+ ] });
116
+ }
117
+ function VmetaFormSection({
118
+ title,
119
+ description,
120
+ columns = 2,
121
+ className,
122
+ children,
123
+ ...props
124
+ }) {
125
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: joinClassNames("vmeta-form-section", className), ...props, children: [
126
+ (title || description) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { children: [
127
+ title && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: title }),
128
+ description && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: description })
129
+ ] }),
130
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "vmeta-form-grid", "data-columns": columns, children })
131
+ ] });
132
+ }
133
+ function VmetaFormItem({
134
+ label,
135
+ htmlFor,
136
+ required,
137
+ help,
138
+ error,
139
+ fullWidth,
140
+ className,
141
+ children,
142
+ ...props
143
+ }) {
144
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
145
+ "div",
146
+ {
147
+ className: joinClassNames("vmeta-form-item", fullWidth && "is-full-width", Boolean(error) && "has-error", className),
148
+ ...props,
149
+ children: [
150
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { htmlFor, children: [
151
+ label,
152
+ required && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: " *" })
153
+ ] }),
154
+ children,
155
+ (error || help) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { role: error ? "alert" : void 0, children: error ?? help })
156
+ ]
157
+ }
158
+ );
159
+ }
160
+ function VmetaInput({ className, ...props }) {
161
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: joinClassNames("vmeta-control", className), ...props });
162
+ }
163
+ function VmetaTextArea({ className, ...props }) {
164
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", { className: joinClassNames("vmeta-control", "vmeta-textarea", className), ...props });
165
+ }
166
+ function VmetaSelect({ className, children, ...props }) {
167
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", { className: joinClassNames("vmeta-control", className), ...props, children });
168
+ }
169
+ function VmetaDataTable({
170
+ columns,
171
+ data,
172
+ rowKey,
173
+ loading,
174
+ error,
175
+ emptyText = "Chua co du lieu",
176
+ onRetry,
177
+ className,
178
+ ...props
179
+ }) {
180
+ if (loading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VmetaSystemState, { tone: "loading", title: "Dang tai du lieu..." });
181
+ if (error) {
182
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
183
+ VmetaSystemState,
184
+ {
185
+ tone: "error",
186
+ title: "Khong the tai du lieu",
187
+ description: error,
188
+ action: onRetry && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VmetaButton, { onClick: onRetry, children: "Thu lai" })
189
+ }
190
+ );
191
+ }
192
+ if (data.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VmetaSystemState, { tone: "empty", title: emptyText });
193
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: joinClassNames("vmeta-data-table", className), ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { children: [
194
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: columns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: { width: column.width, textAlign: column.align }, children: column.title }, column.key)) }) }),
195
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: data.map((row, rowIndex) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: columns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: { textAlign: column.align }, children: column.render(row, rowIndex) }, column.key)) }, rowKey(row, rowIndex))) })
196
+ ] }) });
197
+ }
198
+ // Annotate the CommonJS export names for ESM import in node:
199
+ 0 && (module.exports = {
200
+ VmetaButton,
201
+ VmetaCard,
202
+ VmetaDataTable,
203
+ VmetaFormItem,
204
+ VmetaFormPage,
205
+ VmetaFormSection,
206
+ VmetaInput,
207
+ VmetaPageHeader,
208
+ VmetaSelect,
209
+ VmetaSystemState,
210
+ VmetaTextArea
211
+ });
@@ -0,0 +1,72 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ButtonHTMLAttributes, HTMLAttributes, ReactNode, InputHTMLAttributes, SelectHTMLAttributes, TextareaHTMLAttributes } from 'react';
3
+
4
+ type VmetaTone = 'primary' | 'secondary' | 'neutral' | 'danger';
5
+ type VmetaButtonVariant = 'solid' | 'outline' | 'ghost';
6
+ interface VmetaButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
7
+ tone?: VmetaTone;
8
+ variant?: VmetaButtonVariant;
9
+ loading?: boolean;
10
+ }
11
+ declare function VmetaButton({ tone, variant, loading, disabled, className, children, type, ...props }: VmetaButtonProps): react_jsx_runtime.JSX.Element;
12
+ interface VmetaCardProps extends HTMLAttributes<HTMLElement> {
13
+ as?: 'article' | 'section' | 'div';
14
+ }
15
+ declare function VmetaCard({ as: Component, className, ...props }: VmetaCardProps): react_jsx_runtime.JSX.Element;
16
+ interface VmetaPageHeaderProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
17
+ eyebrow?: ReactNode;
18
+ title: ReactNode;
19
+ description?: ReactNode;
20
+ actions?: ReactNode;
21
+ breadcrumb?: ReactNode;
22
+ }
23
+ declare function VmetaPageHeader({ eyebrow, title, description, actions, breadcrumb, className, ...props }: VmetaPageHeaderProps): react_jsx_runtime.JSX.Element;
24
+ interface VmetaSystemStateProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
25
+ tone?: 'loading' | 'empty' | 'error' | 'forbidden' | 'not-found';
26
+ title: ReactNode;
27
+ description?: ReactNode;
28
+ action?: ReactNode;
29
+ icon?: ReactNode;
30
+ }
31
+ declare function VmetaSystemState({ tone, title, description, action, icon, className, ...props }: VmetaSystemStateProps): react_jsx_runtime.JSX.Element;
32
+ interface VmetaFormPageProps extends HTMLAttributes<HTMLFormElement> {
33
+ actions?: ReactNode;
34
+ }
35
+ declare function VmetaFormPage({ actions, className, children, ...props }: VmetaFormPageProps): react_jsx_runtime.JSX.Element;
36
+ interface VmetaFormSectionProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
37
+ title?: ReactNode;
38
+ description?: ReactNode;
39
+ columns?: 1 | 2 | 3;
40
+ }
41
+ declare function VmetaFormSection({ title, description, columns, className, children, ...props }: VmetaFormSectionProps): react_jsx_runtime.JSX.Element;
42
+ interface VmetaFormItemProps extends HTMLAttributes<HTMLDivElement> {
43
+ label: ReactNode;
44
+ htmlFor?: string;
45
+ required?: boolean;
46
+ help?: ReactNode;
47
+ error?: ReactNode;
48
+ fullWidth?: boolean;
49
+ }
50
+ declare function VmetaFormItem({ label, htmlFor, required, help, error, fullWidth, className, children, ...props }: VmetaFormItemProps): react_jsx_runtime.JSX.Element;
51
+ declare function VmetaInput({ className, ...props }: InputHTMLAttributes<HTMLInputElement>): react_jsx_runtime.JSX.Element;
52
+ declare function VmetaTextArea({ className, ...props }: TextareaHTMLAttributes<HTMLTextAreaElement>): react_jsx_runtime.JSX.Element;
53
+ declare function VmetaSelect({ className, children, ...props }: SelectHTMLAttributes<HTMLSelectElement>): react_jsx_runtime.JSX.Element;
54
+ interface VmetaDataTableColumn<T> {
55
+ key: string;
56
+ title: ReactNode;
57
+ render: (row: T, index: number) => ReactNode;
58
+ width?: string | number;
59
+ align?: 'left' | 'center' | 'right';
60
+ }
61
+ interface VmetaDataTableProps<T> extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
62
+ columns: Array<VmetaDataTableColumn<T>>;
63
+ data: T[];
64
+ rowKey: (row: T, index: number) => string | number;
65
+ loading?: boolean;
66
+ error?: ReactNode;
67
+ emptyText?: ReactNode;
68
+ onRetry?: () => void;
69
+ }
70
+ declare function VmetaDataTable<T>({ columns, data, rowKey, loading, error, emptyText, onRetry, className, ...props }: VmetaDataTableProps<T>): react_jsx_runtime.JSX.Element;
71
+
72
+ export { VmetaButton, type VmetaButtonProps, type VmetaButtonVariant, VmetaCard, type VmetaCardProps, VmetaDataTable, type VmetaDataTableColumn, type VmetaDataTableProps, VmetaFormItem, type VmetaFormItemProps, VmetaFormPage, type VmetaFormPageProps, VmetaFormSection, type VmetaFormSectionProps, VmetaInput, VmetaPageHeader, type VmetaPageHeaderProps, VmetaSelect, VmetaSystemState, type VmetaSystemStateProps, VmetaTextArea, type VmetaTone };
@@ -0,0 +1,72 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ButtonHTMLAttributes, HTMLAttributes, ReactNode, InputHTMLAttributes, SelectHTMLAttributes, TextareaHTMLAttributes } from 'react';
3
+
4
+ type VmetaTone = 'primary' | 'secondary' | 'neutral' | 'danger';
5
+ type VmetaButtonVariant = 'solid' | 'outline' | 'ghost';
6
+ interface VmetaButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
7
+ tone?: VmetaTone;
8
+ variant?: VmetaButtonVariant;
9
+ loading?: boolean;
10
+ }
11
+ declare function VmetaButton({ tone, variant, loading, disabled, className, children, type, ...props }: VmetaButtonProps): react_jsx_runtime.JSX.Element;
12
+ interface VmetaCardProps extends HTMLAttributes<HTMLElement> {
13
+ as?: 'article' | 'section' | 'div';
14
+ }
15
+ declare function VmetaCard({ as: Component, className, ...props }: VmetaCardProps): react_jsx_runtime.JSX.Element;
16
+ interface VmetaPageHeaderProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
17
+ eyebrow?: ReactNode;
18
+ title: ReactNode;
19
+ description?: ReactNode;
20
+ actions?: ReactNode;
21
+ breadcrumb?: ReactNode;
22
+ }
23
+ declare function VmetaPageHeader({ eyebrow, title, description, actions, breadcrumb, className, ...props }: VmetaPageHeaderProps): react_jsx_runtime.JSX.Element;
24
+ interface VmetaSystemStateProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
25
+ tone?: 'loading' | 'empty' | 'error' | 'forbidden' | 'not-found';
26
+ title: ReactNode;
27
+ description?: ReactNode;
28
+ action?: ReactNode;
29
+ icon?: ReactNode;
30
+ }
31
+ declare function VmetaSystemState({ tone, title, description, action, icon, className, ...props }: VmetaSystemStateProps): react_jsx_runtime.JSX.Element;
32
+ interface VmetaFormPageProps extends HTMLAttributes<HTMLFormElement> {
33
+ actions?: ReactNode;
34
+ }
35
+ declare function VmetaFormPage({ actions, className, children, ...props }: VmetaFormPageProps): react_jsx_runtime.JSX.Element;
36
+ interface VmetaFormSectionProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
37
+ title?: ReactNode;
38
+ description?: ReactNode;
39
+ columns?: 1 | 2 | 3;
40
+ }
41
+ declare function VmetaFormSection({ title, description, columns, className, children, ...props }: VmetaFormSectionProps): react_jsx_runtime.JSX.Element;
42
+ interface VmetaFormItemProps extends HTMLAttributes<HTMLDivElement> {
43
+ label: ReactNode;
44
+ htmlFor?: string;
45
+ required?: boolean;
46
+ help?: ReactNode;
47
+ error?: ReactNode;
48
+ fullWidth?: boolean;
49
+ }
50
+ declare function VmetaFormItem({ label, htmlFor, required, help, error, fullWidth, className, children, ...props }: VmetaFormItemProps): react_jsx_runtime.JSX.Element;
51
+ declare function VmetaInput({ className, ...props }: InputHTMLAttributes<HTMLInputElement>): react_jsx_runtime.JSX.Element;
52
+ declare function VmetaTextArea({ className, ...props }: TextareaHTMLAttributes<HTMLTextAreaElement>): react_jsx_runtime.JSX.Element;
53
+ declare function VmetaSelect({ className, children, ...props }: SelectHTMLAttributes<HTMLSelectElement>): react_jsx_runtime.JSX.Element;
54
+ interface VmetaDataTableColumn<T> {
55
+ key: string;
56
+ title: ReactNode;
57
+ render: (row: T, index: number) => ReactNode;
58
+ width?: string | number;
59
+ align?: 'left' | 'center' | 'right';
60
+ }
61
+ interface VmetaDataTableProps<T> extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
62
+ columns: Array<VmetaDataTableColumn<T>>;
63
+ data: T[];
64
+ rowKey: (row: T, index: number) => string | number;
65
+ loading?: boolean;
66
+ error?: ReactNode;
67
+ emptyText?: ReactNode;
68
+ onRetry?: () => void;
69
+ }
70
+ declare function VmetaDataTable<T>({ columns, data, rowKey, loading, error, emptyText, onRetry, className, ...props }: VmetaDataTableProps<T>): react_jsx_runtime.JSX.Element;
71
+
72
+ export { VmetaButton, type VmetaButtonProps, type VmetaButtonVariant, VmetaCard, type VmetaCardProps, VmetaDataTable, type VmetaDataTableColumn, type VmetaDataTableProps, VmetaFormItem, type VmetaFormItemProps, VmetaFormPage, type VmetaFormPageProps, VmetaFormSection, type VmetaFormSectionProps, VmetaInput, VmetaPageHeader, type VmetaPageHeaderProps, VmetaSelect, VmetaSystemState, type VmetaSystemStateProps, VmetaTextArea, type VmetaTone };
package/dist/index.js ADDED
@@ -0,0 +1,176 @@
1
+ // src/index.tsx
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ function joinClassNames(...values) {
4
+ return values.filter(Boolean).join(" ");
5
+ }
6
+ function VmetaButton({
7
+ tone = "primary",
8
+ variant = "solid",
9
+ loading = false,
10
+ disabled,
11
+ className,
12
+ children,
13
+ type = "button",
14
+ ...props
15
+ }) {
16
+ return /* @__PURE__ */ jsxs(
17
+ "button",
18
+ {
19
+ type,
20
+ className: joinClassNames("vmeta-button", `is-${tone}`, `is-${variant}`, className),
21
+ disabled: disabled || loading,
22
+ "aria-busy": loading || void 0,
23
+ ...props,
24
+ children: [
25
+ loading && /* @__PURE__ */ jsx("span", { className: "vmeta-button__spinner", "aria-hidden": "true" }),
26
+ /* @__PURE__ */ jsx("span", { children })
27
+ ]
28
+ }
29
+ );
30
+ }
31
+ function VmetaCard({ as: Component = "section", className, ...props }) {
32
+ return /* @__PURE__ */ jsx(Component, { className: joinClassNames("vmeta-card", className), ...props });
33
+ }
34
+ function VmetaPageHeader({
35
+ eyebrow,
36
+ title,
37
+ description,
38
+ actions,
39
+ breadcrumb,
40
+ className,
41
+ ...props
42
+ }) {
43
+ return /* @__PURE__ */ jsxs("header", { className: joinClassNames("vmeta-page-header", className), ...props, children: [
44
+ /* @__PURE__ */ jsxs("div", { className: "vmeta-page-header__copy", children: [
45
+ breadcrumb && /* @__PURE__ */ jsx("div", { className: "vmeta-page-header__breadcrumb", children: breadcrumb }),
46
+ eyebrow && /* @__PURE__ */ jsx("p", { className: "vmeta-page-header__eyebrow", children: eyebrow }),
47
+ /* @__PURE__ */ jsx("h1", { children: title }),
48
+ description && /* @__PURE__ */ jsx("div", { className: "vmeta-page-header__description", children: description })
49
+ ] }),
50
+ actions && /* @__PURE__ */ jsx("div", { className: "vmeta-page-header__actions", children: actions })
51
+ ] });
52
+ }
53
+ function VmetaSystemState({
54
+ tone = "empty",
55
+ title,
56
+ description,
57
+ action,
58
+ icon,
59
+ className,
60
+ ...props
61
+ }) {
62
+ return /* @__PURE__ */ jsxs(
63
+ "section",
64
+ {
65
+ className: joinClassNames("vmeta-system-state", `is-${tone}`, className),
66
+ role: tone === "error" ? "alert" : "status",
67
+ ...props,
68
+ children: [
69
+ /* @__PURE__ */ jsx("div", { className: "vmeta-system-state__icon", "aria-hidden": "true", children: icon ?? (tone === "loading" ? /* @__PURE__ */ jsx("span", { className: "vmeta-system-state__spinner" }) : "V") }),
70
+ /* @__PURE__ */ jsx("h2", { children: title }),
71
+ description && /* @__PURE__ */ jsx("p", { children: description }),
72
+ action && /* @__PURE__ */ jsx("div", { className: "vmeta-system-state__action", children: action })
73
+ ]
74
+ }
75
+ );
76
+ }
77
+ function VmetaFormPage({ actions, className, children, ...props }) {
78
+ return /* @__PURE__ */ jsxs("form", { className: joinClassNames("vmeta-form-page", className), ...props, children: [
79
+ /* @__PURE__ */ jsx("div", { className: "vmeta-form-page__body", children }),
80
+ actions && /* @__PURE__ */ jsx("div", { className: "vmeta-form-actions", children: actions })
81
+ ] });
82
+ }
83
+ function VmetaFormSection({
84
+ title,
85
+ description,
86
+ columns = 2,
87
+ className,
88
+ children,
89
+ ...props
90
+ }) {
91
+ return /* @__PURE__ */ jsxs("section", { className: joinClassNames("vmeta-form-section", className), ...props, children: [
92
+ (title || description) && /* @__PURE__ */ jsxs("header", { children: [
93
+ title && /* @__PURE__ */ jsx("h2", { children: title }),
94
+ description && /* @__PURE__ */ jsx("p", { children: description })
95
+ ] }),
96
+ /* @__PURE__ */ jsx("div", { className: "vmeta-form-grid", "data-columns": columns, children })
97
+ ] });
98
+ }
99
+ function VmetaFormItem({
100
+ label,
101
+ htmlFor,
102
+ required,
103
+ help,
104
+ error,
105
+ fullWidth,
106
+ className,
107
+ children,
108
+ ...props
109
+ }) {
110
+ return /* @__PURE__ */ jsxs(
111
+ "div",
112
+ {
113
+ className: joinClassNames("vmeta-form-item", fullWidth && "is-full-width", Boolean(error) && "has-error", className),
114
+ ...props,
115
+ children: [
116
+ /* @__PURE__ */ jsxs("label", { htmlFor, children: [
117
+ label,
118
+ required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: " *" })
119
+ ] }),
120
+ children,
121
+ (error || help) && /* @__PURE__ */ jsx("small", { role: error ? "alert" : void 0, children: error ?? help })
122
+ ]
123
+ }
124
+ );
125
+ }
126
+ function VmetaInput({ className, ...props }) {
127
+ return /* @__PURE__ */ jsx("input", { className: joinClassNames("vmeta-control", className), ...props });
128
+ }
129
+ function VmetaTextArea({ className, ...props }) {
130
+ return /* @__PURE__ */ jsx("textarea", { className: joinClassNames("vmeta-control", "vmeta-textarea", className), ...props });
131
+ }
132
+ function VmetaSelect({ className, children, ...props }) {
133
+ return /* @__PURE__ */ jsx("select", { className: joinClassNames("vmeta-control", className), ...props, children });
134
+ }
135
+ function VmetaDataTable({
136
+ columns,
137
+ data,
138
+ rowKey,
139
+ loading,
140
+ error,
141
+ emptyText = "Chua co du lieu",
142
+ onRetry,
143
+ className,
144
+ ...props
145
+ }) {
146
+ if (loading) return /* @__PURE__ */ jsx(VmetaSystemState, { tone: "loading", title: "Dang tai du lieu..." });
147
+ if (error) {
148
+ return /* @__PURE__ */ jsx(
149
+ VmetaSystemState,
150
+ {
151
+ tone: "error",
152
+ title: "Khong the tai du lieu",
153
+ description: error,
154
+ action: onRetry && /* @__PURE__ */ jsx(VmetaButton, { onClick: onRetry, children: "Thu lai" })
155
+ }
156
+ );
157
+ }
158
+ if (data.length === 0) return /* @__PURE__ */ jsx(VmetaSystemState, { tone: "empty", title: emptyText });
159
+ return /* @__PURE__ */ jsx("div", { className: joinClassNames("vmeta-data-table", className), ...props, children: /* @__PURE__ */ jsxs("table", { children: [
160
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: columns.map((column) => /* @__PURE__ */ jsx("th", { style: { width: column.width, textAlign: column.align }, children: column.title }, column.key)) }) }),
161
+ /* @__PURE__ */ jsx("tbody", { children: data.map((row, rowIndex) => /* @__PURE__ */ jsx("tr", { children: columns.map((column) => /* @__PURE__ */ jsx("td", { style: { textAlign: column.align }, children: column.render(row, rowIndex) }, column.key)) }, rowKey(row, rowIndex))) })
162
+ ] }) });
163
+ }
164
+ export {
165
+ VmetaButton,
166
+ VmetaCard,
167
+ VmetaDataTable,
168
+ VmetaFormItem,
169
+ VmetaFormPage,
170
+ VmetaFormSection,
171
+ VmetaInput,
172
+ VmetaPageHeader,
173
+ VmetaSelect,
174
+ VmetaSystemState,
175
+ VmetaTextArea
176
+ };
@@ -0,0 +1,67 @@
1
+ .vmeta-button {
2
+ min-height: var(--control-height, 40px);
3
+ display: inline-flex;
4
+ align-items: center;
5
+ justify-content: center;
6
+ gap: 8px;
7
+ padding: 0 16px;
8
+ border: 1px solid transparent;
9
+ border-radius: var(--radius-md, 12px);
10
+ font: 600 14px/1 var(--font-sans, sans-serif);
11
+ transition: color var(--motion-fast), background var(--motion-fast), border-color var(--motion-fast), box-shadow var(--motion-fast), transform var(--motion-fast);
12
+ }
13
+ .vmeta-button:not(:disabled) { cursor: pointer; }
14
+ .vmeta-button:not(:disabled):active { transform: translateY(1px); }
15
+ .vmeta-button:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
16
+ .vmeta-button:disabled { cursor: not-allowed; opacity: .58; }
17
+ .vmeta-button.is-primary.is-solid { color: var(--color-surface); background: var(--color-primary); }
18
+ .vmeta-button.is-secondary.is-solid { color: var(--color-surface); background: var(--color-secondary); }
19
+ .vmeta-button.is-danger.is-solid { color: var(--color-surface); background: var(--color-error); }
20
+ .vmeta-button.is-neutral.is-solid { color: var(--color-surface); background: var(--color-ink); }
21
+ .vmeta-button.is-outline { color: var(--color-ink); border-color: var(--color-border); background: var(--color-surface); }
22
+ .vmeta-button.is-outline.is-primary, .vmeta-button.is-ghost.is-primary { color: var(--color-primary); }
23
+ .vmeta-button.is-ghost { color: var(--color-ink); background: transparent; }
24
+ .vmeta-button:hover:not(:disabled) { filter: brightness(.96); }
25
+ .vmeta-button__spinner, .vmeta-system-state__spinner { width: 16px; height: 16px; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: vmeta-spin .7s linear infinite; }
26
+ .vmeta-card { min-width: 0; border: 1px solid var(--color-border); border-radius: var(--radius-lg); background: var(--color-surface); box-shadow: var(--shadow-sm); }
27
+ .vmeta-page-header { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 18px 20px; border: 1px solid var(--color-border); border-radius: var(--radius-lg); background: color-mix(in srgb, var(--color-surface) 94%, transparent); box-shadow: var(--shadow-sm); }
28
+ .vmeta-page-header__copy { min-width: 0; }
29
+ .vmeta-page-header__breadcrumb { margin-bottom: 8px; color: var(--color-muted); font-size: 12px; }
30
+ .vmeta-page-header__eyebrow { margin: 0; color: var(--color-primary); font-size: 11px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; }
31
+ .vmeta-page-header h1 { margin: 4px 0 0; color: var(--color-ink); font-size: clamp(21px, 3vw, 28px); line-height: 1.2; letter-spacing: -.035em; }
32
+ .vmeta-page-header__description { max-width: 720px; margin-top: 6px; color: var(--color-muted); font-size: 13px; line-height: 1.55; }
33
+ .vmeta-page-header__actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; }
34
+ .vmeta-system-state { min-height: 220px; display: grid; place-items: center; align-content: center; gap: 8px; padding: 28px; border: 1px dashed var(--color-border-strong); border-radius: var(--radius-lg); color: var(--color-muted); text-align: center; background: var(--color-surface); }
35
+ .vmeta-system-state__icon { width: 48px; height: 48px; display: grid; place-items: center; border-radius: 15px; color: var(--color-primary); background: var(--color-primary-soft); font-weight: 800; }
36
+ .vmeta-system-state h2 { margin: 6px 0 0; color: var(--color-ink); font-size: 17px; }
37
+ .vmeta-system-state p { max-width: 520px; margin: 0; font-size: 13px; line-height: 1.6; }
38
+ .vmeta-system-state__action { margin-top: 8px; }
39
+ .vmeta-form-page { display: grid; gap: 16px; }
40
+ .vmeta-form-page__body { display: grid; gap: 16px; }
41
+ .vmeta-form-section { padding: 20px; border: 1px solid var(--color-border); border-radius: var(--radius-lg); background: var(--color-surface); }
42
+ .vmeta-form-section > header { margin-bottom: 16px; }
43
+ .vmeta-form-section h2 { margin: 0; color: var(--color-ink); font-size: 17px; }
44
+ .vmeta-form-section header p { margin: 5px 0 0; color: var(--color-muted); font-size: 13px; }
45
+ .vmeta-form-grid { display: grid; grid-template-columns: repeat(var(--vmeta-form-columns, 2), minmax(0, 1fr)); gap: 16px; }
46
+ .vmeta-form-grid[data-columns='1'] { --vmeta-form-columns: 1; }
47
+ .vmeta-form-grid[data-columns='2'] { --vmeta-form-columns: 2; }
48
+ .vmeta-form-grid[data-columns='3'] { --vmeta-form-columns: 3; }
49
+ .vmeta-form-item { min-width: 0; display: grid; align-content: start; gap: 7px; }
50
+ .vmeta-form-item.is-full-width { grid-column: 1 / -1; }
51
+ .vmeta-form-item label { color: var(--color-ink); font-size: 13px; font-weight: 600; }
52
+ .vmeta-form-item label span, .vmeta-form-item.has-error small { color: var(--color-error); }
53
+ .vmeta-form-item small { color: var(--color-muted); font-size: 12px; }
54
+ .vmeta-control { width: 100%; min-height: var(--control-height, 40px); padding: 0 12px; border: 1px solid var(--color-border); border-radius: var(--radius-md); color: var(--color-ink); background: var(--color-surface); font: 14px var(--font-sans, sans-serif); outline: none; transition: border-color var(--motion-fast), box-shadow var(--motion-fast); }
55
+ .vmeta-control:hover { border-color: var(--color-primary-light); }
56
+ .vmeta-control:focus { border-color: var(--color-primary); box-shadow: var(--shadow-focus); }
57
+ .vmeta-textarea { min-height: 112px; padding-block: 10px; resize: vertical; }
58
+ .vmeta-form-actions { position: sticky; bottom: 12px; z-index: 5; display: flex; justify-content: flex-end; gap: 8px; padding: 12px; border: 1px solid var(--color-border); border-radius: var(--radius-lg); background: color-mix(in srgb, var(--color-surface) 92%, transparent); box-shadow: var(--shadow-md); backdrop-filter: blur(14px); }
59
+ .vmeta-data-table { max-width: 100%; overflow-x: auto; border: 1px solid var(--color-border); border-radius: var(--radius-lg); background: var(--color-surface); }
60
+ .vmeta-data-table table { width: 100%; min-width: 680px; border-collapse: collapse; }
61
+ .vmeta-data-table th, .vmeta-data-table td { padding: 12px 14px; border-bottom: 1px solid var(--color-border); text-align: left; }
62
+ .vmeta-data-table th { color: var(--color-ink); background: var(--color-primary-soft); font-size: 12px; font-weight: 700; }
63
+ .vmeta-data-table td { color: var(--color-muted); font-size: 13px; }
64
+ .vmeta-data-table tbody tr:hover { background: var(--color-muted-surface); }
65
+ @keyframes vmeta-spin { to { transform: rotate(360deg); } }
66
+ @media (max-width: 767px) { .vmeta-page-header { align-items: flex-start; flex-direction: column; padding: 16px; } .vmeta-page-header__actions { width: 100%; justify-content: flex-start; } .vmeta-form-grid[data-columns] { --vmeta-form-columns: 1; } .vmeta-form-section { padding: 16px; } }
67
+ @media (prefers-reduced-motion: reduce) { .vmeta-button__spinner, .vmeta-system-state__spinner { animation-duration: 1.4s; } }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@leduchai/vmeta-ui-core",
3
+ "version": "0.1.3",
4
+ "description": "React UI primitives khong phu thuoc nghiep vu cua VMETA.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": ["./dist/styles.css"],
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.cjs"
16
+ },
17
+ "./styles.css": "./dist/styles.css"
18
+ },
19
+ "files": ["dist", "README.md"],
20
+ "scripts": {
21
+ "build": "tsup src/index.tsx --format esm,cjs --dts --clean --external react --external react-dom && node ../../scripts/copy-file.mjs src/styles.css dist/styles.css"
22
+ },
23
+ "peerDependencies": {
24
+ "react": ">=18 <20",
25
+ "react-dom": ">=18 <20"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ }
30
+ }