@bgord/ui 0.1.2 → 0.3.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/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/use-exit-action.d.ts +16 -0
- package/dist/hooks/use-field.d.ts +127 -0
- package/dist/hooks/use-hover.d.ts +11 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +228 -38
- package/dist/services/field.d.ts +10 -0
- package/dist/services/fields.d.ts +46 -0
- package/dist/services/form.d.ts +11 -0
- package/dist/services/index.d.ts +4 -0
- package/dist/services/rhythm.d.ts +69 -0
- package/dist/src/components/button.d.ts +1 -0
- package/dist/src/components/index.d.ts +1 -0
- package/dist/src/hooks/index.d.ts +3 -0
- package/dist/src/hooks/use-field.d.ts +127 -0
- package/dist/src/hooks/use-hover.d.ts +12 -0
- package/dist/src/hooks/use-toggle.d.ts +32 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/services/field.d.ts +10 -0
- package/dist/src/services/fields.d.ts +46 -0
- package/dist/src/services/form.d.ts +11 -0
- package/dist/src/services/index.d.ts +4 -0
- package/dist/src/services/rhythm.d.ts +69 -0
- package/package.json +19 -4
package/dist/hooks/index.d.ts
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
type UseExitActionAnimationType = string;
|
|
3
|
+
type UseExitActionOptionsType = {
|
|
4
|
+
actionFn: () => void;
|
|
5
|
+
animation: UseExitActionAnimationType;
|
|
6
|
+
};
|
|
7
|
+
type UseExitActionReturnType = {
|
|
8
|
+
visible: boolean;
|
|
9
|
+
trigger: (event: React.MouseEvent) => void;
|
|
10
|
+
attach: {
|
|
11
|
+
"data-exit": UseExitActionAnimationType;
|
|
12
|
+
onAnimationEnd: (event: React.AnimationEvent) => void;
|
|
13
|
+
} | undefined;
|
|
14
|
+
};
|
|
15
|
+
export declare function useExitAction(options: UseExitActionOptionsType): UseExitActionReturnType;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { FieldValueAllowedTypes } from "../services/field";
|
|
2
|
+
/** Type for field names */
|
|
3
|
+
type NewFieldNameType = string;
|
|
4
|
+
/** Valid HTML elements that can be used as field inputs */
|
|
5
|
+
export type FieldElementType = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
|
|
6
|
+
/**
|
|
7
|
+
* Defines the strategy for field value persistence
|
|
8
|
+
* @enum {string}
|
|
9
|
+
*/
|
|
10
|
+
export declare enum useFieldStrategyEnum {
|
|
11
|
+
/** Store field value in URL parameters */
|
|
12
|
+
params = "params",
|
|
13
|
+
/** Store field value in local state */
|
|
14
|
+
local = "local"
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Configuration options for the useField hook
|
|
18
|
+
* @template T - Type of the field value
|
|
19
|
+
*/
|
|
20
|
+
export type useFieldConfigType<T extends FieldValueAllowedTypes> = {
|
|
21
|
+
/** Unique identifier for the field */
|
|
22
|
+
name: NewFieldNameType;
|
|
23
|
+
/** Initial value for the field */
|
|
24
|
+
defaultValue?: T;
|
|
25
|
+
/** Strategy for value persistence */
|
|
26
|
+
strategy?: useFieldStrategyEnum;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Return type for the useField hook
|
|
30
|
+
* @template T - Type of the field value
|
|
31
|
+
*/
|
|
32
|
+
export type useFieldReturnType<T extends FieldValueAllowedTypes> = {
|
|
33
|
+
/** Current persistence strategy */
|
|
34
|
+
strategy: useFieldStrategyEnum;
|
|
35
|
+
/** Initial field value */
|
|
36
|
+
defaultValue: T;
|
|
37
|
+
/** Current field value */
|
|
38
|
+
currentValue: T;
|
|
39
|
+
/** Non-nullable field value, empty string for empty values */
|
|
40
|
+
value: NonNullable<T>;
|
|
41
|
+
/** Function to set field value */
|
|
42
|
+
set: (value: T) => void;
|
|
43
|
+
/** Change event handler for controlled components */
|
|
44
|
+
handleChange: (event: React.ChangeEvent<FieldElementType>) => void;
|
|
45
|
+
/** Reset field to default value */
|
|
46
|
+
clear: () => void;
|
|
47
|
+
/** Props for field label */
|
|
48
|
+
label: {
|
|
49
|
+
props: {
|
|
50
|
+
htmlFor: NewFieldNameType;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
/** Props for field input */
|
|
54
|
+
input: {
|
|
55
|
+
props: {
|
|
56
|
+
id: NewFieldNameType;
|
|
57
|
+
name: NewFieldNameType;
|
|
58
|
+
value: NonNullable<T>;
|
|
59
|
+
onChange: (event: React.ChangeEvent<FieldElementType>) => void;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
/** Whether field value differs from default */
|
|
63
|
+
changed: boolean;
|
|
64
|
+
/** Whether field value equals default */
|
|
65
|
+
unchanged: boolean;
|
|
66
|
+
/** Whether field is empty */
|
|
67
|
+
empty: boolean;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Hook for managing form field state with URL parameters or local state
|
|
71
|
+
*
|
|
72
|
+
* @template T - Type of the field value
|
|
73
|
+
* @param {useFieldConfigType<T>} config - Field configuration
|
|
74
|
+
* @returns {useFieldReturnType<T>} Field state and handlers
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```tsx
|
|
78
|
+
* // Using local strategy
|
|
79
|
+
* function NameField() {
|
|
80
|
+
* const field = useField({
|
|
81
|
+
* name: "username",
|
|
82
|
+
* defaultValue: "",
|
|
83
|
+
* strategy: useFieldStrategyEnum.local
|
|
84
|
+
* });
|
|
85
|
+
*
|
|
86
|
+
* return (
|
|
87
|
+
* <div>
|
|
88
|
+
* <label {...field.label.props}>Username:</label>
|
|
89
|
+
* <input
|
|
90
|
+
* {...field.input.props}
|
|
91
|
+
* type="text"
|
|
92
|
+
* value={field.value}
|
|
93
|
+
* onChange={field.handleChange}
|
|
94
|
+
* />
|
|
95
|
+
* </div>
|
|
96
|
+
* );
|
|
97
|
+
* }
|
|
98
|
+
*
|
|
99
|
+
* // Using URL parameters strategy
|
|
100
|
+
* function SearchField() {
|
|
101
|
+
* const field = useField({
|
|
102
|
+
* name: "q",
|
|
103
|
+
* strategy: useFieldStrategyEnum.params
|
|
104
|
+
* });
|
|
105
|
+
*
|
|
106
|
+
* return (
|
|
107
|
+
* <input
|
|
108
|
+
* type="search"
|
|
109
|
+
* {...field.input.props}
|
|
110
|
+
* value={field.value}
|
|
111
|
+
* onChange={field.handleChange}
|
|
112
|
+
* />
|
|
113
|
+
* );
|
|
114
|
+
* }
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export declare function useField<T extends FieldValueAllowedTypes>(config: useFieldConfigType<T>): useFieldReturnType<T>;
|
|
118
|
+
/**
|
|
119
|
+
* Utility class for working with local fields
|
|
120
|
+
* @static
|
|
121
|
+
*/
|
|
122
|
+
export declare class LocalFields {
|
|
123
|
+
static clearAll(fields: {
|
|
124
|
+
clear: VoidFunction;
|
|
125
|
+
}[]): () => void;
|
|
126
|
+
}
|
|
127
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type UseHoverConfigType = {
|
|
2
|
+
enabled?: boolean;
|
|
3
|
+
};
|
|
4
|
+
export type UseHoverReturnType<T extends HTMLElement> = {
|
|
5
|
+
attach: {
|
|
6
|
+
ref: React.RefCallback<T | null>;
|
|
7
|
+
};
|
|
8
|
+
isHovering: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare function useHover<T extends HTMLElement = HTMLElement>({ enabled, }?: UseHoverConfigType): UseHoverReturnType<T>;
|
|
11
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export *
|
|
2
|
-
export *
|
|
1
|
+
export * from "./components";
|
|
2
|
+
export * from "./hooks";
|
|
3
|
+
export * from "./services";
|
package/dist/index.js
CHANGED
|
@@ -1,38 +1,133 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
// src/components/button.tsx
|
|
2
|
+
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
3
|
+
function Button() {
|
|
4
|
+
return /* @__PURE__ */ jsxDEV("button", {
|
|
5
|
+
type: "button",
|
|
6
|
+
children: "Click"
|
|
7
|
+
}, undefined, false, undefined, this);
|
|
8
|
+
}
|
|
9
|
+
// src/hooks/use-exit-action.ts
|
|
10
|
+
import React from "react";
|
|
11
|
+
function useExitAction(options) {
|
|
12
|
+
const [phase, setPhase] = React.useState("idle" /* idle */);
|
|
13
|
+
const trigger = (event) => {
|
|
14
|
+
event.preventDefault();
|
|
15
|
+
if (phase === "idle")
|
|
16
|
+
setPhase("exiting" /* exiting */);
|
|
17
|
+
};
|
|
18
|
+
const onAnimationEnd = (event) => {
|
|
19
|
+
if (event.animationName !== options.animation)
|
|
20
|
+
return;
|
|
21
|
+
options.actionFn();
|
|
22
|
+
setPhase("gone" /* gone */);
|
|
23
|
+
};
|
|
24
|
+
const attach = phase === "exiting" ? { "data-exit": options.animation, onAnimationEnd } : undefined;
|
|
25
|
+
return { visible: phase !== "gone", attach, trigger };
|
|
26
|
+
}
|
|
27
|
+
// src/hooks/use-field.ts
|
|
28
|
+
import { useEffect, useState } from "react";
|
|
29
|
+
import { useSearchParams } from "react-router";
|
|
30
|
+
|
|
31
|
+
// src/services/field.ts
|
|
32
|
+
class Field {
|
|
33
|
+
static emptyValue = undefined;
|
|
34
|
+
static isEmpty(value) {
|
|
35
|
+
return value === undefined || value === "" || value === null;
|
|
36
|
+
}
|
|
37
|
+
static compare(one, another) {
|
|
38
|
+
if (Field.isEmpty(one) && Field.isEmpty(another)) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
return one === another;
|
|
42
|
+
}
|
|
43
|
+
value = Field.emptyValue;
|
|
44
|
+
constructor(value) {
|
|
45
|
+
this.value = Field.isEmpty(value) ? Field.emptyValue : value;
|
|
46
|
+
}
|
|
47
|
+
get() {
|
|
48
|
+
return this.value;
|
|
49
|
+
}
|
|
50
|
+
isEmpty() {
|
|
51
|
+
return Field.isEmpty(this.value);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/hooks/use-field.ts
|
|
56
|
+
var useFieldStrategyEnum;
|
|
57
|
+
((useFieldStrategyEnum2) => {
|
|
58
|
+
useFieldStrategyEnum2["params"] = "params";
|
|
59
|
+
useFieldStrategyEnum2["local"] = "local";
|
|
60
|
+
})(useFieldStrategyEnum ||= {});
|
|
61
|
+
function useField(config) {
|
|
62
|
+
const strategy = config.strategy ?? "local" /* local */;
|
|
63
|
+
const [params, setParams] = useSearchParams();
|
|
64
|
+
const givenValue = new Field(params.get(config.name));
|
|
65
|
+
const defaultValue = new Field(config.defaultValue);
|
|
66
|
+
const [currentValue, _setCurrentValue] = useState(givenValue.isEmpty() ? defaultValue.get() : givenValue.get());
|
|
67
|
+
const setCurrentValue = (value2) => {
|
|
68
|
+
const candidate = new Field(value2);
|
|
69
|
+
_setCurrentValue(candidate.get());
|
|
70
|
+
};
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
const current = new Field(currentValue);
|
|
73
|
+
if (strategy === "params" /* params */) {
|
|
74
|
+
if (current.isEmpty()) {
|
|
75
|
+
params.delete(config.name);
|
|
76
|
+
setParams(params);
|
|
77
|
+
} else {
|
|
78
|
+
params.set(config.name, current.get());
|
|
79
|
+
setParams(params);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (strategy === "local" /* local */) {}
|
|
83
|
+
}, [currentValue, params, setParams, config.name, strategy]);
|
|
84
|
+
const value = Field.isEmpty(currentValue) ? "" : currentValue;
|
|
85
|
+
const onChange = (event) => setCurrentValue(event.currentTarget.value);
|
|
86
|
+
return {
|
|
87
|
+
strategy,
|
|
88
|
+
defaultValue: defaultValue.get(),
|
|
89
|
+
currentValue,
|
|
90
|
+
value,
|
|
91
|
+
set: setCurrentValue,
|
|
92
|
+
handleChange: onChange,
|
|
93
|
+
clear: () => setCurrentValue(defaultValue.get()),
|
|
94
|
+
label: { props: { htmlFor: config.name } },
|
|
95
|
+
input: { props: { id: config.name, name: config.name, value, onChange } },
|
|
96
|
+
changed: !Field.compare(currentValue, defaultValue.get()),
|
|
97
|
+
unchanged: Field.compare(currentValue, defaultValue.get()),
|
|
98
|
+
empty: Field.isEmpty(currentValue)
|
|
99
|
+
};
|
|
100
|
+
}
|
|
11
101
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
102
|
+
class LocalFields {
|
|
103
|
+
static clearAll(fields) {
|
|
104
|
+
return () => fields.forEach((field) => field.clear());
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// src/hooks/use-hover.ts
|
|
108
|
+
import { useCallback as useCallback2, useRef } from "react";
|
|
18
109
|
|
|
19
110
|
// src/hooks/use-toggle.ts
|
|
20
|
-
import {
|
|
111
|
+
import { useState as useState2 } from "react";
|
|
21
112
|
function useToggle({ name, defaultValue = false }) {
|
|
22
|
-
const [on,
|
|
23
|
-
const enable =
|
|
24
|
-
const disable =
|
|
25
|
-
const toggle =
|
|
26
|
-
const off =
|
|
27
|
-
const props =
|
|
113
|
+
const [on, setOn] = useState2(defaultValue);
|
|
114
|
+
const enable = () => setOn(true);
|
|
115
|
+
const disable = () => setOn(false);
|
|
116
|
+
const toggle = () => setOn((v) => !v);
|
|
117
|
+
const off = !on;
|
|
118
|
+
const props = {
|
|
28
119
|
controller: {
|
|
29
120
|
"aria-expanded": on ? "true" : "false",
|
|
30
121
|
"aria-controls": name,
|
|
31
122
|
role: "button",
|
|
32
123
|
tabIndex: 0
|
|
33
124
|
},
|
|
34
|
-
target: {
|
|
35
|
-
|
|
125
|
+
target: {
|
|
126
|
+
id: name,
|
|
127
|
+
role: "region",
|
|
128
|
+
"aria-hidden": on ? "false" : "true"
|
|
129
|
+
}
|
|
130
|
+
};
|
|
36
131
|
return { on, off, enable, disable, toggle, props };
|
|
37
132
|
}
|
|
38
133
|
function extractUseToggle(_props) {
|
|
@@ -42,20 +137,115 @@ function extractUseToggle(_props) {
|
|
|
42
137
|
rest
|
|
43
138
|
};
|
|
44
139
|
}
|
|
45
|
-
// src/components/index.ts
|
|
46
|
-
var exports_components = {};
|
|
47
|
-
__export(exports_components, {
|
|
48
|
-
Button: () => Button
|
|
49
|
-
});
|
|
50
140
|
|
|
51
|
-
// src/
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
141
|
+
// src/hooks/use-hover.ts
|
|
142
|
+
function useHover({
|
|
143
|
+
enabled = true
|
|
144
|
+
} = {}) {
|
|
145
|
+
const { on: isOn, enable, disable } = useToggle({ name: "is-hovering" });
|
|
146
|
+
const nodeRef = useRef(null);
|
|
147
|
+
const enterEvent = typeof window !== "undefined" && "PointerEvent" in window ? "pointerenter" : "mouseenter";
|
|
148
|
+
const leaveEvent = typeof window !== "undefined" && "PointerEvent" in window ? "pointerleave" : "mouseleave";
|
|
149
|
+
const ref = useCallback2((node) => {
|
|
150
|
+
const prev = nodeRef.current;
|
|
151
|
+
if (prev) {
|
|
152
|
+
prev.removeEventListener(enterEvent, enable);
|
|
153
|
+
prev.removeEventListener(leaveEvent, disable);
|
|
154
|
+
}
|
|
155
|
+
nodeRef.current = node;
|
|
156
|
+
if (node && enabled) {
|
|
157
|
+
node.addEventListener(enterEvent, enable);
|
|
158
|
+
node.addEventListener(leaveEvent, disable);
|
|
159
|
+
}
|
|
160
|
+
}, [enterEvent, leaveEvent, enabled, enable, disable]);
|
|
161
|
+
return {
|
|
162
|
+
attach: { ref },
|
|
163
|
+
isHovering: isOn && enabled
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
// src/services/fields.ts
|
|
167
|
+
class Fields {
|
|
168
|
+
static allUnchanged(fields) {
|
|
169
|
+
return fields.every((field) => field.unchanged);
|
|
170
|
+
}
|
|
171
|
+
static allEmpty(fields) {
|
|
172
|
+
return fields.every((field) => field.empty);
|
|
173
|
+
}
|
|
174
|
+
static anyEmpty(fields) {
|
|
175
|
+
return fields.some((field) => field.empty);
|
|
176
|
+
}
|
|
177
|
+
static anyUnchanged(fields) {
|
|
178
|
+
return fields.some((field) => field.unchanged);
|
|
179
|
+
}
|
|
180
|
+
static anyChanged(fields) {
|
|
181
|
+
return fields.some((field) => field.changed);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// src/services/form.ts
|
|
185
|
+
class Form {
|
|
186
|
+
static inputPattern(config) {
|
|
187
|
+
const required = config.required ?? true;
|
|
188
|
+
if (config.min && !config.max)
|
|
189
|
+
return { pattern: `.{${config.min}}`, required };
|
|
190
|
+
if (config.min && config.max)
|
|
191
|
+
return { pattern: `.{${config.min},${config.max}}`, required };
|
|
192
|
+
if (!config.min && config.max)
|
|
193
|
+
return { pattern: `.{,${config.max}}`, required };
|
|
194
|
+
return { pattern: undefined, required };
|
|
195
|
+
}
|
|
196
|
+
static textareaPattern(config) {
|
|
197
|
+
const required = config.required ?? true;
|
|
198
|
+
if (config.min && !config.max)
|
|
199
|
+
return { minLength: config.min, required };
|
|
200
|
+
if (config.min && config.max)
|
|
201
|
+
return { minLength: config.min, maxLength: config.max, required };
|
|
202
|
+
if (!config.min && config.max)
|
|
203
|
+
return { maxLength: config.max, required };
|
|
204
|
+
return { required };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// src/services/rhythm.ts
|
|
208
|
+
var DEFAULT_BASE_PX = 12;
|
|
209
|
+
function Rhythm(base = DEFAULT_BASE_PX) {
|
|
210
|
+
return {
|
|
211
|
+
times(times) {
|
|
212
|
+
const result = base * times;
|
|
213
|
+
const dimensions = {
|
|
214
|
+
height: { height: px(result) },
|
|
215
|
+
minHeight: { minHeight: px(result) },
|
|
216
|
+
maxHeight: { maxHeight: px(result) },
|
|
217
|
+
width: { width: px(result) },
|
|
218
|
+
minWidth: { minWidth: px(result) },
|
|
219
|
+
maxWidth: { maxWidth: px(result) },
|
|
220
|
+
square: { height: px(result), width: px(result) }
|
|
221
|
+
};
|
|
222
|
+
const style = {
|
|
223
|
+
height: { style: { height: px(result) } },
|
|
224
|
+
minHeight: { style: { minHeight: px(result) } },
|
|
225
|
+
maxHeight: { style: { maxHeight: px(result) } },
|
|
226
|
+
width: { style: { width: px(result) } },
|
|
227
|
+
minWidth: { style: { minWidth: px(result) } },
|
|
228
|
+
maxWidth: { style: { maxWidth: px(result) } },
|
|
229
|
+
square: { style: { height: px(result), width: px(result) } }
|
|
230
|
+
};
|
|
231
|
+
return { px: px(result), raw: result, style, ...dimensions };
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function px(number) {
|
|
236
|
+
return `${number}px`;
|
|
57
237
|
}
|
|
58
238
|
export {
|
|
59
|
-
|
|
60
|
-
|
|
239
|
+
useToggle,
|
|
240
|
+
useHover,
|
|
241
|
+
useFieldStrategyEnum,
|
|
242
|
+
useField,
|
|
243
|
+
useExitAction,
|
|
244
|
+
extractUseToggle,
|
|
245
|
+
Rhythm,
|
|
246
|
+
LocalFields,
|
|
247
|
+
Form,
|
|
248
|
+
Fields,
|
|
249
|
+
Field,
|
|
250
|
+
Button
|
|
61
251
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type FieldValueAllowedTypes = string | number | undefined | null;
|
|
2
|
+
export declare class Field<T extends FieldValueAllowedTypes> {
|
|
3
|
+
static readonly emptyValue: undefined;
|
|
4
|
+
static isEmpty(value: FieldValueAllowedTypes): boolean;
|
|
5
|
+
static compare(one: FieldValueAllowedTypes, another: FieldValueAllowedTypes): boolean;
|
|
6
|
+
private readonly value;
|
|
7
|
+
constructor(value: FieldValueAllowedTypes);
|
|
8
|
+
get(): T;
|
|
9
|
+
isEmpty(): boolean;
|
|
10
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility class for working with multiple fields
|
|
3
|
+
* @static
|
|
4
|
+
*/
|
|
5
|
+
export declare class Fields {
|
|
6
|
+
/**
|
|
7
|
+
* Check if all fields are unchanged
|
|
8
|
+
* @param {Array<{unchanged: boolean}>} fields - Array of field states
|
|
9
|
+
* @returns {boolean} True if all fields match their default values
|
|
10
|
+
*/
|
|
11
|
+
static allUnchanged(fields: {
|
|
12
|
+
unchanged: boolean;
|
|
13
|
+
}[]): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Check if all fields are empty
|
|
16
|
+
* @param {Array<{empty: boolean}>} fields - Array of field states
|
|
17
|
+
* @returns {boolean} True if all fields are empty
|
|
18
|
+
*/
|
|
19
|
+
static allEmpty(fields: {
|
|
20
|
+
empty: boolean;
|
|
21
|
+
}[]): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Check if any field is empty
|
|
24
|
+
* @param {Array<{empty: boolean}>} fields - Array of field states
|
|
25
|
+
* @returns {boolean} True if any field is empty
|
|
26
|
+
*/
|
|
27
|
+
static anyEmpty(fields: {
|
|
28
|
+
empty: boolean;
|
|
29
|
+
}[]): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Check if any field is unchanged
|
|
32
|
+
* @param {Array<{unchanged: boolean}>} fields - Array of field states
|
|
33
|
+
* @returns {boolean} True if any field matches its default value
|
|
34
|
+
*/
|
|
35
|
+
static anyUnchanged(fields: {
|
|
36
|
+
unchanged: boolean;
|
|
37
|
+
}[]): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Check if any field has changed
|
|
40
|
+
* @param {Array<{changed: boolean}>} fields - Array of field states
|
|
41
|
+
* @returns {boolean} True if any field differs from its default value
|
|
42
|
+
*/
|
|
43
|
+
static anyChanged(fields: {
|
|
44
|
+
changed: boolean;
|
|
45
|
+
}[]): boolean;
|
|
46
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
type PatternConfigType = {
|
|
3
|
+
min?: number;
|
|
4
|
+
max?: number;
|
|
5
|
+
required?: React.JSX.IntrinsicElements["input"]["required"];
|
|
6
|
+
};
|
|
7
|
+
export declare class Form {
|
|
8
|
+
static inputPattern(config: PatternConfigType): React.ComponentPropsWithoutRef<"input">;
|
|
9
|
+
static textareaPattern(config: PatternConfigType): React.ComponentPropsWithoutRef<"textarea">;
|
|
10
|
+
}
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
type RhythmBaseType = number;
|
|
2
|
+
type RhythmTimesType = number;
|
|
3
|
+
export declare function Rhythm(base?: RhythmBaseType): {
|
|
4
|
+
times(times: RhythmTimesType): {
|
|
5
|
+
height: {
|
|
6
|
+
height: string;
|
|
7
|
+
};
|
|
8
|
+
minHeight: {
|
|
9
|
+
minHeight: string;
|
|
10
|
+
};
|
|
11
|
+
maxHeight: {
|
|
12
|
+
maxHeight: string;
|
|
13
|
+
};
|
|
14
|
+
width: {
|
|
15
|
+
width: string;
|
|
16
|
+
};
|
|
17
|
+
minWidth: {
|
|
18
|
+
minWidth: string;
|
|
19
|
+
};
|
|
20
|
+
maxWidth: {
|
|
21
|
+
maxWidth: string;
|
|
22
|
+
};
|
|
23
|
+
square: {
|
|
24
|
+
height: string;
|
|
25
|
+
width: string;
|
|
26
|
+
};
|
|
27
|
+
px: string;
|
|
28
|
+
raw: number;
|
|
29
|
+
style: {
|
|
30
|
+
height: {
|
|
31
|
+
style: {
|
|
32
|
+
height: string;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
minHeight: {
|
|
36
|
+
style: {
|
|
37
|
+
minHeight: string;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
maxHeight: {
|
|
41
|
+
style: {
|
|
42
|
+
maxHeight: string;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
width: {
|
|
46
|
+
style: {
|
|
47
|
+
width: string;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
minWidth: {
|
|
51
|
+
style: {
|
|
52
|
+
minWidth: string;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
maxWidth: {
|
|
56
|
+
style: {
|
|
57
|
+
maxWidth: string;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
square: {
|
|
61
|
+
style: {
|
|
62
|
+
height: string;
|
|
63
|
+
width: string;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function Button(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./button";
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { FieldValueAllowedTypes } from "../services/field";
|
|
2
|
+
/** Type for field names */
|
|
3
|
+
type NewFieldNameType = string;
|
|
4
|
+
/** Valid HTML elements that can be used as field inputs */
|
|
5
|
+
export type FieldElementType = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
|
|
6
|
+
/**
|
|
7
|
+
* Defines the strategy for field value persistence
|
|
8
|
+
* @enum {string}
|
|
9
|
+
*/
|
|
10
|
+
export declare enum useFieldStrategyEnum {
|
|
11
|
+
/** Store field value in URL parameters */
|
|
12
|
+
params = "params",
|
|
13
|
+
/** Store field value in local state */
|
|
14
|
+
local = "local"
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Configuration options for the useField hook
|
|
18
|
+
* @template T - Type of the field value
|
|
19
|
+
*/
|
|
20
|
+
export type useFieldConfigType<T extends FieldValueAllowedTypes> = {
|
|
21
|
+
/** Unique identifier for the field */
|
|
22
|
+
name: NewFieldNameType;
|
|
23
|
+
/** Initial value for the field */
|
|
24
|
+
defaultValue?: T;
|
|
25
|
+
/** Strategy for value persistence */
|
|
26
|
+
strategy?: useFieldStrategyEnum;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Return type for the useField hook
|
|
30
|
+
* @template T - Type of the field value
|
|
31
|
+
*/
|
|
32
|
+
export type useFieldReturnType<T extends FieldValueAllowedTypes> = {
|
|
33
|
+
/** Current persistence strategy */
|
|
34
|
+
strategy: useFieldStrategyEnum;
|
|
35
|
+
/** Initial field value */
|
|
36
|
+
defaultValue: T;
|
|
37
|
+
/** Current field value */
|
|
38
|
+
currentValue: T;
|
|
39
|
+
/** Non-nullable field value, empty string for empty values */
|
|
40
|
+
value: NonNullable<T>;
|
|
41
|
+
/** Function to set field value */
|
|
42
|
+
set: (value: T) => void;
|
|
43
|
+
/** Change event handler for controlled components */
|
|
44
|
+
handleChange: (event: React.ChangeEvent<FieldElementType>) => void;
|
|
45
|
+
/** Reset field to default value */
|
|
46
|
+
clear: () => void;
|
|
47
|
+
/** Props for field label */
|
|
48
|
+
label: {
|
|
49
|
+
props: {
|
|
50
|
+
htmlFor: NewFieldNameType;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
/** Props for field input */
|
|
54
|
+
input: {
|
|
55
|
+
props: {
|
|
56
|
+
id: NewFieldNameType;
|
|
57
|
+
name: NewFieldNameType;
|
|
58
|
+
value: NonNullable<T>;
|
|
59
|
+
onChange: (event: React.ChangeEvent<FieldElementType>) => void;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
/** Whether field value differs from default */
|
|
63
|
+
changed: boolean;
|
|
64
|
+
/** Whether field value equals default */
|
|
65
|
+
unchanged: boolean;
|
|
66
|
+
/** Whether field is empty */
|
|
67
|
+
empty: boolean;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Hook for managing form field state with URL parameters or local state
|
|
71
|
+
*
|
|
72
|
+
* @template T - Type of the field value
|
|
73
|
+
* @param {useFieldConfigType<T>} config - Field configuration
|
|
74
|
+
* @returns {useFieldReturnType<T>} Field state and handlers
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```tsx
|
|
78
|
+
* // Using local strategy
|
|
79
|
+
* function NameField() {
|
|
80
|
+
* const field = useField({
|
|
81
|
+
* name: "username",
|
|
82
|
+
* defaultValue: "",
|
|
83
|
+
* strategy: useFieldStrategyEnum.local
|
|
84
|
+
* });
|
|
85
|
+
*
|
|
86
|
+
* return (
|
|
87
|
+
* <div>
|
|
88
|
+
* <label {...field.label.props}>Username:</label>
|
|
89
|
+
* <input
|
|
90
|
+
* {...field.input.props}
|
|
91
|
+
* type="text"
|
|
92
|
+
* value={field.value}
|
|
93
|
+
* onChange={field.handleChange}
|
|
94
|
+
* />
|
|
95
|
+
* </div>
|
|
96
|
+
* );
|
|
97
|
+
* }
|
|
98
|
+
*
|
|
99
|
+
* // Using URL parameters strategy
|
|
100
|
+
* function SearchField() {
|
|
101
|
+
* const field = useField({
|
|
102
|
+
* name: "q",
|
|
103
|
+
* strategy: useFieldStrategyEnum.params
|
|
104
|
+
* });
|
|
105
|
+
*
|
|
106
|
+
* return (
|
|
107
|
+
* <input
|
|
108
|
+
* type="search"
|
|
109
|
+
* {...field.input.props}
|
|
110
|
+
* value={field.value}
|
|
111
|
+
* onChange={field.handleChange}
|
|
112
|
+
* />
|
|
113
|
+
* );
|
|
114
|
+
* }
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export declare function useField<T extends FieldValueAllowedTypes>(config: useFieldConfigType<T>): useFieldReturnType<T>;
|
|
118
|
+
/**
|
|
119
|
+
* Utility class for working with local fields
|
|
120
|
+
* @static
|
|
121
|
+
*/
|
|
122
|
+
export declare class LocalFields {
|
|
123
|
+
static clearAll(fields: {
|
|
124
|
+
clear: VoidFunction;
|
|
125
|
+
}[]): () => void;
|
|
126
|
+
}
|
|
127
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { UseToggleReturnType } from "./use-toggle";
|
|
2
|
+
type UseHoverConfigType = {
|
|
3
|
+
enabled?: boolean;
|
|
4
|
+
};
|
|
5
|
+
type UseHoverReturnType = {
|
|
6
|
+
attach: {
|
|
7
|
+
ref: React.RefObject<any>;
|
|
8
|
+
};
|
|
9
|
+
isHovering: UseToggleReturnType["on"];
|
|
10
|
+
};
|
|
11
|
+
export declare function useHover(config?: UseHoverConfigType): UseHoverReturnType;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type UseToggleValueType = boolean;
|
|
2
|
+
export type UseToggleConfigType = {
|
|
3
|
+
name: string;
|
|
4
|
+
defaultValue?: UseToggleValueType;
|
|
5
|
+
};
|
|
6
|
+
type UseToggleProps = {
|
|
7
|
+
controller: {
|
|
8
|
+
"aria-expanded": "true" | "false";
|
|
9
|
+
"aria-controls": string;
|
|
10
|
+
role: "button";
|
|
11
|
+
tabIndex: 0;
|
|
12
|
+
};
|
|
13
|
+
target: {
|
|
14
|
+
id: string;
|
|
15
|
+
role: "region";
|
|
16
|
+
"aria-hidden": "true" | "false";
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export type UseToggleReturnType = {
|
|
20
|
+
on: UseToggleValueType;
|
|
21
|
+
off: UseToggleValueType;
|
|
22
|
+
enable: () => void;
|
|
23
|
+
disable: () => void;
|
|
24
|
+
toggle: () => void;
|
|
25
|
+
props: UseToggleProps;
|
|
26
|
+
};
|
|
27
|
+
export declare function useToggle({ name, defaultValue }: UseToggleConfigType): UseToggleReturnType;
|
|
28
|
+
export declare function extractUseToggle<X>(_props: UseToggleReturnType & X): {
|
|
29
|
+
toggle: UseToggleReturnType;
|
|
30
|
+
rest: X;
|
|
31
|
+
};
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type FieldValueAllowedTypes = string | number | undefined | null;
|
|
2
|
+
export declare class Field<T extends FieldValueAllowedTypes> {
|
|
3
|
+
static readonly emptyValue: undefined;
|
|
4
|
+
static isEmpty(value: FieldValueAllowedTypes): boolean;
|
|
5
|
+
static compare(one: FieldValueAllowedTypes, another: FieldValueAllowedTypes): boolean;
|
|
6
|
+
private readonly value;
|
|
7
|
+
constructor(value: FieldValueAllowedTypes);
|
|
8
|
+
get(): T;
|
|
9
|
+
isEmpty(): boolean;
|
|
10
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility class for working with multiple fields
|
|
3
|
+
* @static
|
|
4
|
+
*/
|
|
5
|
+
export declare class Fields {
|
|
6
|
+
/**
|
|
7
|
+
* Check if all fields are unchanged
|
|
8
|
+
* @param {Array<{unchanged: boolean}>} fields - Array of field states
|
|
9
|
+
* @returns {boolean} True if all fields match their default values
|
|
10
|
+
*/
|
|
11
|
+
static allUnchanged(fields: {
|
|
12
|
+
unchanged: boolean;
|
|
13
|
+
}[]): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Check if all fields are empty
|
|
16
|
+
* @param {Array<{empty: boolean}>} fields - Array of field states
|
|
17
|
+
* @returns {boolean} True if all fields are empty
|
|
18
|
+
*/
|
|
19
|
+
static allEmpty(fields: {
|
|
20
|
+
empty: boolean;
|
|
21
|
+
}[]): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Check if any field is empty
|
|
24
|
+
* @param {Array<{empty: boolean}>} fields - Array of field states
|
|
25
|
+
* @returns {boolean} True if any field is empty
|
|
26
|
+
*/
|
|
27
|
+
static anyEmpty(fields: {
|
|
28
|
+
empty: boolean;
|
|
29
|
+
}[]): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Check if any field is unchanged
|
|
32
|
+
* @param {Array<{unchanged: boolean}>} fields - Array of field states
|
|
33
|
+
* @returns {boolean} True if any field matches its default value
|
|
34
|
+
*/
|
|
35
|
+
static anyUnchanged(fields: {
|
|
36
|
+
unchanged: boolean;
|
|
37
|
+
}[]): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Check if any field has changed
|
|
40
|
+
* @param {Array<{changed: boolean}>} fields - Array of field states
|
|
41
|
+
* @returns {boolean} True if any field differs from its default value
|
|
42
|
+
*/
|
|
43
|
+
static anyChanged(fields: {
|
|
44
|
+
changed: boolean;
|
|
45
|
+
}[]): boolean;
|
|
46
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
type PatternConfigType = {
|
|
3
|
+
min?: number;
|
|
4
|
+
max?: number;
|
|
5
|
+
required?: React.JSX.IntrinsicElements["input"]["required"];
|
|
6
|
+
};
|
|
7
|
+
export declare class Form {
|
|
8
|
+
static inputPattern(config: PatternConfigType): React.ComponentPropsWithoutRef<"input">;
|
|
9
|
+
static textareaPattern(config: PatternConfigType): React.ComponentPropsWithoutRef<"textarea">;
|
|
10
|
+
}
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
type RhythmBaseType = number;
|
|
2
|
+
type RhythmTimesType = number;
|
|
3
|
+
export declare function Rhythm(base?: RhythmBaseType): {
|
|
4
|
+
times(times: RhythmTimesType): {
|
|
5
|
+
height: {
|
|
6
|
+
height: string;
|
|
7
|
+
};
|
|
8
|
+
minHeight: {
|
|
9
|
+
minHeight: string;
|
|
10
|
+
};
|
|
11
|
+
maxHeight: {
|
|
12
|
+
maxHeight: string;
|
|
13
|
+
};
|
|
14
|
+
width: {
|
|
15
|
+
width: string;
|
|
16
|
+
};
|
|
17
|
+
minWidth: {
|
|
18
|
+
minWidth: string;
|
|
19
|
+
};
|
|
20
|
+
maxWidth: {
|
|
21
|
+
maxWidth: string;
|
|
22
|
+
};
|
|
23
|
+
square: {
|
|
24
|
+
height: string;
|
|
25
|
+
width: string;
|
|
26
|
+
};
|
|
27
|
+
px: string;
|
|
28
|
+
raw: number;
|
|
29
|
+
style: {
|
|
30
|
+
height: {
|
|
31
|
+
style: {
|
|
32
|
+
height: string;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
minHeight: {
|
|
36
|
+
style: {
|
|
37
|
+
minHeight: string;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
maxHeight: {
|
|
41
|
+
style: {
|
|
42
|
+
maxHeight: string;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
width: {
|
|
46
|
+
style: {
|
|
47
|
+
width: string;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
minWidth: {
|
|
51
|
+
style: {
|
|
52
|
+
minWidth: string;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
maxWidth: {
|
|
56
|
+
style: {
|
|
57
|
+
maxWidth: string;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
square: {
|
|
61
|
+
style: {
|
|
62
|
+
height: string;
|
|
63
|
+
width: string;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bgord/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -13,10 +13,11 @@
|
|
|
13
13
|
],
|
|
14
14
|
"peerDependencies": {
|
|
15
15
|
"react": "19.1.0",
|
|
16
|
-
"react-dom": "19.1.0"
|
|
16
|
+
"react-dom": "19.1.0",
|
|
17
|
+
"react-router": "7.6.3"
|
|
17
18
|
},
|
|
18
19
|
"scripts": {
|
|
19
|
-
"build:js": "bun build src/index.ts --format esm --outdir dist --packages external --external react --external react-dom --external react/jsx-runtime",
|
|
20
|
+
"build:js": "bun build src/index.ts --format esm --outdir dist --packages external --external react --external react-dom --external react/jsx-runtime --external react-router",
|
|
20
21
|
"build:types": "bunx tsc --emitDeclarationOnly",
|
|
21
22
|
"build": "bun run build:js && bun run build:types"
|
|
22
23
|
},
|
|
@@ -24,7 +25,21 @@
|
|
|
24
25
|
"access": "public"
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|
|
28
|
+
"@happy-dom/global-registrator": "18.0.1",
|
|
29
|
+
"@testing-library/dom": "10.4.0",
|
|
30
|
+
"@testing-library/jest-dom": "6.6.3",
|
|
31
|
+
"@testing-library/react": "16.3.0",
|
|
32
|
+
"@testing-library/user-event": "14.6.1",
|
|
33
|
+
"@types/bun": "1.2.18",
|
|
27
34
|
"@types/react": "19.1.8",
|
|
28
|
-
"@types/react-dom": "19.1.6"
|
|
35
|
+
"@types/react-dom": "19.1.6",
|
|
36
|
+
"@biomejs/biome": "2.0.6",
|
|
37
|
+
"@commitlint/cli": "19.8.1",
|
|
38
|
+
"@commitlint/config-conventional": "19.8.1",
|
|
39
|
+
"cspell": "9.1.3",
|
|
40
|
+
"knip": "5.61.3",
|
|
41
|
+
"lefthook": "1.12.1",
|
|
42
|
+
"only-allow": "1.2.1",
|
|
43
|
+
"shellcheck": "3.1.0"
|
|
29
44
|
}
|
|
30
45
|
}
|