@bgord/ui 0.3.0 → 0.4.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/index.js CHANGED
@@ -105,7 +105,7 @@ class LocalFields {
105
105
  }
106
106
  }
107
107
  // src/hooks/use-hover.ts
108
- import { useCallback as useCallback2, useRef } from "react";
108
+ import { useCallback, useRef } from "react";
109
109
 
110
110
  // src/hooks/use-toggle.ts
111
111
  import { useState as useState2 } from "react";
@@ -146,7 +146,7 @@ function useHover({
146
146
  const nodeRef = useRef(null);
147
147
  const enterEvent = typeof window !== "undefined" && "PointerEvent" in window ? "pointerenter" : "mouseenter";
148
148
  const leaveEvent = typeof window !== "undefined" && "PointerEvent" in window ? "pointerleave" : "mouseleave";
149
- const ref = useCallback2((node) => {
149
+ const ref = useCallback((node) => {
150
150
  const prev = nodeRef.current;
151
151
  if (prev) {
152
152
  prev.removeEventListener(enterEvent, enable);
@@ -163,6 +163,19 @@ function useHover({
163
163
  isHovering: isOn && enabled
164
164
  };
165
165
  }
166
+ // src/services/colorful.ts
167
+ function Colorful(color) {
168
+ const value = `var(--${color})`;
169
+ const options = {
170
+ color: { color: value },
171
+ background: { background: value }
172
+ };
173
+ const style = {
174
+ color: { style: { color: value } },
175
+ background: { style: { background: value } }
176
+ };
177
+ return { ...options, style };
178
+ }
166
179
  // src/services/fields.ts
167
180
  class Fields {
168
181
  static allUnchanged(fields) {
@@ -204,6 +217,24 @@ class Form {
204
217
  return { required };
205
218
  }
206
219
  }
220
+ // src/services/pluralize.ts
221
+ import { polishPlurals } from "polish-plurals";
222
+ function pluralize(options) {
223
+ if (options.language === "en" /* en */) {
224
+ const plural = options.plural ?? `${options.singular}s`;
225
+ if (options.value === 1)
226
+ return options.singular;
227
+ return plural;
228
+ }
229
+ if (options.language === "pl" /* pl */) {
230
+ const value = options.value ?? 1;
231
+ if (value === 1)
232
+ return options.singular;
233
+ return polishPlurals(options.singular, String(options.plural), String(options.genitive), value);
234
+ }
235
+ console.warn(`[@bgord/frontend] missing pluralization function for language: ${options.language}.`);
236
+ return options.singular;
237
+ }
207
238
  // src/services/rhythm.ts
208
239
  var DEFAULT_BASE_PX = 12;
209
240
  function Rhythm(base = DEFAULT_BASE_PX) {
@@ -235,17 +266,60 @@ function Rhythm(base = DEFAULT_BASE_PX) {
235
266
  function px(number) {
236
267
  return `${number}px`;
237
268
  }
269
+ // src/services/translations.tsx
270
+ import { createContext, use, useCallback as useCallback2 } from "react";
271
+ var TranslationsContext = createContext({
272
+ translations: {},
273
+ language: "en"
274
+ });
275
+ function useTranslations() {
276
+ const value = use(TranslationsContext);
277
+ if (value === undefined) {
278
+ throw new Error("useTranslations must be used within the TranslationsContext");
279
+ }
280
+ const translate = useCallback2((key, variables) => {
281
+ const translation = value.translations[key];
282
+ if (!translation) {
283
+ console.warn(`[@bgord/ui] missing translation for key: ${key}`);
284
+ return key;
285
+ }
286
+ if (!variables)
287
+ return translation;
288
+ return Object.entries(variables).reduce((result, [placeholder, value2]) => {
289
+ const regex = new RegExp(`{{${placeholder}}}`, "g");
290
+ return result.replace(regex, String(value2));
291
+ }, translation);
292
+ }, [value.translations]);
293
+ return translate;
294
+ }
295
+ function useLanguage() {
296
+ const value = use(TranslationsContext);
297
+ if (value === undefined) {
298
+ throw new Error("useLanguage must be used within the TranslationsContext");
299
+ }
300
+ return value.language;
301
+ }
302
+ function usePluralize() {
303
+ const language = useLanguage();
304
+ return (options) => pluralize({ ...options, language });
305
+ }
238
306
  export {
307
+ useTranslations,
239
308
  useToggle,
309
+ usePluralize,
310
+ useLanguage,
240
311
  useHover,
241
312
  useFieldStrategyEnum,
242
313
  useField,
243
314
  useExitAction,
315
+ pluralize,
244
316
  extractUseToggle,
317
+ TranslationsContext,
245
318
  Rhythm,
246
319
  LocalFields,
247
320
  Form,
248
321
  Fields,
249
322
  Field,
323
+ Colorful,
250
324
  Button
251
325
  };
@@ -0,0 +1,22 @@
1
+ type ColorType = string;
2
+ export declare function Colorful(color: ColorType): {
3
+ style: {
4
+ color: {
5
+ style: {
6
+ color: string;
7
+ };
8
+ };
9
+ background: {
10
+ style: {
11
+ background: string;
12
+ };
13
+ };
14
+ };
15
+ color: {
16
+ color: string;
17
+ };
18
+ background: {
19
+ background: string;
20
+ };
21
+ };
22
+ export {};
@@ -1,4 +1,7 @@
1
+ export * from "./colorful";
1
2
  export * from "./field";
2
3
  export * from "./fields";
3
4
  export * from "./form";
5
+ export * from "./pluralize";
4
6
  export * from "./rhythm";
7
+ export * from "./translations";
@@ -0,0 +1,11 @@
1
+ type PluralizeWordType = string;
2
+ type PluralizeValueType = number | null | undefined;
3
+ export type PluralizeOptionsType = {
4
+ value: PluralizeValueType;
5
+ singular: PluralizeWordType;
6
+ plural?: PluralizeWordType;
7
+ genitive?: PluralizeWordType;
8
+ language: string;
9
+ };
10
+ export declare function pluralize(options: PluralizeOptionsType): PluralizeWordType;
11
+ export {};
@@ -0,0 +1,16 @@
1
+ import { PluralizeOptionsType } from "./pluralize";
2
+ export type TranslationsKeyType = string;
3
+ export type TranslationsValueType = string;
4
+ export type TranslationsType = Record<TranslationsKeyType, TranslationsValueType>;
5
+ type TranslationPlaceholderType = string;
6
+ type TranslationPlaceholderValueType = string | number;
7
+ type TranslationVariableType = Record<TranslationPlaceholderType, TranslationPlaceholderValueType>;
8
+ export type TranslationsContextValueType = {
9
+ translations: TranslationsType;
10
+ language: string;
11
+ };
12
+ export declare const TranslationsContext: import("react").Context<TranslationsContextValueType>;
13
+ export declare function useTranslations(): (key: TranslationsKeyType, variables?: TranslationVariableType) => string;
14
+ export declare function useLanguage(): TranslationsContextValueType["language"];
15
+ export declare function usePluralize(): (options: Omit<PluralizeOptionsType, "language">) => string;
16
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bgord/ui",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -38,8 +38,11 @@
38
38
  "@commitlint/config-conventional": "19.8.1",
39
39
  "cspell": "9.1.3",
40
40
  "knip": "5.61.3",
41
- "lefthook": "1.12.1",
41
+ "lefthook": "1.12.2",
42
42
  "only-allow": "1.2.1",
43
43
  "shellcheck": "3.1.0"
44
+ },
45
+ "dependencies": {
46
+ "polish-plurals": "^1.1.0"
44
47
  }
45
48
  }
@@ -1 +0,0 @@
1
- export declare function Button(): import("react/jsx-runtime").JSX.Element;
@@ -1 +0,0 @@
1
- export * from "./button";
@@ -1,3 +0,0 @@
1
- export * from "./use-field";
2
- export * from "./use-hover";
3
- export * from "./use-toggle";
@@ -1,127 +0,0 @@
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 {};
@@ -1,12 +0,0 @@
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 {};
@@ -1,32 +0,0 @@
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 {};
@@ -1,3 +0,0 @@
1
- export * from "./components";
2
- export * from "./hooks";
3
- export * from "./services";
@@ -1,10 +0,0 @@
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
- }
@@ -1,46 +0,0 @@
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,11 +0,0 @@
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 {};
@@ -1,4 +0,0 @@
1
- export * from "./field";
2
- export * from "./fields";
3
- export * from "./form";
4
- export * from "./rhythm";
@@ -1,69 +0,0 @@
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 {};