@ainias42/react-bootstrap-mobile 0.2.2 → 0.2.4

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.
@@ -0,0 +1,3 @@
1
+ export declare const SwitchController: import("../../../helper/withForwardRef").RefComponent<Omit<import("../Switch/Switch").SwitchProps<unknown>, "value" | "ref" | "onBlur" | "name" | "onChangeChecked"> & {
2
+ name: string;
3
+ }, never>;
@@ -8,5 +8,6 @@ export type FileInputProps<OnChangeFileData> = RbmComponentProps<Override<Omit<M
8
8
  value?: FileType;
9
9
  mimeType?: string;
10
10
  required?: boolean;
11
+ "data-test-id"?: string;
11
12
  } & Listener<'onChangeFile', OnChangeFileData, FileType | undefined>>>;
12
13
  export declare const FileInput: <OnChangeData>({ value, mimeType, required, ...otherProps }: FileInputProps<OnChangeData>) => React.JSX.Element;
@@ -14,5 +14,6 @@ export type MultipleFileInputProps<OnChangeFilesData> = RbmComponentProps<Overri
14
14
  allowOverride?: boolean;
15
15
  showDeleteButton?: boolean;
16
16
  error?: string;
17
+ "data-test-id"?: string;
17
18
  } & Listener<'onChangeFiles', OnChangeFilesData, FileType[]>>>;
18
- export declare const MultipleFileInput: <OnChangeData>({ className, style, value, label, mimeTypes, maxFiles, maxSizePerFile, allowOverride, onError, showDeleteButton, error, ...otherProps }: MultipleFileInputProps<OnChangeData>) => React.JSX.Element;
19
+ export declare const MultipleFileInput: <OnChangeData>({ className, style, value, label, mimeTypes, maxFiles, maxSizePerFile, allowOverride, onError, showDeleteButton, error, "data-test-id": testId, ...otherProps }: MultipleFileInputProps<OnChangeData>) => React.JSX.Element;
@@ -0,0 +1,5 @@
1
+ export declare function useDelayedState<T>(initialState: T | (() => T), delay?: number, maxDelay?: number | undefined): {
2
+ state: T;
3
+ immediateState: T;
4
+ setState: (newValue: (T)) => void;
5
+ };
@@ -3,6 +3,7 @@ import { ComponentType } from 'react';
3
3
  import { RbmComponentProps } from '../RbmComponentProps';
4
4
  import { IconSource } from '../Icon/Icon';
5
5
  export type TopBarActionButtonType = {
6
+ order?: number;
6
7
  title: string;
7
8
  icon?: IconSource;
8
9
  action: () => void;
@@ -21,6 +22,6 @@ export type TopBarProps = RbmComponentProps<{
21
22
  transparent?: boolean;
22
23
  drawBehind?: boolean;
23
24
  }>;
24
- declare function TopBar({ title, rightButtons, leftButtons, hiddenButtons, className, transparent, drawBehind, ...rbmProps }: TopBarProps): React.JSX.Element;
25
+ declare function TopBar({ title, rightButtons: unsortedRightButtons, leftButtons: unsortedLeftButtons, hiddenButtons: unsortedHiddenButtons, className, transparent, drawBehind, ...rbmProps }: TopBarProps): React.JSX.Element;
25
26
  declare const TopBarMemo: typeof TopBar;
26
27
  export { TopBarMemo as TopBar };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainias42/react-bootstrap-mobile",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Mobile React Components using Bootstrap",
5
5
  "main": "dist/bootstrapReactMobile",
6
6
  "scripts": {
@@ -92,7 +92,7 @@
92
92
  "webpack-dev-server": "^5.0.4"
93
93
  },
94
94
  "dependencies": {
95
- "@ainias42/js-helper": ">=0.8.15",
95
+ "@ainias42/js-helper": ">=0.8.16",
96
96
  "@types/react-virtualized-auto-sizer": "^1.0.4",
97
97
  "classnames": "^2.5.1",
98
98
  "isomorphic-style-loader": "^5.3.2",
@@ -315,7 +315,7 @@ function Clickable<
315
315
  };
316
316
  if (typeof href === 'string') {
317
317
  return (
318
- <a {...props} href={href} ref={refSetter as ForwardedRef<HTMLAnchorElement>}>
318
+ <a {...props} className={classNames(styles.link, props.className)} href={href} ref={refSetter as ForwardedRef<HTMLAnchorElement>}>
319
319
  {children}
320
320
  </a>
321
321
  );
@@ -1,3 +1,8 @@
1
1
  .clickable {
2
2
  cursor: pointer;
3
3
  }
4
+
5
+ .link {
6
+ color: var(--link-color);
7
+ text-decoration: underline;
8
+ }
@@ -0,0 +1,4 @@
1
+ import { Switch } from "../Switch/Switch";
2
+ import { withHookController } from "./withHookController";
3
+
4
+ export const SwitchController = withHookController(Switch, "onChangeChecked");
@@ -41,6 +41,7 @@ export function withHookController<C extends ComponentType<any>, OnChangeProp ex
41
41
  {...field}
42
42
  {...onChangeProps}
43
43
  value={field.value ?? emptyValue}
44
+ checked={field.value ?? emptyValue}
44
45
  ref={composedRef}
45
46
  error={errorMessage}
46
47
  >
@@ -1,25 +1,24 @@
1
1
  import * as React from 'react';
2
- import {RbmComponentProps} from '../../../RbmComponentProps';
3
- import {Override} from '../../../../TypeHelpers';
4
- import {useCallback, useMemo} from 'react';
5
- import {Listener, useListenerWithExtractedProps} from '../../../Hooks/useListener';
6
-
2
+ import { RbmComponentProps } from '../../../RbmComponentProps';
3
+ import { Override } from '../../../../TypeHelpers';
4
+ import { useCallback, useMemo } from 'react';
5
+ import { Listener, useListenerWithExtractedProps } from '../../../Hooks/useListener';
7
6
  import styles from './fileInput.scss';
8
- import {withMemo} from '../../../../helper/withMemo';
9
- import {FileType} from "./FileType";
10
- import {MultipleFileInput, MultipleFileInputProps} from "./MultipleFileInput";
7
+ import { withMemo } from '../../../../helper/withMemo';
8
+ import { FileType } from "./FileType";
9
+ import { MultipleFileInput, MultipleFileInputProps } from "./MultipleFileInput";
11
10
 
12
11
 
13
12
  export type FileInputProps<OnChangeFileData> = RbmComponentProps<
14
13
  Override<
15
- Omit<MultipleFileInputProps<unknown>, "onChangeFiles" | "maxFiles" | "onChangeFilesData" | "mimeTypes"|"showDeleteButton">, {
14
+ Omit<MultipleFileInputProps<unknown>, "onChangeFiles" | "maxFiles" | "onChangeFilesData" | "mimeTypes" | "showDeleteButton">, {
16
15
  value?: FileType,
17
16
  mimeType?: string,
18
17
  required?: boolean,
18
+ "data-test-id"?: string;
19
19
  } & Listener<'onChangeFile', OnChangeFileData, FileType | undefined>>
20
20
  >;
21
21
 
22
- // TODO use MultipleFileInput internal
23
22
  export const FileInput = withMemo(function FileInput<OnChangeData>({
24
23
  value,
25
24
  mimeType,
@@ -1,21 +1,21 @@
1
1
  import * as React from 'react';
2
- import {RbmComponentProps} from '../../../RbmComponentProps';
3
- import {Override} from '../../../../TypeHelpers';
4
- import {ChangeEventHandler, DragEvent, InputHTMLAttributes, useCallback, useRef} from 'react';
5
- import {Listener, useListenerWithExtractedProps} from '../../../Hooks/useListener';
2
+ import { RbmComponentProps } from '../../../RbmComponentProps';
3
+ import { Override } from '../../../../TypeHelpers';
4
+ import { ChangeEventHandler, DragEvent, InputHTMLAttributes, useCallback, useRef } from 'react';
5
+ import { Listener, useListenerWithExtractedProps } from '../../../Hooks/useListener';
6
6
  import styles from './fileInput.scss';
7
- import {withMemo} from '../../../../helper/withMemo';
7
+ import { withMemo } from '../../../../helper/withMemo';
8
8
  import classNames from 'classnames';
9
- import {Block} from '../../../Layout/Block';
10
- import {Text} from '../../../Text/Text';
11
- import {Flex} from '../../../Layout/Flex';
12
- import {Grow} from '../../../Layout/Grow';
13
- import {Icon} from '../../../Icon/Icon';
14
- import {faFile, faPlus, faTimesCircle} from '@fortawesome/free-solid-svg-icons';
15
- import {Image} from '../../../Image/Image';
16
- import {Clickable} from '../../../Clickable/Clickable';
17
- import {Inline} from '../../../Layout/Inline';
18
- import {FileType} from "./FileType";
9
+ import { Block } from '../../../Layout/Block';
10
+ import { Text } from '../../../Text/Text';
11
+ import { Flex } from '../../../Layout/Flex';
12
+ import { Grow } from '../../../Layout/Grow';
13
+ import { Icon } from '../../../Icon/Icon';
14
+ import { faFile, faPlus, faTimesCircle } from '@fortawesome/free-solid-svg-icons';
15
+ import { Image } from '../../../Image/Image';
16
+ import { Clickable } from '../../../Clickable/Clickable';
17
+ import { Inline } from '../../../Layout/Inline';
18
+ import { FileType } from "./FileType";
19
19
  import { InlineBlock } from "../../../Layout/InlineBlock";
20
20
 
21
21
 
@@ -32,6 +32,7 @@ export type MultipleFileInputProps<OnChangeFilesData> = RbmComponentProps<
32
32
  allowOverride?: boolean
33
33
  showDeleteButton?: boolean
34
34
  error?: string,
35
+ "data-test-id"?: string;
35
36
  } & Listener<'onChangeFiles', OnChangeFilesData, FileType[]>
36
37
  >
37
38
  >;
@@ -47,7 +48,8 @@ export const MultipleFileInput = withMemo(function MultipleImageInput<OnChangeDa
47
48
  allowOverride = maxFiles === 1,
48
49
  onError,
49
50
  showDeleteButton = true,
50
- error,
51
+ error,
52
+ "data-test-id": testId,
51
53
  ...otherProps
52
54
  }: MultipleFileInputProps<OnChangeData>) {
53
55
  // Variables
@@ -78,7 +80,7 @@ export const MultipleFileInput = withMemo(function MultipleImageInput<OnChangeDa
78
80
  [mimeTypes]
79
81
  );
80
82
 
81
- const [onChangeFiles, ...props] = useListenerWithExtractedProps('onChangeFiles', otherProps);
83
+ const [onChangeFiles, props] = useListenerWithExtractedProps('onChangeFiles', otherProps);
82
84
  const getBase64 = useCallback((inputFiles: Blob[]) => {
83
85
  const promises = inputFiles.map(
84
86
  (file) =>
@@ -220,6 +222,7 @@ export const MultipleFileInput = withMemo(function MultipleImageInput<OnChangeDa
220
222
  <span
221
223
  className={classNames(styles.fileInput, className)}
222
224
  style={style}
225
+ data-test-id={testId}
223
226
  >
224
227
  <Flex horizontal={true}>
225
228
  {!!label && (
@@ -248,13 +251,15 @@ export const MultipleFileInput = withMemo(function MultipleImageInput<OnChangeDa
248
251
  >
249
252
  {renderFile(file)}
250
253
  {showDeleteButton &&
251
- <Clickable className={styles.previewRemove} onClick={removeFile} onClickData={index}>
254
+ <Clickable className={styles.previewRemove} onClick={removeFile}
255
+ onClickData={index}>
252
256
  <Icon icon={faTimesCircle}/>
253
257
  </Clickable>}
254
258
  </Clickable>
255
259
  </Grow>;
256
260
  })}
257
- <Grow className={classNames(styles.addFile, {[styles.hidden]: value.length >= maxFiles})} center={true}>
261
+ <Grow className={classNames(styles.addFile, {[styles.hidden]: value.length >= maxFiles})}
262
+ center={true}>
258
263
  <Clickable
259
264
  className={styles.addFileButton}
260
265
  onDrop={onDrop}
@@ -19,6 +19,7 @@ import { useOnChangeDone } from '../hooks/useOnChangeDone';
19
19
  import { InlineBlock } from "../../Layout/InlineBlock";
20
20
  import { Text } from "../../Text/Text";
21
21
  import { useSendFormContext } from "../Controller/SendFormContext";
22
+ import { useDebounced } from "../../Hooks/useDebounced";
22
23
 
23
24
  export type InputProps<OnChangeData, OnBlurData, OnChangeDoneData> = RbmComponentProps<
24
25
  Override<
@@ -119,10 +120,12 @@ export const Input = withForwardRef(function Input<OnChangeData, OnBlurData, OnC
119
120
  otherPropsWithoutOnchange
120
121
  );
121
122
 
122
- const [onChangeDone, otherPropsWithoutData] = useListenerWithExtractedProps<'onChangeDone', OnChangeDoneData>(
123
+ const [onChangeDoneWithoutDeboune, otherPropsWithoutData] = useListenerWithExtractedProps<'onChangeDone', OnChangeDoneData>(
123
124
  'onChangeDone',
124
125
  otherPropsWithoutBlur
125
126
  );
127
+ const onChangeDone = useDebounced(onChangeDoneWithoutDeboune, [onChangeDoneWithoutDeboune]);
128
+
126
129
 
127
130
  const realOnKeyDown = useCallback(
128
131
  (e: KeyboardEvent<HTMLInputElement>) => {
@@ -156,7 +159,7 @@ export const Input = withForwardRef(function Input<OnChangeData, OnBlurData, OnC
156
159
  }
157
160
  }
158
161
  },
159
- [onKeyDown, onChange, otherProps]
162
+ [onKeyDown, onEnter, otherProps.type, otherProps.step, otherProps.max, otherProps.min, onChange]
160
163
  );
161
164
 
162
165
  // Effects
@@ -0,0 +1,18 @@
1
+ import { useCallback, useState } from "react";
2
+ import { useDelayed } from "./useDelayed";
3
+
4
+ export function useDelayedState<T>(initialState: T | (() => T), delay = 100, maxDelay: number | undefined = undefined) {
5
+ const [immediateState, setImmediateState] = useState(initialState);
6
+ const [state, setState] = useState(immediateState);
7
+
8
+ const setDelayedState = useDelayed((newState: (T)) => {
9
+ setState(newState);
10
+ }, [], delay, maxDelay);
11
+
12
+ const setValue = useCallback((newValue: (T)) => {
13
+ setImmediateState(newValue);
14
+ setDelayedState(newValue);
15
+ }, [setDelayedState]);
16
+
17
+ return {state, immediateState, setState: setValue};
18
+ }
@@ -37,6 +37,7 @@ function Flex<AsType extends keyof JSX.IntrinsicElements = 'div'>(
37
37
  className={classNames(className, styles.flex, {
38
38
  [styles.horizontal]: horizontal,
39
39
  [styles.grow]: grow,
40
+ [styles.weight1]: grow,
40
41
  })}
41
42
  as={as as AsType}
42
43
  ref={ref}
@@ -18,7 +18,7 @@ $containerBreakpoints: $grid-breakpoints;
18
18
  @mixin printClasses {
19
19
  @media print {
20
20
  @for $i from 1 through $columns {
21
- .item-print-#{$i} {
21
+ > .item-print-#{$i} {
22
22
  grid-column: auto / span $i;
23
23
  }
24
24
  }
@@ -26,14 +26,14 @@ $containerBreakpoints: $grid-breakpoints;
26
26
  // Start with `1` because `0` is and invalid value.
27
27
  // Ends with `$columns - 1` because offsetting by the width of an entire row isn't possible.
28
28
  @for $i from 1 through ($columns - 1) {
29
- .start-print-#{$i} {
29
+ > .start-print-#{$i} {
30
30
  grid-column-start: $i;
31
31
  }
32
32
  }
33
33
 
34
34
  // Add classes for reordering
35
35
  @for $i from -10 through 10 {
36
- .order-print-#{$i} {
36
+ > .order-print-#{$i} {
37
37
  order: $i;
38
38
  }
39
39
  }
@@ -42,7 +42,7 @@ $containerBreakpoints: $grid-breakpoints;
42
42
 
43
43
  @mixin contentMin($breakpointName) {
44
44
  @for $i from 1 through $columns {
45
- .item-#{$breakpointName}-#{$i} {
45
+ > .item-#{$breakpointName}-#{$i} {
46
46
  grid-column: auto / span $i;
47
47
  }
48
48
  }
@@ -50,7 +50,7 @@ $containerBreakpoints: $grid-breakpoints;
50
50
  // Start with `1` because `0` is and invalid value.
51
51
  // Ends with `$columns - 1` because offsetting by the width of an entire row isn't possible.
52
52
  @for $i from 1 through ($columns - 1) {
53
- .start-#{$breakpointName}-#{$i} {
53
+ > .start-#{$breakpointName}-#{$i} {
54
54
  grid-column-start: $i;
55
55
  }
56
56
  }
@@ -58,7 +58,7 @@ $containerBreakpoints: $grid-breakpoints;
58
58
 
59
59
  @mixin contentOnly($breakpointName) {
60
60
  @for $i from -10 through 10 {
61
- .order-#{$breakpointName}-#{$i} {
61
+ > .order-#{$breakpointName}-#{$i} {
62
62
  order: $i;
63
63
  }
64
64
  }
@@ -110,7 +110,7 @@ $containerBreakpoints: $grid-breakpoints;
110
110
  @each $breakpoint in map-keys($breakpoints) {
111
111
  @include media-breakpoint-up($breakpoint, $breakpoints) {
112
112
  @for $i from 1 through $columns {
113
- .item-#{$breakpoint}-#{$i} {
113
+ > .item-#{$breakpoint}-#{$i} {
114
114
  grid-column: auto / span $i;
115
115
  }
116
116
  }
@@ -118,7 +118,7 @@ $containerBreakpoints: $grid-breakpoints;
118
118
  // Start with `1` because `0` is and invalid value.
119
119
  // Ends with `$columns - 1` because offsetting by the width of an entire row isn't possible.
120
120
  @for $i from 1 through ($columns - 1) {
121
- .start-#{$breakpoint}-#{$i} {
121
+ > .start-#{$breakpoint}-#{$i} {
122
122
  grid-column-start: $i;
123
123
  }
124
124
  }
@@ -127,7 +127,7 @@ $containerBreakpoints: $grid-breakpoints;
127
127
  // Add classes for reordering
128
128
  @include media-breakpoint-only($breakpoint, $breakpoints) {
129
129
  @for $i from -10 through 10 {
130
- .order-#{$breakpoint}-#{$i} {
130
+ > .order-#{$breakpoint}-#{$i} {
131
131
  order: $i;
132
132
  }
133
133
  }
@@ -18,6 +18,7 @@ import { Inline } from '../Layout/Inline';
18
18
  import { View } from '../Layout/View';
19
19
 
20
20
  export type TopBarActionButtonType = {
21
+ order?: number;
21
22
  title: string;
22
23
  icon?: IconSource;
23
24
  action: () => void;
@@ -45,11 +46,11 @@ function getButtonComponents(buttons: TopBarButtonType[]) {
45
46
  const key = button.key ?? String(index);
46
47
  if ('component' in button) {
47
48
  const Component = button.component;
48
- return <Component {...button} key={key} onClick={button.action} />;
49
+ return <Component {...button} key={key} onClick={button.action}/>;
49
50
  }
50
51
  let child: string | ReactElement | undefined = button.title;
51
52
  if (button.icon) {
52
- child = <Icon icon={button.icon} />;
53
+ child = <Icon icon={button.icon}/>;
53
54
  }
54
55
  return (
55
56
  <TopBarButton key={key} onClick={button.action} disabled={button.disabled} __allowChildren="all">
@@ -60,15 +61,15 @@ function getButtonComponents(buttons: TopBarButtonType[]) {
60
61
  }
61
62
 
62
63
  function TopBar({
63
- title = '',
64
- rightButtons = [],
65
- leftButtons = [],
66
- hiddenButtons = [],
67
- className,
68
- transparent = false,
69
- drawBehind = false,
70
- ...rbmProps
71
- }: TopBarProps) {
64
+ title = '',
65
+ rightButtons: unsortedRightButtons = [],
66
+ leftButtons: unsortedLeftButtons = [],
67
+ hiddenButtons: unsortedHiddenButtons = [],
68
+ className,
69
+ transparent = false,
70
+ drawBehind = false,
71
+ ...rbmProps
72
+ }: TopBarProps) {
72
73
  const [isHiddenMenuOpen, setIsHiddenMenuOpen] = useState(false);
73
74
 
74
75
  if (isHiddenMenuOpen) {
@@ -76,6 +77,9 @@ function TopBar({
76
77
  }
77
78
 
78
79
  const actionSheetRef = useRef<React.ElementRef<typeof ActionSheet>>(null);
80
+ let rightButtons = useMemo(() => unsortedRightButtons.sort((a, b) => (a.order ?? 0) - (b.order ?? 0)), [unsortedRightButtons]);
81
+ const leftButtons = useMemo(() => unsortedLeftButtons.sort((a, b) => (a.order ?? 0) - (b.order ?? 0)), [unsortedLeftButtons]);
82
+ let hiddenButtons = useMemo(() => unsortedHiddenButtons.sort((a, b) => (a.order ?? 0) - (b.order ?? 0)), [unsortedHiddenButtons]);
79
83
 
80
84
  const toggleHiddenMenu = useCallback(() => {
81
85
  setIsHiddenMenuOpen((isOpen) => {
@@ -121,7 +125,7 @@ function TopBar({
121
125
 
122
126
  const rightButtonComponents = getButtonComponents(rightButtons);
123
127
  const leftButtonComponents = getButtonComponents(leftButtons);
124
- const hiddenButtonComponents = getButtonComponents(hiddenButtons.map(({ icon: _, ...button }) => button));
128
+ const hiddenButtonComponents = getButtonComponents(hiddenButtons.map(({icon: _, ...button}) => button));
125
129
 
126
130
  const actions: ActionSheetAction<any>[] = useMemo(
127
131
  () =>
@@ -157,7 +161,7 @@ function TopBar({
157
161
  </Flex>
158
162
  {hiddenButtons.length > 0 && isHiddenMenuOpen ? (
159
163
  <Inline className={styles.hiddenContainer}>
160
- <View aria-hidden={true} className={styles.hiddenCloseCurtain} onClick={toggleHiddenMenu} />
164
+ <View aria-hidden={true} className={styles.hiddenCloseCurtain} onClick={toggleHiddenMenu}/>
161
165
  <View className={styles.hiddenMenu}>{hiddenButtonComponents}</View>
162
166
  </Inline>
163
167
  ) : null}
@@ -30,12 +30,12 @@
30
30
 
31
31
  display: block;
32
32
  padding: 0.5rem 1rem;
33
- color: #0d3efd; // TODO change color
33
+ color: var(--top-bar-button, #0d3efd);
34
34
  text-decoration: none;
35
35
  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out;
36
36
 
37
37
  &.disabled {
38
- color: #6c757d; // TODO change color
38
+ color: var(--top-bar-button-disabled, #6c757d);
39
39
  pointer-events: none;
40
40
  cursor: default;
41
41
  }
@@ -1,5 +1,4 @@
1
1
 
2
-
3
2
  :root {
4
3
  --flavor-basic: #000000;
5
4
  --flavor-constructive: #15803D;
@@ -12,6 +11,10 @@
12
11
 
13
12
  --text-error: var(--flavor-destructive);
14
13
 
14
+ --top-bar-button: #0d3efd;
15
+ --top-bar-button-disabled: #6c757d;
16
+
17
+ --link-color: #0d6efd;
15
18
  }
16
19
 
17
20
  .#{$material} {