@kirill.konshin/bootstrap 0.0.1

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.
Files changed (98) hide show
  1. package/.ctirc +11 -0
  2. package/.storybook/main.ts +2 -0
  3. package/.storybook/preview.ts +2 -0
  4. package/.turbo/turbo-build.log +24 -0
  5. package/dist/adaptiveContainer.d.ts +4 -0
  6. package/dist/adaptiveContainer.d.ts.map +1 -0
  7. package/dist/adaptiveContainer.js +11 -0
  8. package/dist/adaptiveContainer.js.map +1 -0
  9. package/dist/controls.d.ts +38 -0
  10. package/dist/controls.d.ts.map +1 -0
  11. package/dist/controls.js +78 -0
  12. package/dist/controls.js.map +1 -0
  13. package/dist/controls.stories.d.ts +9 -0
  14. package/dist/controls.stories.d.ts.map +1 -0
  15. package/dist/error.d.ts +9 -0
  16. package/dist/error.d.ts.map +1 -0
  17. package/dist/error.js +27 -0
  18. package/dist/error.js.map +1 -0
  19. package/dist/error.stories.d.ts +8 -0
  20. package/dist/error.stories.d.ts.map +1 -0
  21. package/dist/field.d.ts +11 -0
  22. package/dist/field.d.ts.map +1 -0
  23. package/dist/field.js +29 -0
  24. package/dist/field.js.map +1 -0
  25. package/dist/field.stories.d.ts +8 -0
  26. package/dist/field.stories.d.ts.map +1 -0
  27. package/dist/footer.d.ts +10 -0
  28. package/dist/footer.d.ts.map +1 -0
  29. package/dist/footer.js +45 -0
  30. package/dist/footer.js.map +1 -0
  31. package/dist/globalLoading.d.ts +6 -0
  32. package/dist/globalLoading.d.ts.map +1 -0
  33. package/dist/globalLoading.js +10 -0
  34. package/dist/globalLoading.js.map +1 -0
  35. package/dist/globalLoading.stories.d.ts +7 -0
  36. package/dist/globalLoading.stories.d.ts.map +1 -0
  37. package/dist/index.d.ts +13 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +35 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/loading.d.ts +10 -0
  42. package/dist/loading.d.ts.map +1 -0
  43. package/dist/loading.js +19 -0
  44. package/dist/loading.js.map +1 -0
  45. package/dist/loading.stories.d.ts +9 -0
  46. package/dist/loading.stories.d.ts.map +1 -0
  47. package/dist/responsiveHelper.d.ts +8 -0
  48. package/dist/responsiveHelper.d.ts.map +1 -0
  49. package/dist/responsiveHelper.js +46 -0
  50. package/dist/responsiveHelper.js.map +1 -0
  51. package/dist/screen.d.ts +22 -0
  52. package/dist/screen.d.ts.map +1 -0
  53. package/dist/screen.js +80 -0
  54. package/dist/screen.js.map +1 -0
  55. package/dist/screen.stories.d.ts +14 -0
  56. package/dist/screen.stories.d.ts.map +1 -0
  57. package/dist/toaster.d.ts +11 -0
  58. package/dist/toaster.d.ts.map +1 -0
  59. package/dist/toaster.js +34 -0
  60. package/dist/toaster.js.map +1 -0
  61. package/dist/toaster.stories.d.ts +8 -0
  62. package/dist/toaster.stories.d.ts.map +1 -0
  63. package/dist/useModal.d.ts +23 -0
  64. package/dist/useModal.d.ts.map +1 -0
  65. package/dist/useModal.js +50 -0
  66. package/dist/useModal.js.map +1 -0
  67. package/dist/useModal.stories.d.ts +9 -0
  68. package/dist/useModal.stories.d.ts.map +1 -0
  69. package/dist/useWrappedForm.d.ts +14 -0
  70. package/dist/useWrappedForm.d.ts.map +1 -0
  71. package/dist/useWrappedForm.js +32 -0
  72. package/dist/useWrappedForm.js.map +1 -0
  73. package/package.json +68 -0
  74. package/src/adaptiveContainer.tsx +12 -0
  75. package/src/controls.stories.tsx +62 -0
  76. package/src/controls.tsx +119 -0
  77. package/src/error.stories.tsx +34 -0
  78. package/src/error.tsx +37 -0
  79. package/src/field.stories.tsx +42 -0
  80. package/src/field.tsx +37 -0
  81. package/src/footer.tsx +61 -0
  82. package/src/globalLoading.stories.tsx +25 -0
  83. package/src/globalLoading.tsx +19 -0
  84. package/src/index.ts +12 -0
  85. package/src/loading.stories.tsx +35 -0
  86. package/src/loading.tsx +24 -0
  87. package/src/main.scss +86 -0
  88. package/src/responsiveHelper.tsx +55 -0
  89. package/src/screen.stories.tsx +160 -0
  90. package/src/screen.tsx +146 -0
  91. package/src/toaster.stories.tsx +32 -0
  92. package/src/toaster.tsx +40 -0
  93. package/src/useModal.stories.tsx +51 -0
  94. package/src/useModal.tsx +93 -0
  95. package/src/useWrappedForm.ts +42 -0
  96. package/tsconfig.json +10 -0
  97. package/turbo.json +10 -0
  98. package/vite.config.ts +2 -0
@@ -0,0 +1,119 @@
1
+ import React, { HTMLInputTypeAttribute, memo, FC } from 'react';
2
+
3
+ const lcFirst = (str: string) => str[0].toLowerCase() + str.substring(1, str.length);
4
+
5
+ const toProperty = (str: string) => lcFirst(str).split(' ').join('');
6
+
7
+ export interface ControlProps {
8
+ value?: any;
9
+ setValue?: (v: any) => void;
10
+ defaultValue?: any;
11
+ options?: { [key: string]: any };
12
+ setOptions?: (v: { [key: string]: any }) => void;
13
+ defaultOptions?: { [key: string]: any };
14
+ name: string;
15
+ children?: React.ReactNode;
16
+ inputProps?: {
17
+ type: HTMLInputTypeAttribute;
18
+ property?: string;
19
+ valueExtractor?: (e: any) => any;
20
+ className?: string;
21
+ } & any;
22
+ hideValue?: boolean;
23
+ }
24
+
25
+ export type LabelProps = Pick<ControlProps, 'name' | 'value' | 'defaultValue'> & { reset: () => void };
26
+
27
+ export const Label: FC<LabelProps> = memo(function Label({ name, reset, value, defaultValue }) {
28
+ return (
29
+ <label className="flex-grow-1 mb-0 d-flex align-items-center justify-content-start gap-2">
30
+ {name}
31
+ {value !== defaultValue && (
32
+ <>
33
+ <a href="javascript: void(0)" onClick={reset}>
34
+ <small>reset</small>
35
+ </a>
36
+ </>
37
+ )}
38
+ </label>
39
+ );
40
+ });
41
+
42
+ export const Control: FC<ControlProps> = memo(function Control({
43
+ value = null,
44
+ setValue,
45
+ defaultValue = null,
46
+ options = {},
47
+ setOptions,
48
+ defaultOptions = {},
49
+ name,
50
+ inputProps: { Tag = 'input', property = 'value', valueExtractor = (e: any) => e.target.value, ...inputProps } = {},
51
+ hideValue = false,
52
+ children,
53
+ }) {
54
+ const prop = toProperty(name);
55
+
56
+ defaultValue = defaultValue || defaultOptions[prop];
57
+ value = value || options?.[prop];
58
+ setValue = setValue || ((v) => setOptions?.({ [prop]: v }));
59
+
60
+ const reset = () => setValue(defaultValue);
61
+
62
+ return (
63
+ <div className="d-flex align-items-center gap-2">
64
+ <Label name={name} reset={reset} value={value} defaultValue={defaultValue} />
65
+ <Tag
66
+ {...inputProps}
67
+ {...{ [property]: value || '' }} // null is needed to make it always controlled
68
+ className={`${value === defaultValue ? 'opacity-75' : ''} ${inputProps.className}`}
69
+ onChange={(e: Event) => setValue(valueExtractor(e))}
70
+ >
71
+ {children}
72
+ </Tag>
73
+ {!hideValue && (
74
+ <span onDoubleClick={reset} className="text-start" style={{ width: '20px' }}>
75
+ {value}
76
+ </span>
77
+ )}
78
+ </div>
79
+ );
80
+ });
81
+
82
+ export interface RangeProps extends ControlProps {
83
+ min: number;
84
+ max: number;
85
+ step?: number;
86
+ }
87
+
88
+ export const Range: FC<RangeProps> = memo(function Range({ min, max, step = 0.1, ...props }) {
89
+ return (
90
+ <Control
91
+ inputProps={{
92
+ type: 'range',
93
+ min,
94
+ max,
95
+ step,
96
+ className: 'form-control-range',
97
+ valueExtractor: (e: any) => parseFloat(e.target.value),
98
+ }}
99
+ {...props}
100
+ />
101
+ );
102
+ });
103
+
104
+ export const Checkbox: FC<ControlProps> = memo(function Checkbox(props) {
105
+ return (
106
+ <Control
107
+ inputProps={{ type: 'checkbox', property: 'checked', valueExtractor: (e: any) => e.target.checked }}
108
+ {...props}
109
+ />
110
+ );
111
+ });
112
+
113
+ export const Select: FC<ControlProps> = memo(function Select({ children, ...props }) {
114
+ return (
115
+ <Control inputProps={{ Tag: 'select', className: 'form-select' }} {...props} hideValue={true}>
116
+ {children}
117
+ </Control>
118
+ );
119
+ });
@@ -0,0 +1,34 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { fn } from '@storybook/test';
3
+
4
+ import { ErrorAlert } from './error';
5
+
6
+ // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
7
+ const meta: Meta<typeof ErrorAlert> = {
8
+ title: 'Bootstrap / ErrorAlert',
9
+ component: ErrorAlert,
10
+ parameters: {
11
+ layout: 'centered',
12
+ },
13
+ tags: ['autodocs'],
14
+ argTypes: {
15
+ // backgroundColor: { control: 'color' },
16
+ children: { control: 'text' },
17
+ },
18
+ args: { onRetry: fn() },
19
+ };
20
+
21
+ export default meta;
22
+ type Story = StoryObj<typeof meta>;
23
+
24
+ export const String: Story = {
25
+ args: {
26
+ children: 'An error occurred',
27
+ },
28
+ };
29
+
30
+ export const ErrorObject: Story = {
31
+ args: {
32
+ children: new Error('An error occurred') as any,
33
+ },
34
+ };
package/src/error.tsx ADDED
@@ -0,0 +1,37 @@
1
+ 'use client';
2
+
3
+ import React, { FC } from 'react';
4
+ import { Alert, AlertProps, Button, ButtonProps } from 'react-bootstrap';
5
+
6
+ export type ErrorAlertProps = {
7
+ children?: Error | string | any;
8
+ onRetry?: () => any;
9
+ buttonProps?: ButtonProps;
10
+ };
11
+
12
+ //TODO Create MUI-specific?
13
+ export const ErrorAlert: FC<ErrorAlertProps> = function ErrorAlert({ onRetry, children, buttonProps, ...props }) {
14
+ if (!children) return null;
15
+
16
+ return (
17
+ <Alert variant="danger" {...props}>
18
+ <div>{children.message || children.toString()}</div>
19
+ {onRetry && (
20
+ <div>
21
+ <Button
22
+ variant="link"
23
+ className="alert-link"
24
+ {...buttonProps}
25
+ onClick={(e) => {
26
+ e.preventDefault();
27
+ e.stopPropagation();
28
+ onRetry();
29
+ }}
30
+ >
31
+ Retry
32
+ </Button>
33
+ </div>
34
+ )}
35
+ </Alert>
36
+ );
37
+ };
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { fn } from '@storybook/test';
4
+
5
+ import { FieldGroup } from './field';
6
+ import { FormControl } from 'react-bootstrap';
7
+
8
+ // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
9
+ const meta: Meta<typeof FieldGroup> = {
10
+ title: 'Bootstrap / FieldGroup',
11
+ component: FieldGroup,
12
+ parameters: {
13
+ layout: 'centered',
14
+ },
15
+ tags: ['autodocs'],
16
+ argTypes: {
17
+ text: { control: 'text' },
18
+ label: { control: 'text' },
19
+ horizontal: { control: 'check' },
20
+ },
21
+ args: { onClick: fn() },
22
+ };
23
+
24
+ export default meta;
25
+
26
+ type Story = StoryObj<typeof meta>;
27
+
28
+ export const Vertical: Story = {
29
+ args: {
30
+ label: 'Label',
31
+ children: <FormControl placeholder="Input" />,
32
+ text: 'Text',
33
+ },
34
+ };
35
+
36
+ export const Horizontal: Story = {
37
+ args: {
38
+ label: 'Label',
39
+ children: <FormControl placeholder="Input" />,
40
+ horizontal: true,
41
+ },
42
+ };
package/src/field.tsx ADDED
@@ -0,0 +1,37 @@
1
+ import React, { FC } from 'react';
2
+ import { Form, FormGroupProps, FormLabelProps, FormTextProps } from 'react-bootstrap';
3
+
4
+ export type FieldGroupProps = FormGroupProps & {
5
+ label?: any;
6
+ labelProps?: FormLabelProps;
7
+ text?: any;
8
+ textProps?: FormTextProps;
9
+ horizontal?: boolean;
10
+ };
11
+
12
+ export const FieldGroup: FC<FieldGroupProps> = function FieldGroup({
13
+ label,
14
+ labelProps = {},
15
+ text,
16
+ textProps = {},
17
+ horizontal,
18
+ children,
19
+ ...props
20
+ }) {
21
+ if (horizontal) {
22
+ props.className = 'hstack gap-3 ' + (props.className || '');
23
+ labelProps.className = 'm-0 ' + (labelProps.className || '');
24
+ }
25
+
26
+ if (text && horizontal) {
27
+ throw new Error('Cannot use text and horizontal together');
28
+ }
29
+
30
+ return (
31
+ <Form.Group {...props}>
32
+ {label && <Form.Label {...labelProps}>{label}</Form.Label>}
33
+ {children}
34
+ {text && !horizontal && <Form.Text {...textProps}>{text}</Form.Text>}{' '}
35
+ </Form.Group>
36
+ );
37
+ };
package/src/footer.tsx ADDED
@@ -0,0 +1,61 @@
1
+ import React, { FC } from 'react';
2
+ import { Nav, Navbar } from 'react-bootstrap';
3
+ import { useBreakpoint } from './responsiveHelper';
4
+ import { AdaptiveContainer } from './adaptiveContainer';
5
+
6
+ export type FooterNavItemProps = {
7
+ href: any;
8
+ icon: any;
9
+ active?: boolean | undefined;
10
+ children?: string | undefined;
11
+ };
12
+
13
+ export const FooterNavItem: FC<FooterNavItemProps> = function FooterNavItem({
14
+ href,
15
+ icon,
16
+ active = false,
17
+ children = '',
18
+ }) {
19
+ // const pathname = usePathname();
20
+
21
+ const isFilepath = icon.includes('.');
22
+ let iconElement;
23
+
24
+ if (isFilepath) {
25
+ iconElement = <img src={icon} alt="icon" className="me-lg-2" />;
26
+ } else {
27
+ iconElement = <span className={`lead me-lg-2 ${icon}`} />;
28
+ }
29
+
30
+ // const active = isActivePath(href, pathname);
31
+
32
+ const link = (
33
+ <Nav.Link
34
+ // as={Link}
35
+ href={href}
36
+ // active={active} // has no effect when not pills or tabs
37
+ className={`px-0 px-lg-3 d-flex align-items-center flex-column flex-lg-row ${active ? 'text-primary' : ''}`}
38
+ >
39
+ {iconElement}
40
+ {children}
41
+ </Nav.Link>
42
+ );
43
+
44
+ return <Nav.Item style={{ flex: 1 }}>{link}</Nav.Item>;
45
+ };
46
+
47
+ export const Footer: FC<any> = function Footer({ children }) {
48
+ const { isDesktop } = useBreakpoint();
49
+
50
+ return (
51
+ // border-bottom
52
+ <div className={isDesktop ? '' : 'border-top'}>
53
+ <Navbar className={isDesktop ? '' : 'py-0'}>
54
+ <AdaptiveContainer>
55
+ {isDesktop && <Navbar.Brand href="/">Le Brand</Navbar.Brand>}
56
+ <Nav className={isDesktop ? 'me-auto' : 'justify-content-center flex-nowrap w-100'}>{children}</Nav>
57
+ </AdaptiveContainer>
58
+ </Navbar>
59
+ </div>
60
+ );
61
+ };
@@ -0,0 +1,25 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+
3
+ import { GlobalLoading } from './globalLoading';
4
+
5
+ const meta: Meta<typeof GlobalLoading> = {
6
+ title: 'Bootstrap / GlobalLoading',
7
+ component: GlobalLoading,
8
+ parameters: {
9
+ layout: 'centered',
10
+ },
11
+ tags: ['autodocs'],
12
+ argTypes: {
13
+ loading: { control: 'check' },
14
+ },
15
+ };
16
+
17
+ export default meta;
18
+
19
+ type Story = StoryObj<typeof meta>;
20
+
21
+ export const Default: Story = {
22
+ args: {
23
+ loading: true,
24
+ },
25
+ };
@@ -0,0 +1,19 @@
1
+ import React, { FC } from 'react';
2
+ // import { useLoading, useLoadingTimeout } from '../redux/loadingSlice';
3
+ import { Loading } from './loading';
4
+
5
+ export type GlobalLoadingProps = { loading: boolean };
6
+
7
+ export const GlobalLoading: FC<GlobalLoadingProps> = function GlobalLoading({ loading }) {
8
+ // const { loading } = useLoading();
9
+
10
+ // useLoadingTimeout(15000); // escape latch if something is stuck
11
+
12
+ if (!loading) return null;
13
+
14
+ return (
15
+ <div className="bg-white position-absolute top-0 start-0 w-100 h-100 z-3 d-flex align-items-center justify-content-center">
16
+ <Loading />
17
+ </div>
18
+ );
19
+ };
package/src/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ export * from './adaptiveContainer';
2
+ export * from './controls';
3
+ export * from './error';
4
+ export * from './field';
5
+ export * from './footer';
6
+ export * from './globalLoading';
7
+ export * from './loading';
8
+ export * from './responsiveHelper';
9
+ export * from './screen';
10
+ export * from './toaster';
11
+ export * from './useModal';
12
+ export * from './useWrappedForm';
@@ -0,0 +1,35 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+
3
+ import { Loading } from './loading';
4
+
5
+ const meta: Meta<typeof Loading> = {
6
+ title: 'Bootstrap / Loading',
7
+ component: Loading,
8
+ parameters: {
9
+ layout: 'centered',
10
+ },
11
+ tags: ['autodocs'],
12
+ argTypes: {
13
+ children: { control: 'text' },
14
+ show: { control: 'check' },
15
+ size: { control: 'select', options: ['sm', 'md'] },
16
+ },
17
+ };
18
+
19
+ export default meta;
20
+
21
+ type Story = StoryObj<typeof meta>;
22
+
23
+ export const Default: Story = {};
24
+
25
+ export const Children: Story = {
26
+ args: {
27
+ children: 'Custom Loading...',
28
+ },
29
+ };
30
+
31
+ export const Size: Story = {
32
+ args: {
33
+ size: 'sm',
34
+ },
35
+ };
@@ -0,0 +1,24 @@
1
+ import { Spinner, SpinnerProps, Stack } from 'react-bootstrap';
2
+ import React, { FC, memo } from 'react';
3
+
4
+ export type LoadingProps = {
5
+ children?: any;
6
+ show?: boolean;
7
+ className?: string;
8
+ size?: SpinnerProps['size'];
9
+ };
10
+
11
+ export const Loading: FC<LoadingProps> = memo(function Loading({
12
+ children = 'Loading...' as any,
13
+ show = true,
14
+ className = '',
15
+ size,
16
+ }) {
17
+ if (!show) return null;
18
+ return (
19
+ <Stack direction="horizontal" gap={3} className={`align-self-auto ${className}`}>
20
+ <Spinner animation="border" role="status" size={size as any} />
21
+ <span>{children}</span>
22
+ </Stack>
23
+ );
24
+ });
package/src/main.scss ADDED
@@ -0,0 +1,86 @@
1
+ $primary: darken(#fd7e14, 6%) !default; // in order to accomodate MCR=3
2
+ $secondary: #0d6efd !default;
3
+ $danger: #dc3545 !default;
4
+ $warning: #fd7e14 !default;
5
+ $success: #198754 !default;
6
+
7
+ $theme-colors: (
8
+ 'primary': $primary,
9
+ 'secondary': $secondary,
10
+ 'success': $success,
11
+ // "info": $info,
12
+ 'warning': $warning,
13
+ 'danger': $danger,
14
+ // non-bootstrap colors
15
+ 'light': #dddddd,
16
+ 'dark': #2c2414,
17
+ );
18
+
19
+ $min-contrast-ratio: 3 !default; // 4.5 produces black on orange @see https://stackoverflow.com/a/68303393/5125659
20
+
21
+ @import 'bootstrap/scss/functions.scss';
22
+ @import 'bootstrap/scss/variables.scss';
23
+ @import 'bootstrap/scss/mixins.scss';
24
+ @import 'bootstrap/scss/bootstrap.scss';
25
+
26
+ // Nav
27
+
28
+ .btn-nav {
29
+ @extend .btn-outline-primary; // just vars to control styling
30
+ }
31
+
32
+ .main-nav .btn-nav {
33
+ border-width: 0;
34
+
35
+ @include media-breakpoint-down(md) {
36
+ --bs-btn-active-color: var(--bs-primary);
37
+ --bs-btn-hover-color: var(--bs-primary);
38
+ background: none !important;
39
+ padding: 0;
40
+ text-decoration: none;
41
+ font-weight: 500;
42
+ }
43
+
44
+ &.disabled {
45
+ color: var(--bs-gray-600);
46
+ }
47
+ }
48
+
49
+ // Dropdowns
50
+
51
+ .dropdown-toggle-no-arrow::after,
52
+ .dropdown-toggle-no-arrow > .dropdown-toggle::after {
53
+ display: none !important;
54
+ }
55
+
56
+ .dropdown > button {
57
+ display: block; // fixes small padding on dropdowns
58
+ }
59
+
60
+ // Tables
61
+
62
+ .table {
63
+ //fixes table borders & margins
64
+ margin-bottom: 0;
65
+
66
+ & > :not(caption) > * > * {
67
+ background: none;
68
+ }
69
+
70
+ & > :not(caption) > tr:last-of-type > * {
71
+ border-bottom: none;
72
+ }
73
+ }
74
+
75
+ // Badges
76
+
77
+ .badge {
78
+ --bs-badge-font-weight: normal;
79
+ }
80
+
81
+ // Icons
82
+
83
+ .icon-thick {
84
+ webkit-text-stroke: 1px;
85
+ text-stroke: 1px;
86
+ }
@@ -0,0 +1,55 @@
1
+ 'use client';
2
+
3
+ import React, { FC, useEffect, useState } from 'react';
4
+
5
+ export function useBreakpoint(): {
6
+ breakpoint: string;
7
+ isMobile: boolean;
8
+ isDesktop: boolean;
9
+ } {
10
+ const [breakpoint, setBreakpoint] = useState('xs');
11
+
12
+ useEffect(() => {
13
+ const updateBreakpoint = () => {
14
+ const width = window.innerWidth;
15
+ if (width < 576) {
16
+ setBreakpoint('xs');
17
+ } else if (width < 768) {
18
+ setBreakpoint('sm');
19
+ } else if (width < 992) {
20
+ setBreakpoint('md');
21
+ } else if (width < 1200) {
22
+ setBreakpoint('lg');
23
+ } else if (width < 1400) {
24
+ setBreakpoint('xl');
25
+ } else {
26
+ setBreakpoint('xxl');
27
+ }
28
+ };
29
+
30
+ updateBreakpoint();
31
+
32
+ window.addEventListener('resize', updateBreakpoint);
33
+
34
+ return () => {
35
+ window.removeEventListener('resize', updateBreakpoint);
36
+ };
37
+ }, []);
38
+
39
+ const isMobile = ['xs', 'sm', 'md'].includes(breakpoint);
40
+
41
+ return { breakpoint, isMobile, isDesktop: !isMobile };
42
+ }
43
+
44
+ export const ResponsiveHelperBS: FC = function ResponsiveHelperBS() {
45
+ return (
46
+ <span className="small position-fixed bottom-0 z-3" style={{ fontSize: '9px', right: 1, lineHeight: 1 }}>
47
+ <small className="d-block d-sm-none">XS</small>
48
+ <small className="d-none d-sm-block d-md-none">SM</small>
49
+ <small className="d-none d-md-block d-lg-none">MD</small>
50
+ <small className="d-none d-lg-block d-xl-none">LG</small>
51
+ <small className="d-none d-xl-block d-xxl-none">XL</small>
52
+ <small className="d-none d-xxl-block">XXL</small>
53
+ </span>
54
+ );
55
+ };