@xfilecom/front-core 0.2.24 → 0.2.25
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 +29 -0
- package/dist/base.css +60 -0
- package/dist/components/atoms/Checkbox.d.ts +17 -0
- package/dist/components/atoms/Checkbox.js +17 -0
- package/dist/components/atoms/Field.d.ts +19 -0
- package/dist/components/atoms/Field.js +29 -0
- package/dist/components/atoms/Select.d.ts +13 -0
- package/dist/components/atoms/Select.js +27 -0
- package/dist/components/atoms/Textarea.d.ts +13 -0
- package/dist/components/atoms/Textarea.js +27 -0
- package/dist/components/atoms/Toast.d.ts +4 -1
- package/dist/components/atoms/Toast.js +9 -4
- package/dist/components/atoms/index.d.ts +4 -0
- package/dist/components/atoms/index.js +9 -1
- package/dist/components/overlays/BottomSheet.js +1 -0
- package/dist/components/overlays/ConfirmDialog.js +1 -1
- package/dist/components/overlays/Dialog.js +1 -0
- package/dist/components/overlays/index.d.ts +1 -0
- package/dist/components/overlays/index.js +5 -1
- package/dist/components/overlays/overlayHooks.d.ts +6 -0
- package/dist/components/overlays/overlayHooks.js +50 -0
- package/dist/generatedVersion.d.ts +2 -0
- package/dist/generatedVersion.js +5 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +15 -2
- package/dist/tokens.css +47 -0
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# @xfilecom/front-core
|
|
2
|
+
|
|
3
|
+
Design tokens (`tokens.css`), atomic layout/CSS (`base.css`), and browser-only React atoms + overlays. **Nest / 서버 의존 없음.**
|
|
4
|
+
|
|
5
|
+
## CSS import order
|
|
6
|
+
|
|
7
|
+
1. `@xfilecom/front-core/tokens.css`
|
|
8
|
+
2. `@xfilecom/front-core/base.css`
|
|
9
|
+
3. (optional) 앱/공유 테마 — 예: `xfc-theme.css`에서 `--xfc-*` 덮어쓰기
|
|
10
|
+
|
|
11
|
+
**Dark mode:** `tokens.css` 끝에 `html.dark` / `data-theme="dark"` 프리셋이 있다. 루트에 클래스 또는 속성만 켜면 동일 변수명으로 전환된다.
|
|
12
|
+
|
|
13
|
+
## React
|
|
14
|
+
|
|
15
|
+
- 루트 export 또는 `@xfilecom/front-core/atoms`, `@xfilecom/front-core/overlays`.
|
|
16
|
+
- **기본 UI 문자열은 영어** (토스트 닫기 `Dismiss`, ConfirmDialog `OK` / `Cancel`). 한국어 등은 props로 넘긴다.
|
|
17
|
+
|
|
18
|
+
## Accessibility
|
|
19
|
+
|
|
20
|
+
- **Dialog / BottomSheet / ConfirmDialog:** body 스크롤 잠금, Escape, 초기 포커스·복귀, **Tab 포커스 트랩** (`useFocusTrap`).
|
|
21
|
+
- **Toast:** `error`는 `role="alert"`, 그 외 `role="status"`. `ToastList`는 `ToastEntry.dismissible`로 행마다 닫기 버튼 on/off.
|
|
22
|
+
|
|
23
|
+
## Version
|
|
24
|
+
|
|
25
|
+
`XFRAME_FRONT_CORE_VERSION`은 `npm run build` 시 `package.json`의 `version`으로 `src/generatedVersion.ts`가 갱신된다.
|
|
26
|
+
|
|
27
|
+
## Form atoms
|
|
28
|
+
|
|
29
|
+
`Input`, `Textarea`, `Select`, `Checkbox`, `Field` — `Field`는 단일 자식 컨트롤에 `aria-describedby` / `aria-invalid` / `id`를 병합한다. 라벨 `htmlFor`와 컨트롤 `id`를 맞출 것.
|
package/dist/base.css
CHANGED
|
@@ -345,6 +345,66 @@ a:hover {
|
|
|
345
345
|
color: var(--xfc-fg-muted);
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
+
.xfc-textarea {
|
|
349
|
+
min-height: 96px;
|
|
350
|
+
padding-top: var(--xfc-space-md);
|
|
351
|
+
padding-bottom: var(--xfc-space-md);
|
|
352
|
+
resize: vertical;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.xfc-select {
|
|
356
|
+
appearance: auto;
|
|
357
|
+
cursor: pointer;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.xfc-checkbox-row {
|
|
361
|
+
display: inline-flex;
|
|
362
|
+
align-items: flex-start;
|
|
363
|
+
gap: var(--xfc-space-sm);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.xfc-checkbox {
|
|
367
|
+
width: 1.125rem;
|
|
368
|
+
height: 1.125rem;
|
|
369
|
+
margin-top: 2px;
|
|
370
|
+
flex-shrink: 0;
|
|
371
|
+
accent-color: var(--xfc-accent);
|
|
372
|
+
cursor: pointer;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.xfc-checkbox:disabled {
|
|
376
|
+
cursor: not-allowed;
|
|
377
|
+
opacity: 0.6;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.xfc-checkbox-label {
|
|
381
|
+
font-size: var(--xfc-text-body);
|
|
382
|
+
line-height: var(--xfc-leading-normal);
|
|
383
|
+
color: var(--xfc-fg-label);
|
|
384
|
+
cursor: pointer;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.xfc-field {
|
|
388
|
+
display: flex;
|
|
389
|
+
flex-direction: column;
|
|
390
|
+
gap: var(--xfc-space-xs);
|
|
391
|
+
align-items: stretch;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.xfc-field__label {
|
|
395
|
+
font-size: var(--xfc-text-small);
|
|
396
|
+
font-weight: 600;
|
|
397
|
+
color: var(--xfc-fg-label);
|
|
398
|
+
line-height: var(--xfc-leading-normal);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.xfc-field__error {
|
|
402
|
+
margin: 0;
|
|
403
|
+
font-size: var(--xfc-text-small);
|
|
404
|
+
line-height: var(--xfc-leading-normal);
|
|
405
|
+
color: var(--xfc-danger);
|
|
406
|
+
}
|
|
407
|
+
|
|
348
408
|
/* —— Badges —— */
|
|
349
409
|
|
|
350
410
|
.xfc-badge {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type InputHTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
export type CheckboxProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> & {
|
|
3
|
+
/** false 이면 disabled */
|
|
4
|
+
enabled?: boolean;
|
|
5
|
+
/** 라벨 텍스트(오른쪽). 있으면 `htmlFor` 연결 */
|
|
6
|
+
label?: ReactNode;
|
|
7
|
+
/** 행 래퍼 class */
|
|
8
|
+
rowClassName?: string;
|
|
9
|
+
};
|
|
10
|
+
export declare const Checkbox: import("react").ForwardRefExoticComponent<Omit<InputHTMLAttributes<HTMLInputElement>, "type"> & {
|
|
11
|
+
/** false 이면 disabled */
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
/** 라벨 텍스트(오른쪽). 있으면 `htmlFor` 연결 */
|
|
14
|
+
label?: ReactNode;
|
|
15
|
+
/** 행 래퍼 class */
|
|
16
|
+
rowClassName?: string;
|
|
17
|
+
} & import("react").RefAttributes<HTMLInputElement>>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Checkbox = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
exports.Checkbox = (0, react_1.forwardRef)(function Checkbox({ className = '', enabled = true, label, rowClassName = '', disabled, id: idProp, ...rest }, ref) {
|
|
7
|
+
const genId = (0, react_1.useId)();
|
|
8
|
+
const inputId = idProp ?? genId;
|
|
9
|
+
const isDisabled = disabled === true || !enabled;
|
|
10
|
+
const rowCls = ['xfc-checkbox-row', rowClassName].filter(Boolean).join(' ');
|
|
11
|
+
const inputCls = ['xfc-checkbox', className].filter(Boolean).join(' ');
|
|
12
|
+
const input = ((0, jsx_runtime_1.jsx)("input", { ref: ref, type: "checkbox", id: inputId, className: inputCls, disabled: isDisabled, ...rest }));
|
|
13
|
+
if (label == null) {
|
|
14
|
+
return input;
|
|
15
|
+
}
|
|
16
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: rowCls, children: [input, (0, jsx_runtime_1.jsx)("label", { htmlFor: inputId, className: "xfc-checkbox-label", children: label })] }));
|
|
17
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export type FieldProps = {
|
|
3
|
+
/** `<label htmlFor>` — 자식 컨트롤의 `id`와 같아야 한다 */
|
|
4
|
+
htmlFor: string;
|
|
5
|
+
label: ReactNode;
|
|
6
|
+
/** 컨트롤과 라벨 사이에 삽입되는 단일 폼 컨트롤 (`Input`, `Select`, …) */
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
hint?: ReactNode;
|
|
9
|
+
error?: ReactNode;
|
|
10
|
+
/** true면 자식에 `aria-invalid` 전달 */
|
|
11
|
+
invalid?: boolean;
|
|
12
|
+
className?: string;
|
|
13
|
+
labelClassName?: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* 라벨 + 힌트 + 에러 문구 + 단일 컨트롤.
|
|
17
|
+
* 자식 하나에 `aria-describedby`·`aria-invalid`·`id`를 병합한다(단일 ReactElement일 때만).
|
|
18
|
+
*/
|
|
19
|
+
export declare function Field({ htmlFor, label, children, hint, error, invalid, className, labelClassName, }: FieldProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Field = Field;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
/**
|
|
7
|
+
* 라벨 + 힌트 + 에러 문구 + 단일 컨트롤.
|
|
8
|
+
* 자식 하나에 `aria-describedby`·`aria-invalid`·`id`를 병합한다(단일 ReactElement일 때만).
|
|
9
|
+
*/
|
|
10
|
+
function Field({ htmlFor, label, children, hint, error, invalid = false, className = '', labelClassName = '', }) {
|
|
11
|
+
const hintId = `${htmlFor}-field-hint`;
|
|
12
|
+
const errId = `${htmlFor}-field-error`;
|
|
13
|
+
const hasHint = Boolean(hint);
|
|
14
|
+
const hasErr = Boolean(error);
|
|
15
|
+
const descIds = [hasHint ? hintId : null, hasErr ? errId : null].filter(Boolean);
|
|
16
|
+
let control = children;
|
|
17
|
+
if ((0, react_1.isValidElement)(children)) {
|
|
18
|
+
const child = react_1.Children.only(children);
|
|
19
|
+
const prevDesc = child.props['aria-describedby'] ?? '';
|
|
20
|
+
const mergedDesc = [...(prevDesc ? [prevDesc] : []), ...descIds].join(' ').trim() || undefined;
|
|
21
|
+
const childInvalid = child.props['aria-invalid'];
|
|
22
|
+
control = (0, react_1.cloneElement)(child, {
|
|
23
|
+
id: child.props.id ?? htmlFor,
|
|
24
|
+
'aria-describedby': mergedDesc,
|
|
25
|
+
'aria-invalid': invalid || hasErr || childInvalid || undefined,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: ['xfc-field', className].filter(Boolean).join(' '), children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: htmlFor, className: ['xfc-field__label', labelClassName].filter(Boolean).join(' '), children: label }), control, hasHint ? ((0, jsx_runtime_1.jsx)("p", { id: hintId, className: "xfc-input-description", children: hint })) : null, hasErr ? ((0, jsx_runtime_1.jsx)("p", { id: errId, className: "xfc-field__error", role: "alert", children: error })) : null] }));
|
|
29
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ReactNode, type SelectHTMLAttributes } from 'react';
|
|
2
|
+
export type SelectProps = SelectHTMLAttributes<HTMLSelectElement> & {
|
|
3
|
+
enabled?: boolean;
|
|
4
|
+
invalid?: boolean;
|
|
5
|
+
description?: ReactNode;
|
|
6
|
+
wrapperClassName?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const Select: import("react").ForwardRefExoticComponent<SelectHTMLAttributes<HTMLSelectElement> & {
|
|
9
|
+
enabled?: boolean;
|
|
10
|
+
invalid?: boolean;
|
|
11
|
+
description?: ReactNode;
|
|
12
|
+
wrapperClassName?: string;
|
|
13
|
+
} & import("react").RefAttributes<HTMLSelectElement>>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Select = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
exports.Select = (0, react_1.forwardRef)(function Select({ className = '', enabled = true, invalid = false, description, wrapperClassName = '', disabled, id: idProp, 'aria-describedby': ariaDescribedByProp, 'aria-invalid': ariaInvalidProp, children, ...rest }, ref) {
|
|
7
|
+
const genId = (0, react_1.useId)();
|
|
8
|
+
const selectId = idProp ?? genId;
|
|
9
|
+
const descId = `${selectId}-desc`;
|
|
10
|
+
const hasDesc = Boolean(description);
|
|
11
|
+
const cls = [
|
|
12
|
+
'xfc-input',
|
|
13
|
+
'xfc-select',
|
|
14
|
+
invalid ? 'xfc-input--invalid' : '',
|
|
15
|
+
className,
|
|
16
|
+
]
|
|
17
|
+
.filter(Boolean)
|
|
18
|
+
.join(' ');
|
|
19
|
+
const isDisabled = disabled === true || !enabled;
|
|
20
|
+
const ariaInvalid = ariaInvalidProp ?? invalid;
|
|
21
|
+
const ariaDescribedBy = [ariaDescribedByProp, hasDesc ? descId : null].filter(Boolean).join(' ') || undefined;
|
|
22
|
+
const el = ((0, jsx_runtime_1.jsx)("select", { ref: ref, id: selectId, className: cls, disabled: isDisabled, "aria-invalid": ariaInvalid || undefined, "aria-describedby": ariaDescribedBy, ...rest, children: children }));
|
|
23
|
+
if (!hasDesc) {
|
|
24
|
+
return el;
|
|
25
|
+
}
|
|
26
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: wrapperClassName || undefined, children: [el, (0, jsx_runtime_1.jsx)("p", { id: descId, className: "xfc-input-description", children: description })] }));
|
|
27
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ReactNode, type TextareaHTMLAttributes } from 'react';
|
|
2
|
+
export type TextareaProps = TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
|
3
|
+
enabled?: boolean;
|
|
4
|
+
invalid?: boolean;
|
|
5
|
+
description?: ReactNode;
|
|
6
|
+
wrapperClassName?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const Textarea: import("react").ForwardRefExoticComponent<TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
|
9
|
+
enabled?: boolean;
|
|
10
|
+
invalid?: boolean;
|
|
11
|
+
description?: ReactNode;
|
|
12
|
+
wrapperClassName?: string;
|
|
13
|
+
} & import("react").RefAttributes<HTMLTextAreaElement>>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Textarea = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
exports.Textarea = (0, react_1.forwardRef)(function Textarea({ className = '', enabled = true, invalid = false, description, wrapperClassName = '', disabled, id: idProp, 'aria-describedby': ariaDescribedByProp, 'aria-invalid': ariaInvalidProp, rows = 4, ...rest }, ref) {
|
|
7
|
+
const genId = (0, react_1.useId)();
|
|
8
|
+
const inputId = idProp ?? genId;
|
|
9
|
+
const descId = `${inputId}-desc`;
|
|
10
|
+
const hasDesc = Boolean(description);
|
|
11
|
+
const cls = [
|
|
12
|
+
'xfc-input',
|
|
13
|
+
'xfc-textarea',
|
|
14
|
+
invalid ? 'xfc-input--invalid' : '',
|
|
15
|
+
className,
|
|
16
|
+
]
|
|
17
|
+
.filter(Boolean)
|
|
18
|
+
.join(' ');
|
|
19
|
+
const isDisabled = disabled === true || !enabled;
|
|
20
|
+
const ariaInvalid = ariaInvalidProp ?? invalid;
|
|
21
|
+
const ariaDescribedBy = [ariaDescribedByProp, hasDesc ? descId : null].filter(Boolean).join(' ') || undefined;
|
|
22
|
+
const el = ((0, jsx_runtime_1.jsx)("textarea", { ref: ref, id: inputId, className: cls, disabled: isDisabled, rows: rows, "aria-invalid": ariaInvalid || undefined, "aria-describedby": ariaDescribedBy, ...rest }));
|
|
23
|
+
if (!hasDesc) {
|
|
24
|
+
return el;
|
|
25
|
+
}
|
|
26
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: wrapperClassName || undefined, children: [el, (0, jsx_runtime_1.jsx)("p", { id: descId, className: "xfc-input-description", children: description })] }));
|
|
27
|
+
});
|
|
@@ -22,13 +22,16 @@ export type ToastEntry = {
|
|
|
22
22
|
severity: ToastSeverity;
|
|
23
23
|
message: ReactNode;
|
|
24
24
|
icon?: ReactNode | null;
|
|
25
|
+
/** 행별 닫기 버튼. 생략 시 `ToastList`의 `dismissible`(기본 true)을 따름. */
|
|
26
|
+
dismissible?: boolean;
|
|
25
27
|
};
|
|
26
28
|
export type ToastListProps = {
|
|
27
29
|
toasts: ToastEntry[];
|
|
28
30
|
onDismiss: (id: string) => void;
|
|
29
31
|
className?: string;
|
|
30
32
|
dismissAriaLabel?: string;
|
|
33
|
+
/** 행에 `dismissible`이 없을 때 쓰는 기본값. 생략 시 각 토스트는 `dismissible` 기본 true. */
|
|
31
34
|
dismissible?: boolean;
|
|
32
35
|
};
|
|
33
36
|
/** 하단 고정 토스트 스택 (business-promotion `ToastList` UI, 상태는 부모에서 주입). */
|
|
34
|
-
export declare function ToastList({ toasts, onDismiss, className, dismissAriaLabel, dismissible, }: ToastListProps): import("react/jsx-runtime").JSX.Element | null;
|
|
37
|
+
export declare function ToastList({ toasts, onDismiss, className, dismissAriaLabel, dismissible: listDismissible, }: ToastListProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -31,16 +31,21 @@ function ToastCloseIcon() {
|
|
|
31
31
|
return ((0, jsx_runtime_1.jsx)("svg", { width: 20, height: 20, viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": true, children: (0, jsx_runtime_1.jsx)("path", { d: "M5 5l10 10M15 5L5 15", stroke: "currentColor", strokeWidth: "1.33", strokeLinecap: "round" }) }));
|
|
32
32
|
}
|
|
33
33
|
/** 단일 토스트 행 (목록·커스텀 레이아웃용). */
|
|
34
|
-
function Toast({ severity, message, onDismiss, className = '', icon, dismissAriaLabel = '
|
|
34
|
+
function Toast({ severity, message, onDismiss, className = '', icon, dismissAriaLabel = 'Dismiss', dismissible = true, }) {
|
|
35
35
|
const root = ['xfc-toast', `xfc-toast--${severity}`, className].filter(Boolean).join(' ');
|
|
36
36
|
const showDismiss = dismissible && onDismiss;
|
|
37
37
|
const iconContent = icon === null ? null : icon !== undefined ? (icon) : ((0, jsx_runtime_1.jsx)(ToastSeverityIcon, { severity: severity }));
|
|
38
|
-
|
|
38
|
+
/** Errors use assertive alert; other severities use status (less disruptive for SR). */
|
|
39
|
+
const liveRole = severity === 'error' ? 'alert' : 'status';
|
|
40
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: root, role: liveRole, children: [iconContent != null ? (0, jsx_runtime_1.jsx)("span", { className: "xfc-toast__icon", children: iconContent }) : null, (0, jsx_runtime_1.jsx)("div", { className: "xfc-toast__message", children: message }), showDismiss ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "xfc-toast-dismiss", onClick: onDismiss, "aria-label": dismissAriaLabel, children: (0, jsx_runtime_1.jsx)(ToastCloseIcon, {}) })) : null] }));
|
|
39
41
|
}
|
|
40
42
|
/** 하단 고정 토스트 스택 (business-promotion `ToastList` UI, 상태는 부모에서 주입). */
|
|
41
|
-
function ToastList({ toasts, onDismiss, className = '', dismissAriaLabel, dismissible, }) {
|
|
43
|
+
function ToastList({ toasts, onDismiss, className = '', dismissAriaLabel, dismissible: listDismissible, }) {
|
|
42
44
|
if (!toasts.length)
|
|
43
45
|
return null;
|
|
44
46
|
const listCls = ['xfc-toast-list', className].filter(Boolean).join(' ');
|
|
45
|
-
return ((0, jsx_runtime_1.jsx)("div", { className: listCls, "aria-live": "polite", children: toasts.map((t) =>
|
|
47
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: listCls, "aria-live": "polite", "aria-relevant": "additions text", children: toasts.map((t) => {
|
|
48
|
+
const rowDismissible = t.dismissible ?? listDismissible ?? true;
|
|
49
|
+
return ((0, jsx_runtime_1.jsx)(Toast, { severity: t.severity, message: t.message, icon: t.icon, dismissAriaLabel: dismissAriaLabel, dismissible: rowDismissible, onDismiss: () => onDismiss(t.id) }, t.id));
|
|
50
|
+
}) }));
|
|
46
51
|
}
|
|
@@ -6,7 +6,11 @@ export { Badge, type BadgeProps } from './Badge';
|
|
|
6
6
|
export { Box, type BoxProps } from './Box';
|
|
7
7
|
export { Button, type ButtonProps } from './Button';
|
|
8
8
|
export { Card, type CardProps } from './Card';
|
|
9
|
+
export { Checkbox, type CheckboxProps } from './Checkbox';
|
|
10
|
+
export { Field, type FieldProps } from './Field';
|
|
9
11
|
export { Input, type InputProps } from './Input';
|
|
12
|
+
export { Select, type SelectProps } from './Select';
|
|
13
|
+
export { Textarea, type TextareaProps } from './Textarea';
|
|
10
14
|
export { InlineErrorList, type InlineErrorEntry, type InlineErrorListProps, } from './InlineErrorList';
|
|
11
15
|
export { LoadingOverlay, type LoadingOverlayProps } from './LoadingOverlay';
|
|
12
16
|
export { Stack, type StackProps } from './Stack';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Text = exports.ToastSeverityIcon = exports.ToastList = exports.Toast = exports.Stack = exports.LoadingOverlay = exports.InlineErrorList = exports.Input = exports.Card = exports.Button = exports.Box = exports.Badge = void 0;
|
|
3
|
+
exports.Text = exports.ToastSeverityIcon = exports.ToastList = exports.Toast = exports.Stack = exports.LoadingOverlay = exports.InlineErrorList = exports.Textarea = exports.Select = exports.Input = exports.Field = exports.Checkbox = exports.Card = exports.Button = exports.Box = exports.Badge = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Atomic UI — design tokens + base.css 의 `.xfc-*` 와 1:1.
|
|
6
6
|
* 패키지 루트는 `components/index.ts` → 여기서 동일 심볼을 재export.
|
|
@@ -13,8 +13,16 @@ var Button_1 = require("./Button");
|
|
|
13
13
|
Object.defineProperty(exports, "Button", { enumerable: true, get: function () { return Button_1.Button; } });
|
|
14
14
|
var Card_1 = require("./Card");
|
|
15
15
|
Object.defineProperty(exports, "Card", { enumerable: true, get: function () { return Card_1.Card; } });
|
|
16
|
+
var Checkbox_1 = require("./Checkbox");
|
|
17
|
+
Object.defineProperty(exports, "Checkbox", { enumerable: true, get: function () { return Checkbox_1.Checkbox; } });
|
|
18
|
+
var Field_1 = require("./Field");
|
|
19
|
+
Object.defineProperty(exports, "Field", { enumerable: true, get: function () { return Field_1.Field; } });
|
|
16
20
|
var Input_1 = require("./Input");
|
|
17
21
|
Object.defineProperty(exports, "Input", { enumerable: true, get: function () { return Input_1.Input; } });
|
|
22
|
+
var Select_1 = require("./Select");
|
|
23
|
+
Object.defineProperty(exports, "Select", { enumerable: true, get: function () { return Select_1.Select; } });
|
|
24
|
+
var Textarea_1 = require("./Textarea");
|
|
25
|
+
Object.defineProperty(exports, "Textarea", { enumerable: true, get: function () { return Textarea_1.Textarea; } });
|
|
18
26
|
var InlineErrorList_1 = require("./InlineErrorList");
|
|
19
27
|
Object.defineProperty(exports, "InlineErrorList", { enumerable: true, get: function () { return InlineErrorList_1.InlineErrorList; } });
|
|
20
28
|
var LoadingOverlay_1 = require("./LoadingOverlay");
|
|
@@ -14,6 +14,7 @@ function BottomSheet({ open, onOpenChange, children, title, subtitle, headerExtr
|
|
|
14
14
|
}, [onOpenChange]);
|
|
15
15
|
(0, overlayHooks_1.useBodyScrollLock)(open);
|
|
16
16
|
(0, overlayHooks_1.useEscapeKey)(open && closeOnEscape, requestClose);
|
|
17
|
+
(0, overlayHooks_1.useFocusTrap)(panelRef, open);
|
|
17
18
|
(0, react_1.useEffect)(() => {
|
|
18
19
|
if (!open || typeof document === 'undefined')
|
|
19
20
|
return;
|
|
@@ -4,7 +4,7 @@ exports.ConfirmDialog = ConfirmDialog;
|
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
const Button_1 = require("../atoms/Button");
|
|
6
6
|
const Dialog_1 = require("./Dialog");
|
|
7
|
-
function ConfirmDialog({ open, onOpenChange, onConfirm, onCancel, title, message, confirmLabel = '
|
|
7
|
+
function ConfirmDialog({ open, onOpenChange, onConfirm, onCancel, title, message, confirmLabel = 'OK', cancelLabel = 'Cancel', destructive = false, confirmLoading = false, closeOnBackdropClick = true, panelClassName = '', confirmButtonProps, cancelButtonProps, }) {
|
|
8
8
|
const { className: confirmClassName, onClick: confirmOnClick, disabled: confirmDisabled, ...confirmRest } = confirmButtonProps ?? {};
|
|
9
9
|
const { className: cancelClassName, onClick: cancelOnClick, disabled: cancelDisabled, ...cancelRest } = cancelButtonProps ?? {};
|
|
10
10
|
const handleCancel = (e) => {
|
|
@@ -15,6 +15,7 @@ function Dialog({ open, onOpenChange, children, title, titleAdornment, headerExt
|
|
|
15
15
|
}, [onOpenChange]);
|
|
16
16
|
(0, overlayHooks_1.useBodyScrollLock)(open);
|
|
17
17
|
(0, overlayHooks_1.useEscapeKey)(open && closeOnEscape, requestClose);
|
|
18
|
+
(0, overlayHooks_1.useFocusTrap)(panelRef, open);
|
|
18
19
|
(0, react_1.useEffect)(() => {
|
|
19
20
|
if (!open || typeof document === 'undefined')
|
|
20
21
|
return;
|
|
@@ -5,3 +5,4 @@
|
|
|
5
5
|
export { BottomSheet, type BottomSheetProps } from './BottomSheet';
|
|
6
6
|
export { ConfirmDialog, type ConfirmDialogProps } from './ConfirmDialog';
|
|
7
7
|
export { Dialog, type DialogProps } from './Dialog';
|
|
8
|
+
export { useBodyScrollLock, useEscapeKey, useFocusTrap } from './overlayHooks';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Dialog = exports.ConfirmDialog = exports.BottomSheet = void 0;
|
|
3
|
+
exports.useFocusTrap = exports.useEscapeKey = exports.useBodyScrollLock = exports.Dialog = exports.ConfirmDialog = exports.BottomSheet = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* 오버레이·다이얼로그 — atoms 위 레벨. `base.css` 의 `.xfc-dialog-*` / `.xfc-bottom-sheet-*` 와 짝.
|
|
6
6
|
* 패키지 루트 또는 `@xfilecom/front-core/overlays`.
|
|
@@ -11,3 +11,7 @@ var ConfirmDialog_1 = require("./ConfirmDialog");
|
|
|
11
11
|
Object.defineProperty(exports, "ConfirmDialog", { enumerable: true, get: function () { return ConfirmDialog_1.ConfirmDialog; } });
|
|
12
12
|
var Dialog_1 = require("./Dialog");
|
|
13
13
|
Object.defineProperty(exports, "Dialog", { enumerable: true, get: function () { return Dialog_1.Dialog; } });
|
|
14
|
+
var overlayHooks_1 = require("./overlayHooks");
|
|
15
|
+
Object.defineProperty(exports, "useBodyScrollLock", { enumerable: true, get: function () { return overlayHooks_1.useBodyScrollLock; } });
|
|
16
|
+
Object.defineProperty(exports, "useEscapeKey", { enumerable: true, get: function () { return overlayHooks_1.useEscapeKey; } });
|
|
17
|
+
Object.defineProperty(exports, "useFocusTrap", { enumerable: true, get: function () { return overlayHooks_1.useFocusTrap; } });
|
|
@@ -1,2 +1,8 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* 열린 모달·시트 안에서 Tab / Shift+Tab 이 첫/끝 포커스 가능 요소에서 순환하도록 한다.
|
|
4
|
+
* 패널 루트(`tabIndex={-1}`)가 포커스를 받은 직후 Tab 도 첫 요소로 보낸다.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useFocusTrap(containerRef: RefObject<HTMLElement | null>, active: boolean): void;
|
|
1
7
|
export declare function useBodyScrollLock(active: boolean): void;
|
|
2
8
|
export declare function useEscapeKey(active: boolean, onEscape: () => void): void;
|
|
@@ -1,8 +1,58 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useFocusTrap = useFocusTrap;
|
|
3
4
|
exports.useBodyScrollLock = useBodyScrollLock;
|
|
4
5
|
exports.useEscapeKey = useEscapeKey;
|
|
5
6
|
const react_1 = require("react");
|
|
7
|
+
/** 포커스 가능한 대화형 요소 (패널 `tabIndex={-1}` 제외) */
|
|
8
|
+
const FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
9
|
+
function visibleFocusables(container) {
|
|
10
|
+
return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter((el) => {
|
|
11
|
+
if (el.getAttribute('aria-hidden') === 'true')
|
|
12
|
+
return false;
|
|
13
|
+
const style = typeof window !== 'undefined' ? window.getComputedStyle(el) : null;
|
|
14
|
+
if (style && (style.visibility === 'hidden' || style.display === 'none'))
|
|
15
|
+
return false;
|
|
16
|
+
return true;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 열린 모달·시트 안에서 Tab / Shift+Tab 이 첫/끝 포커스 가능 요소에서 순환하도록 한다.
|
|
21
|
+
* 패널 루트(`tabIndex={-1}`)가 포커스를 받은 직후 Tab 도 첫 요소로 보낸다.
|
|
22
|
+
*/
|
|
23
|
+
function useFocusTrap(containerRef, active) {
|
|
24
|
+
(0, react_1.useEffect)(() => {
|
|
25
|
+
if (!active || typeof document === 'undefined')
|
|
26
|
+
return;
|
|
27
|
+
const root = containerRef.current;
|
|
28
|
+
if (!root)
|
|
29
|
+
return;
|
|
30
|
+
const onKeyDown = (e) => {
|
|
31
|
+
if (e.key !== 'Tab')
|
|
32
|
+
return;
|
|
33
|
+
const focusable = visibleFocusables(root);
|
|
34
|
+
if (focusable.length === 0)
|
|
35
|
+
return;
|
|
36
|
+
const first = focusable[0];
|
|
37
|
+
const last = focusable[focusable.length - 1];
|
|
38
|
+
const activeEl = document.activeElement;
|
|
39
|
+
if (!activeEl || !root.contains(activeEl))
|
|
40
|
+
return;
|
|
41
|
+
if (e.shiftKey) {
|
|
42
|
+
if (activeEl === first || activeEl === root) {
|
|
43
|
+
e.preventDefault();
|
|
44
|
+
last.focus();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else if (activeEl === last || activeEl === root) {
|
|
48
|
+
e.preventDefault();
|
|
49
|
+
first.focus();
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
document.addEventListener('keydown', onKeyDown, true);
|
|
53
|
+
return () => document.removeEventListener('keydown', onKeyDown, true);
|
|
54
|
+
}, [active, containerRef]);
|
|
55
|
+
}
|
|
6
56
|
function useBodyScrollLock(active) {
|
|
7
57
|
(0, react_1.useEffect)(() => {
|
|
8
58
|
if (!active || typeof document === 'undefined')
|
package/dist/index.d.ts
CHANGED
|
@@ -8,8 +8,11 @@
|
|
|
8
8
|
* import '<shared>/styles/app.css'; // 레이아웃 + 추가 오버라이드
|
|
9
9
|
*
|
|
10
10
|
* React: atoms `@xfilecom/front-core/atoms`, 오버레이 `@xfilecom/front-core/overlays` 또는 루트 export.
|
|
11
|
+
*
|
|
12
|
+
* 다크 모드: `tokens.css` — `html.dark` 또는 `data-theme="dark"`.
|
|
13
|
+
* 기본 UI 문자열(토스트 닫기, ConfirmDialog 버튼 등)은 영어; 앱에서 props로 한국어 등 덮어쓰기.
|
|
11
14
|
*/
|
|
12
|
-
export
|
|
15
|
+
export { XFRAME_FRONT_CORE_VERSION } from './generatedVersion';
|
|
13
16
|
/** CSS 커스텀 프로퍼티 이름 (-- 제외 시 var()와 함께 사용) */
|
|
14
17
|
export declare const tokenVars: {
|
|
15
18
|
readonly colorBg: "--xfc-bg";
|
|
@@ -43,4 +46,6 @@ export declare const tokenVars: {
|
|
|
43
46
|
readonly toastWarnBg: "--xfc-toast-warn-bg";
|
|
44
47
|
readonly toastErrorBg: "--xfc-toast-error-bg";
|
|
45
48
|
};
|
|
46
|
-
export { Badge, BottomSheet, Box, Button, Card, ConfirmDialog, Dialog, InlineErrorList, Input, LoadingOverlay, Stack, Text, Toast, ToastList, ToastSeverityIcon, type BadgeProps, type BottomSheetProps, type BoxProps, type ButtonProps, type CardProps, type ConfirmDialogProps, type DialogProps, type InlineErrorEntry, type InlineErrorListProps, type InputProps, type LoadingOverlayProps, type StackProps, type TextProps, type TextVariant, type ToastEntry, type ToastListProps, type ToastProps, type ToastSeverity, type ToastSeverityIconProps, } from './components';
|
|
49
|
+
export { Badge, BottomSheet, Box, Button, Card, Checkbox, ConfirmDialog, Dialog, Field, InlineErrorList, Input, LoadingOverlay, Select, Stack, Text, Textarea, Toast, ToastList, ToastSeverityIcon, type BadgeProps, type BottomSheetProps, type BoxProps, type ButtonProps, type CardProps, type CheckboxProps, type ConfirmDialogProps, type DialogProps, type FieldProps, type InlineErrorEntry, type InlineErrorListProps, type InputProps, type LoadingOverlayProps, type SelectProps, type StackProps, type TextProps, type TextVariant, type TextareaProps, type ToastEntry, type ToastListProps, type ToastProps, type ToastSeverity, type ToastSeverityIconProps, } from './components';
|
|
50
|
+
/** 커스텀 오버레이용 — `Dialog`/`BottomSheet`와 동일한 훅 */
|
|
51
|
+
export { useBodyScrollLock, useEscapeKey, useFocusTrap } from './components';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ToastSeverityIcon = exports.ToastList = exports.Toast = exports.Text = exports.Stack = exports.LoadingOverlay = exports.Input = exports.InlineErrorList = exports.Dialog = exports.ConfirmDialog = exports.Card = exports.Button = exports.Box = exports.BottomSheet = exports.Badge = exports.tokenVars = exports.XFRAME_FRONT_CORE_VERSION = void 0;
|
|
3
|
+
exports.useFocusTrap = exports.useEscapeKey = exports.useBodyScrollLock = exports.ToastSeverityIcon = exports.ToastList = exports.Toast = exports.Textarea = exports.Text = exports.Stack = exports.Select = exports.LoadingOverlay = exports.Input = exports.InlineErrorList = exports.Field = exports.Dialog = exports.ConfirmDialog = exports.Checkbox = exports.Card = exports.Button = exports.Box = exports.BottomSheet = exports.Badge = exports.tokenVars = exports.XFRAME_FRONT_CORE_VERSION = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* @xfilecom/front-core — design tokens + atomic React components (browser-only).
|
|
6
6
|
*
|
|
@@ -11,8 +11,12 @@ exports.ToastSeverityIcon = exports.ToastList = exports.Toast = exports.Text = e
|
|
|
11
11
|
* import '<shared>/styles/app.css'; // 레이아웃 + 추가 오버라이드
|
|
12
12
|
*
|
|
13
13
|
* React: atoms `@xfilecom/front-core/atoms`, 오버레이 `@xfilecom/front-core/overlays` 또는 루트 export.
|
|
14
|
+
*
|
|
15
|
+
* 다크 모드: `tokens.css` — `html.dark` 또는 `data-theme="dark"`.
|
|
16
|
+
* 기본 UI 문자열(토스트 닫기, ConfirmDialog 버튼 등)은 영어; 앱에서 props로 한국어 등 덮어쓰기.
|
|
14
17
|
*/
|
|
15
|
-
|
|
18
|
+
var generatedVersion_1 = require("./generatedVersion");
|
|
19
|
+
Object.defineProperty(exports, "XFRAME_FRONT_CORE_VERSION", { enumerable: true, get: function () { return generatedVersion_1.XFRAME_FRONT_CORE_VERSION; } });
|
|
16
20
|
/** CSS 커스텀 프로퍼티 이름 (-- 제외 시 var()와 함께 사용) */
|
|
17
21
|
exports.tokenVars = {
|
|
18
22
|
colorBg: '--xfc-bg',
|
|
@@ -52,13 +56,22 @@ Object.defineProperty(exports, "BottomSheet", { enumerable: true, get: function
|
|
|
52
56
|
Object.defineProperty(exports, "Box", { enumerable: true, get: function () { return components_1.Box; } });
|
|
53
57
|
Object.defineProperty(exports, "Button", { enumerable: true, get: function () { return components_1.Button; } });
|
|
54
58
|
Object.defineProperty(exports, "Card", { enumerable: true, get: function () { return components_1.Card; } });
|
|
59
|
+
Object.defineProperty(exports, "Checkbox", { enumerable: true, get: function () { return components_1.Checkbox; } });
|
|
55
60
|
Object.defineProperty(exports, "ConfirmDialog", { enumerable: true, get: function () { return components_1.ConfirmDialog; } });
|
|
56
61
|
Object.defineProperty(exports, "Dialog", { enumerable: true, get: function () { return components_1.Dialog; } });
|
|
62
|
+
Object.defineProperty(exports, "Field", { enumerable: true, get: function () { return components_1.Field; } });
|
|
57
63
|
Object.defineProperty(exports, "InlineErrorList", { enumerable: true, get: function () { return components_1.InlineErrorList; } });
|
|
58
64
|
Object.defineProperty(exports, "Input", { enumerable: true, get: function () { return components_1.Input; } });
|
|
59
65
|
Object.defineProperty(exports, "LoadingOverlay", { enumerable: true, get: function () { return components_1.LoadingOverlay; } });
|
|
66
|
+
Object.defineProperty(exports, "Select", { enumerable: true, get: function () { return components_1.Select; } });
|
|
60
67
|
Object.defineProperty(exports, "Stack", { enumerable: true, get: function () { return components_1.Stack; } });
|
|
61
68
|
Object.defineProperty(exports, "Text", { enumerable: true, get: function () { return components_1.Text; } });
|
|
69
|
+
Object.defineProperty(exports, "Textarea", { enumerable: true, get: function () { return components_1.Textarea; } });
|
|
62
70
|
Object.defineProperty(exports, "Toast", { enumerable: true, get: function () { return components_1.Toast; } });
|
|
63
71
|
Object.defineProperty(exports, "ToastList", { enumerable: true, get: function () { return components_1.ToastList; } });
|
|
64
72
|
Object.defineProperty(exports, "ToastSeverityIcon", { enumerable: true, get: function () { return components_1.ToastSeverityIcon; } });
|
|
73
|
+
/** 커스텀 오버레이용 — `Dialog`/`BottomSheet`와 동일한 훅 */
|
|
74
|
+
var components_2 = require("./components");
|
|
75
|
+
Object.defineProperty(exports, "useBodyScrollLock", { enumerable: true, get: function () { return components_2.useBodyScrollLock; } });
|
|
76
|
+
Object.defineProperty(exports, "useEscapeKey", { enumerable: true, get: function () { return components_2.useEscapeKey; } });
|
|
77
|
+
Object.defineProperty(exports, "useFocusTrap", { enumerable: true, get: function () { return components_2.useFocusTrap; } });
|
package/dist/tokens.css
CHANGED
|
@@ -81,3 +81,50 @@
|
|
|
81
81
|
/* Focus */
|
|
82
82
|
--xfc-focus-ring: 0 0 0 3px color-mix(in srgb, var(--xfc-accent) 35%, transparent);
|
|
83
83
|
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Dark theme (opt-in): `<html class="dark">` or `data-theme="dark"` on `:root` / `html`.
|
|
87
|
+
* 동일한 변수 이름만 덮어쓴다 — 앱은 토큰 참조를 바꿀 필요 없음.
|
|
88
|
+
*/
|
|
89
|
+
html.dark,
|
|
90
|
+
:root[data-theme='dark'],
|
|
91
|
+
html[data-theme='dark'] {
|
|
92
|
+
color-scheme: dark;
|
|
93
|
+
|
|
94
|
+
--xfc-bg: #0f1419;
|
|
95
|
+
--xfc-bg-elevated: #1a222c;
|
|
96
|
+
--xfc-bg-muted: #141b22;
|
|
97
|
+
--xfc-bg-disabled: #2a3440;
|
|
98
|
+
|
|
99
|
+
--xfc-fg: #e8eef4;
|
|
100
|
+
--xfc-fg-strong: #ffffff;
|
|
101
|
+
--xfc-fg-label: #dce4ec;
|
|
102
|
+
--xfc-fg-muted: #8b9aab;
|
|
103
|
+
--xfc-fg-placeholder: #5c6b7a;
|
|
104
|
+
--xfc-fg-icon: #a8b8c8;
|
|
105
|
+
|
|
106
|
+
--xfc-border: #2a3440;
|
|
107
|
+
--xfc-border-strong: #3d4a5c;
|
|
108
|
+
--xfc-border-header: #2f3d4d;
|
|
109
|
+
|
|
110
|
+
--xfc-accent: #5b8aff;
|
|
111
|
+
--xfc-accent-hover: #7aa3ff;
|
|
112
|
+
--xfc-accent-fg: #0f1419;
|
|
113
|
+
|
|
114
|
+
--xfc-danger: #ff6b6b;
|
|
115
|
+
--xfc-success: #34d399;
|
|
116
|
+
--xfc-success-bg: #064e3b;
|
|
117
|
+
--xfc-warning: #fbbf24;
|
|
118
|
+
--xfc-warning-bg: #78350f;
|
|
119
|
+
|
|
120
|
+
--xfc-toast-info-bg: #1e293b;
|
|
121
|
+
--xfc-toast-success-bg: #047857;
|
|
122
|
+
--xfc-toast-warn-bg: #b45309;
|
|
123
|
+
--xfc-toast-error-bg: #dc2626;
|
|
124
|
+
--xfc-toast-shadow: 0 2px 12px rgba(0, 0, 0, 0.45);
|
|
125
|
+
|
|
126
|
+
--xfc-input-shadow: 0 4px 24px rgba(0, 0, 0, 0.25);
|
|
127
|
+
--xfc-card-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
|
|
128
|
+
|
|
129
|
+
--xfc-focus-ring: 0 0 0 3px color-mix(in srgb, var(--xfc-accent) 45%, transparent);
|
|
130
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xfilecom/front-core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.25",
|
|
4
4
|
"description": "Shared design tokens, base CSS, and atomic React components (browser-only; no Nest dependency)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
],
|
|
26
26
|
"scripts": {
|
|
27
27
|
"prepublishOnly": "npm run build",
|
|
28
|
-
"build": "npm run build:ts && npm run build:css",
|
|
28
|
+
"build": "node scripts/write-version.js && npm run build:ts && npm run build:css",
|
|
29
29
|
"build:ts": "tsc",
|
|
30
30
|
"build:css": "cp src/tokens.css dist/tokens.css && cp src/base.css dist/base.css"
|
|
31
31
|
},
|