@bgord/ui 0.2.0 → 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.
@@ -1,2 +1,4 @@
1
+ export * from "./use-exit-action";
1
2
  export * from "./use-field";
3
+ export * from "./use-hover";
2
4
  export * from "./use-toggle";
@@ -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 {};
@@ -55,6 +55,8 @@ export type useFieldReturnType<T extends FieldValueAllowedTypes> = {
55
55
  props: {
56
56
  id: NewFieldNameType;
57
57
  name: NewFieldNameType;
58
+ value: NonNullable<T>;
59
+ onChange: (event: React.ChangeEvent<FieldElementType>) => void;
58
60
  };
59
61
  };
60
62
  /** Whether field value differs from default */
@@ -113,36 +115,6 @@ export type useFieldReturnType<T extends FieldValueAllowedTypes> = {
113
115
  * ```
114
116
  */
115
117
  export declare function useField<T extends FieldValueAllowedTypes>(config: useFieldConfigType<T>): useFieldReturnType<T>;
116
- /**
117
- * Utility class for working with multiple fields
118
- * @static
119
- */
120
- export declare class Fields {
121
- /**
122
- * Check if all fields are unchanged
123
- * @param {Array<{unchanged: boolean}>} fields - Array of field states
124
- * @returns {boolean} True if all fields match their default values
125
- */
126
- static allUnchanged(fields: {
127
- unchanged: boolean;
128
- }[]): boolean;
129
- /**
130
- * Check if any field is unchanged
131
- * @param {Array<{unchanged: boolean}>} fields - Array of field states
132
- * @returns {boolean} True if any field matches its default value
133
- */
134
- static anyUnchanged(fields: {
135
- unchanged: boolean;
136
- }[]): boolean;
137
- /**
138
- * Check if any field has changed
139
- * @param {Array<{changed: boolean}>} fields - Array of field states
140
- * @returns {boolean} True if any field differs from its default value
141
- */
142
- static anyChanged(fields: {
143
- changed: boolean;
144
- }[]): boolean;
145
- }
146
118
  /**
147
119
  * Utility class for working with local fields
148
120
  * @static
@@ -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.js CHANGED
@@ -6,6 +6,24 @@ function Button() {
6
6
  children: "Click"
7
7
  }, undefined, false, undefined, this);
8
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
+ }
9
27
  // src/hooks/use-field.ts
10
28
  import { useEffect, useState } from "react";
11
29
  import { useSearchParams } from "react-router";
@@ -46,8 +64,8 @@ function useField(config) {
46
64
  const givenValue = new Field(params.get(config.name));
47
65
  const defaultValue = new Field(config.defaultValue);
48
66
  const [currentValue, _setCurrentValue] = useState(givenValue.isEmpty() ? defaultValue.get() : givenValue.get());
49
- const setCurrentValue = (value) => {
50
- const candidate = new Field(value);
67
+ const setCurrentValue = (value2) => {
68
+ const candidate = new Field(value2);
51
69
  _setCurrentValue(candidate.get());
52
70
  };
53
71
  useEffect(() => {
@@ -63,56 +81,53 @@ function useField(config) {
63
81
  }
64
82
  if (strategy === "local" /* local */) {}
65
83
  }, [currentValue, params, setParams, config.name, strategy]);
84
+ const value = Field.isEmpty(currentValue) ? "" : currentValue;
85
+ const onChange = (event) => setCurrentValue(event.currentTarget.value);
66
86
  return {
67
87
  strategy,
68
88
  defaultValue: defaultValue.get(),
69
89
  currentValue,
70
- value: Field.isEmpty(currentValue) ? "" : currentValue,
90
+ value,
71
91
  set: setCurrentValue,
72
- handleChange: (event) => setCurrentValue(event.currentTarget.value),
92
+ handleChange: onChange,
73
93
  clear: () => setCurrentValue(defaultValue.get()),
74
94
  label: { props: { htmlFor: config.name } },
75
- input: { props: { id: config.name, name: config.name } },
95
+ input: { props: { id: config.name, name: config.name, value, onChange } },
76
96
  changed: !Field.compare(currentValue, defaultValue.get()),
77
97
  unchanged: Field.compare(currentValue, defaultValue.get()),
78
98
  empty: Field.isEmpty(currentValue)
79
99
  };
80
100
  }
81
101
 
82
- class Fields {
83
- static allUnchanged(fields) {
84
- return fields.every((field) => field.unchanged);
85
- }
86
- static anyUnchanged(fields) {
87
- return fields.some((field) => field.unchanged);
88
- }
89
- static anyChanged(fields) {
90
- return fields.some((field) => field.changed);
91
- }
92
- }
93
-
94
102
  class LocalFields {
95
103
  static clearAll(fields) {
96
104
  return () => fields.forEach((field) => field.clear());
97
105
  }
98
106
  }
107
+ // src/hooks/use-hover.ts
108
+ import { useCallback as useCallback2, useRef } from "react";
109
+
99
110
  // src/hooks/use-toggle.ts
100
- import { useCallback, useMemo, useState as useState2 } from "react";
111
+ import { useState as useState2 } from "react";
101
112
  function useToggle({ name, defaultValue = false }) {
102
- const [on, setIsOn] = useState2(defaultValue);
103
- const enable = useCallback(() => setIsOn(true), []);
104
- const disable = useCallback(() => setIsOn(false), []);
105
- const toggle = useCallback(() => setIsOn((v) => !v), []);
106
- const off = useMemo(() => !on, [on]);
107
- const props = useMemo(() => ({
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 = {
108
119
  controller: {
109
120
  "aria-expanded": on ? "true" : "false",
110
121
  "aria-controls": name,
111
122
  role: "button",
112
123
  tabIndex: 0
113
124
  },
114
- target: { id: name, role: "region", "aria-hidden": on ? "false" : "true" }
115
- }), [on, name]);
125
+ target: {
126
+ id: name,
127
+ role: "region",
128
+ "aria-hidden": on ? "false" : "true"
129
+ }
130
+ };
116
131
  return { on, off, enable, disable, toggle, props };
117
132
  }
118
133
  function extractUseToggle(_props) {
@@ -122,6 +137,50 @@ function extractUseToggle(_props) {
122
137
  rest
123
138
  };
124
139
  }
140
+
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
+ }
125
184
  // src/services/form.ts
126
185
  class Form {
127
186
  static inputPattern(config) {
@@ -178,8 +237,10 @@ function px(number) {
178
237
  }
179
238
  export {
180
239
  useToggle,
240
+ useHover,
181
241
  useFieldStrategyEnum,
182
242
  useField,
243
+ useExitAction,
183
244
  extractUseToggle,
184
245
  Rhythm,
185
246
  LocalFields,
@@ -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
+ }
@@ -1,3 +1,4 @@
1
1
  export * from "./field";
2
+ export * from "./fields";
2
3
  export * from "./form";
3
4
  export * from "./rhythm";
@@ -0,0 +1 @@
1
+ export declare function Button(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export * from "./button";
@@ -0,0 +1,3 @@
1
+ export * from "./use-field";
2
+ export * from "./use-hover";
3
+ export * from "./use-toggle";
@@ -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,3 @@
1
+ export * from "./components";
2
+ export * from "./hooks";
3
+ export * from "./services";
@@ -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,4 @@
1
+ export * from "./field";
2
+ export * from "./fields";
3
+ export * from "./form";
4
+ export * from "./rhythm";
@@ -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.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -38,7 +38,7 @@
38
38
  "@commitlint/config-conventional": "19.8.1",
39
39
  "cspell": "9.1.3",
40
40
  "knip": "5.61.3",
41
- "lefthook": "1.11.16",
41
+ "lefthook": "1.12.1",
42
42
  "only-allow": "1.2.1",
43
43
  "shellcheck": "3.1.0"
44
44
  }