@kirill.konshin/utils 0.0.7 → 0.0.9

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 (101) hide show
  1. package/.ctirc +10 -0
  2. package/.storybook/main.ts +21 -0
  3. package/.storybook/preview.ts +17 -0
  4. package/.turbo/turbo-build.log +67 -0
  5. package/.turbo/turbo-test.log +118 -0
  6. package/CHANGELOG.md +12 -0
  7. package/README.md +59 -0
  8. package/builders/.swcrc +18 -0
  9. package/builders/build.config.ts +11 -0
  10. package/builders/bun.build.js +23 -0
  11. package/builders/package.json +209 -0
  12. package/builders/perf.mjs +63 -0
  13. package/builders/rollup.config.mjs +50 -0
  14. package/builders/rslib.config.ts +26 -0
  15. package/builders/tsconfig.json +21 -0
  16. package/builders/tsup.config.ts +20 -0
  17. package/builders/turbo.json +24 -0
  18. package/builders/vite.config.ts +37 -0
  19. package/demo/cache-demo.ts +2 -1
  20. package/package.json +166 -21
  21. package/src/bootstrap/adaptiveContainer.tsx +12 -0
  22. package/src/bootstrap/controls.stories.tsx +62 -0
  23. package/src/bootstrap/controls.tsx +119 -0
  24. package/src/bootstrap/error.stories.tsx +34 -0
  25. package/src/bootstrap/error.tsx +40 -0
  26. package/src/bootstrap/field.stories.tsx +42 -0
  27. package/src/bootstrap/field.tsx +35 -0
  28. package/src/bootstrap/footer.tsx +49 -0
  29. package/src/bootstrap/globalLoading.stories.tsx +25 -0
  30. package/src/bootstrap/globalLoading.tsx +17 -0
  31. package/src/bootstrap/index.ts +12 -0
  32. package/src/bootstrap/loading.stories.tsx +35 -0
  33. package/src/bootstrap/loading.tsx +22 -0
  34. package/src/bootstrap/main.scss +86 -0
  35. package/src/bootstrap/responsiveHelper.tsx +50 -0
  36. package/src/bootstrap/screen.stories.tsx +160 -0
  37. package/src/bootstrap/screen.tsx +144 -0
  38. package/src/bootstrap/toaster.stories.tsx +32 -0
  39. package/src/bootstrap/toaster.tsx +38 -0
  40. package/src/bootstrap/useModal.stories.tsx +51 -0
  41. package/src/bootstrap/useModal.tsx +88 -0
  42. package/src/bootstrap/useWrappedForm.ts +40 -0
  43. package/src/core/cache.test.ts +121 -0
  44. package/src/{cache.ts → core/cache.ts} +29 -10
  45. package/src/electron/README.md +13 -0
  46. package/src/electron/createWindow.ts +234 -0
  47. package/src/electron/index.ts +2 -0
  48. package/src/electron/updater.ts +54 -0
  49. package/src/electron-builder/builder.ts +150 -0
  50. package/src/electron-builder/images.ts +33 -0
  51. package/src/electron-builder/index.ts +2 -0
  52. package/src/mui/README.md +3 -0
  53. package/src/mui/formControlFieldset.stories.tsx +71 -0
  54. package/src/mui/formControlFieldset.tsx +26 -0
  55. package/src/mui/formLabelLegend.tsx +11 -0
  56. package/src/mui/genericControl.tsx +20 -0
  57. package/src/mui/index.ts +4 -0
  58. package/src/mui/readOnly.tsx +6 -0
  59. package/src/next/appLink.tsx +39 -0
  60. package/src/next/index.ts +5 -0
  61. package/src/next/measure.ts +7 -0
  62. package/src/next/noSSR.tsx +17 -0
  63. package/src/next/redirect.tsx +13 -0
  64. package/src/next/useIsInner.ts +13 -0
  65. package/src/react/apiCall.ts +25 -0
  66. package/src/react/form/client.tsx +62 -0
  67. package/src/react/form/form.tsx +109 -0
  68. package/src/react/form/index.ts +2 -0
  69. package/src/react/index.ts +4 -0
  70. package/src/react/useFetch.ts +29 -0
  71. package/src/react/useFetcher.ts +49 -0
  72. package/src/react-native/index.ts +3 -0
  73. package/src/react-native/share.ts +31 -0
  74. package/src/react-native/update.tsx +40 -0
  75. package/src/react-native/useAppState.ts +18 -0
  76. package/src/tailwind/fullpage.tsx +11 -0
  77. package/src/tailwind/index.ts +2 -0
  78. package/src/tailwind/responsiveHelper.tsx +16 -0
  79. package/src-todo/auth0.tsx +177 -0
  80. package/tsconfig.json +15 -18
  81. package/turbo.json +2 -11
  82. package/vite.config.ts +47 -0
  83. package/vite.exports.ts +121 -0
  84. package/build/cache.d.ts +0 -119
  85. package/build/cache.js +0 -176
  86. package/build/errors.d.ts +0 -1
  87. package/build/errors.js +0 -14
  88. package/build/index.d.ts +0 -5
  89. package/build/index.js +0 -5
  90. package/build/measure.d.ts +0 -34
  91. package/build/measure.js +0 -48
  92. package/build/mutex.d.ts +0 -5
  93. package/build/mutex.js +0 -23
  94. package/build/worker.d.ts +0 -83
  95. package/build/worker.js +0 -250
  96. package/tsconfig.tsbuildinfo +0 -1
  97. package/src/{errors.ts → core/errors.ts} +0 -0
  98. package/src/{index.ts → core/index.ts} +1 -1
  99. /package/src/{measure.ts → core/measure.ts} +0 -0
  100. /package/src/{mutex.ts → core/mutex.ts} +0 -0
  101. /package/src/{worker.ts → core/worker.ts} +0 -0
@@ -0,0 +1,50 @@
1
+ import { globSync } from 'glob';
2
+ import dts from 'vite-plugin-dts';
3
+ import resolve from '@rollup/plugin-node-resolve';
4
+ import swc from '@rollup/plugin-swc';
5
+ import commonjs from '@rollup/plugin-commonjs';
6
+ import del from 'rollup-plugin-delete';
7
+ import multi from '@rollup/plugin-multi-entry';
8
+ import { fixExports, excludeGlob, includeGlob, entryGlob, entry, external } from './exports.mjs';
9
+
10
+ //FIXME https://github.com/rollup/rollup/issues/4629 watch spits all files
11
+ //FIXME https://github.com/rollup/rollup/issues/3150 watch spits all files
12
+ //FIXME Multi produces virtual files
13
+
14
+ const dir = 'dist-rollpup';
15
+
16
+ /** @type {import('rollup').RollupOptions} */
17
+ const config = {
18
+ // input: includeGlob, // multi
19
+ // input: entryGlob, // multi
20
+ input: entry, // glob array, seems to be a bit faster
21
+ // ignore: excludeGlob,
22
+ output: {
23
+ dir,
24
+ preserveModules: true,
25
+ preserveModulesRoot: 'src',
26
+ sourcemap: true,
27
+ format: 'es',
28
+ },
29
+ external,
30
+ onwarn(warning, warn) {
31
+ if (warning.code === 'MODULE_LEVEL_DIRECTIVE') return;
32
+ warn(warning);
33
+ },
34
+ plugins: [
35
+ del({ targets: dir, runOnce: true }),
36
+ swc(),
37
+ commonjs(),
38
+ resolve({ extensions: ['.ts', '.tsx'] }),
39
+ dts({ outDir: dir }), // exclude: excludeGlob,
40
+ // multi({ preserveModules: true }),
41
+ {
42
+ name: 'Generate Exports',
43
+ async buildEnd() {
44
+ // await fixExports();
45
+ },
46
+ },
47
+ ],
48
+ };
49
+
50
+ export default config;
@@ -0,0 +1,26 @@
1
+ import { defineConfig } from '@rslib/core';
2
+ import type { RsbuildPlugin } from '@rsbuild/core';
3
+ import { fixExports, generateIndex, includeGlob } from './vite.exports';
4
+
5
+ //FIXME https://github.com/egoist/tsup/issues/945 dts is slow
6
+ //TODO https://api-extractor.com/pages/setup/invoking/ instead of dts
7
+ //TODO https://github.com/egoist/tsup/issues/615 incremental
8
+
9
+ export default defineConfig({
10
+ lib: [{ bundle: false, format: 'esm', dts: { bundle: false } }],
11
+ source: { entry: { index: includeGlob } },
12
+ output: { sourceMap: true, cleanDistPath: true, distPath: { root: 'dist-rslib' } },
13
+ plugins: [
14
+ // {
15
+ // name: 'Generate Index & Exports',
16
+ // setup(api) {
17
+ // api.onBeforeBuild(async () => {
18
+ // await generateIndex();
19
+ // });
20
+ // api.onAfterBuild(async () => {
21
+ // await fixExports();
22
+ // });
23
+ // },
24
+ // } satisfies RsbuildPlugin,
25
+ ],
26
+ });
@@ -0,0 +1,21 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist",
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "declarationDir": "dist",
9
+ "incremental": false,
10
+ "allowJs": true,
11
+ /* Build-specific options */
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "isolatedModules": true,
15
+ "moduleDetection": "force",
16
+ "noEmit": true,
17
+ "jsx": "react-jsx"
18
+ },
19
+ "include": ["src"],
20
+ "exclude": ["**/*.test.ts", "**/*.stories.ts", "**/*.stories.tsx"]
21
+ }
@@ -0,0 +1,20 @@
1
+ import { defineConfig } from 'tsup';
2
+ import { fixExports, formats, includeGlob } from './exports.mjs';
3
+
4
+ //FIXME https://github.com/egoist/tsup/issues/945 dts is slow
5
+ //TODO https://api-extractor.com/pages/setup/invoking/ instead of dts
6
+ //TODO https://github.com/egoist/tsup/issues/615 incremental
7
+
8
+ export default defineConfig({
9
+ outDir: 'dist-tsup',
10
+ entry: [includeGlob],
11
+ format: Object.keys(formats) as any,
12
+ bundle: false,
13
+ splitting: false,
14
+ sourcemap: true,
15
+ clean: true,
16
+ dts: true,
17
+ async onSuccess() {
18
+ // await fixExports();
19
+ },
20
+ });
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://turbo.build/schema.json",
3
+ "extends": ["//"],
4
+ "tasks": {
5
+ "build": {
6
+ "dependsOn": ["^build", "build:vite", "build:exports"]
7
+ },
8
+ "build:exports": {
9
+ "outputs": ["package.json"]
10
+ },
11
+ "build:index": {
12
+ "outputs": ["src/**/*/index.ts"]
13
+ },
14
+ "build:vite": {
15
+ "outputs": ["dist/**/*"],
16
+ "dependsOn": ["build:index"]
17
+ },
18
+ "start": {
19
+ "persistent": true,
20
+ "cache": false,
21
+ "dependsOn": ["build:index", "build:exports"]
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,37 @@
1
+ import { defineConfig } from 'vitest/config';
2
+ import react from '@vitejs/plugin-react';
3
+ import dts from 'vite-plugin-dts';
4
+ import { fixExports, formats, entry, external, distDir } from './exports.mjs';
5
+
6
+ // https://rbardini.com/how-to-build-ts-library-with-vite/
7
+
8
+ export default defineConfig({
9
+ build: {
10
+ ssr: true,
11
+ sourcemap: true,
12
+ outDir: distDir,
13
+ emptyOutDir: true,
14
+ // target: 'esnext',
15
+ lib: {
16
+ entry,
17
+ formats: Object.keys(formats) as any,
18
+ },
19
+ rollupOptions: {
20
+ external,
21
+ output: {
22
+ preserveModules: true,
23
+ preserveModulesRoot: 'src',
24
+ },
25
+ },
26
+ },
27
+ plugins: [
28
+ react(),
29
+ dts(),
30
+ {
31
+ name: 'Generate Exports',
32
+ async buildEnd() {
33
+ // await fixExports();
34
+ },
35
+ },
36
+ ],
37
+ });
@@ -188,7 +188,8 @@ function create<
188
188
  S extends ResSchemaFinal<ResSchema>,
189
189
  M extends keyof S,
190
190
  F extends (key: S[M][0], input: S[M][1]) => S[M][2],
191
- >(schema: S, message: M, responder: F): F { // context: Context<S, M, 'response'>
191
+ >(schema: S, message: M, responder: F): F {
192
+ // context: Context<S, M, 'response'>
192
193
  return responder as any;
193
194
  }
194
195
 
package/package.json CHANGED
@@ -1,36 +1,181 @@
1
1
  {
2
2
  "name": "@kirill.konshin/utils",
3
- "version": "0.0.7",
3
+ "description": "Utilities",
4
+ "version": "0.0.9",
4
5
  "type": "module",
5
- "main": "build/index.js",
6
- "module": "build/index.js",
7
- "types": "build/index.d.ts",
8
- "exports": {
9
- ".": "./build/index.js",
10
- "./cache": "./build/cache.js",
11
- "./errors": "./build/errors.js",
12
- "./measure": "./build/measure.js",
13
- "./mutex": "./build/mutex.js",
14
- "./worker": "./build/worker.js"
15
- },
16
6
  "scripts": {
17
- "clean": "rm -rf .tscache tsconfig.tsbuildinfo build",
18
- "build": "tsc --build",
19
- "start": "yarn build:ts --watch --preserveWatchOutput",
20
- "wait": "wait-on build/index.js"
7
+ "----- BUILD -----": "",
8
+ "clean": "rm -rf dist .tscache tsconfig.tsbuildinfo",
9
+ "build": "vite build",
10
+ "build:index": "cti create ./src/*",
11
+ "build:check-types": "attw --pack .",
12
+ "start": "yarn build --watch",
13
+ "wait": "wait-on ./dist/core/index.mjs",
14
+ "----- TEST -----": "",
15
+ "test": "vitest run --coverage",
16
+ "test:watch": "vitest watch --coverage",
17
+ "----- STORYBOOK -----": "",
18
+ "storybook:start": "storybook dev -p 6006",
19
+ "storybook:build": "storybook build"
21
20
  },
22
21
  "dependencies": {
22
+ "clsx": "^2.1.1",
23
23
  "colors": "^1.4.0",
24
- "many-keys-map": "^2.0.1"
24
+ "expo-file-system": "^18.0.7",
25
+ "expo-sharing": "^13.0.1",
26
+ "expo-updates": "^0.26.13",
27
+ "many-keys-map": "^2.0.1",
28
+ "psd": "^3.4.0",
29
+ "react-hook-form": "^7.54.2",
30
+ "sharp": "^0.33.5"
25
31
  },
26
32
  "devDependencies": {
27
- "@types/lodash": "^4.17.14",
28
- "typescript": "^5.7.3"
33
+ "@arethetypeswrong/cli": "^0.17.3",
34
+ "@chromatic-com/storybook": "^3.2.4",
35
+ "@emotion/react": "^11.14.0",
36
+ "@emotion/styled": "^11.14.0",
37
+ "@mui/material": "^6.4.2",
38
+ "@mui/styled-engine": "^6.4.2",
39
+ "@storybook/addon-essentials": "^8.5.2",
40
+ "@storybook/addon-interactions": "^8.5.2",
41
+ "@storybook/addon-onboarding": "^8.5.2",
42
+ "@storybook/blocks": "^8.5.2",
43
+ "@storybook/react": "^8.5.2",
44
+ "@storybook/react-vite": "^8.5.2",
45
+ "@storybook/test": "^8.5.2",
46
+ "@types/psd": "^3.4.3",
47
+ "@vitest/coverage-v8": "^3.0.4",
48
+ "bootstrap": "^5.3.3",
49
+ "bootstrap-icons": "^1.11.3",
50
+ "create-ts-index": "^1.14.0",
51
+ "electron": "^34.0.1",
52
+ "electron-builder": "^25.1.8",
53
+ "electron-default-menu": "^1.0.2",
54
+ "electron-store": "^10.0.1",
55
+ "electron-updater": "^6.4.0",
56
+ "glob": "^11.0.1",
57
+ "next": "^15.1.6",
58
+ "react-bootstrap": "^2.10.8",
59
+ "react-native": "^0.77.0",
60
+ "sass-embedded": "^1.83.4",
61
+ "storybook": "^8.5.2",
62
+ "tailwindcss": "^4.0.1",
63
+ "vite": "^6.0.11",
64
+ "vitest": "^3.0.4",
65
+ "zod": "^3.24.1"
66
+ },
67
+ "peerDependencies": {
68
+ "@mui/material": "^6",
69
+ "bootstrap": "^5",
70
+ "bootstrap-icons": "^1",
71
+ "electron": "^34",
72
+ "next": "^15",
73
+ "react": "^19",
74
+ "react-bootstrap": "^2",
75
+ "react-native": "^0.77",
76
+ "tailwindcss": "^4",
77
+ "zod": "^3"
78
+ },
79
+ "peerDependenciesMeta": {
80
+ "@mui/material": {
81
+ "optional": true
82
+ },
83
+ "bootstrap": {
84
+ "optional": true
85
+ },
86
+ "bootstrap-icons": {
87
+ "optional": true
88
+ },
89
+ "electron": {
90
+ "optional": true
91
+ },
92
+ "next": {
93
+ "optional": true
94
+ },
95
+ "react": {
96
+ "optional": true
97
+ },
98
+ "react-bootstrap": {
99
+ "optional": true
100
+ },
101
+ "react-native": {
102
+ "optional": true
103
+ },
104
+ "tailwindcss": {
105
+ "optional": true
106
+ },
107
+ "zod": {
108
+ "optional": true
109
+ }
29
110
  },
30
111
  "publishConfig": {
31
112
  "access": "public"
32
113
  },
33
114
  "author": "Kirill Konshin <kirill@konshin.org> (https://konshin.org)",
34
115
  "license": "MIT",
35
- "description": ""
36
- }
116
+ "exports": {
117
+ "./tailwind": {
118
+ "import": {
119
+ "import": "./dist/tailwind/index.mjs",
120
+ "types": "./dist/tailwind/index.d.ts"
121
+ }
122
+ },
123
+ "./react-native": {
124
+ "import": {
125
+ "import": "./dist/react-native/index.mjs",
126
+ "types": "./dist/react-native/index.d.ts"
127
+ }
128
+ },
129
+ "./react": {
130
+ "import": {
131
+ "import": "./dist/react/index.mjs",
132
+ "types": "./dist/react/index.d.ts"
133
+ }
134
+ },
135
+ "./next": {
136
+ "import": {
137
+ "import": "./dist/next/index.mjs",
138
+ "types": "./dist/next/index.d.ts"
139
+ }
140
+ },
141
+ "./mui": {
142
+ "import": {
143
+ "import": "./dist/mui/index.mjs",
144
+ "types": "./dist/mui/index.d.ts"
145
+ }
146
+ },
147
+ "./electron-builder": {
148
+ "import": {
149
+ "import": "./dist/electron-builder/index.mjs",
150
+ "types": "./dist/electron-builder/index.d.ts"
151
+ }
152
+ },
153
+ "./electron": {
154
+ "import": {
155
+ "import": "./dist/electron/index.mjs",
156
+ "types": "./dist/electron/index.d.ts"
157
+ }
158
+ },
159
+ "./core": {
160
+ "import": {
161
+ "import": "./dist/core/index.mjs",
162
+ "types": "./dist/core/index.d.ts"
163
+ }
164
+ },
165
+ "./bootstrap": {
166
+ "import": {
167
+ "import": "./dist/bootstrap/index.mjs",
168
+ "types": "./dist/bootstrap/index.d.ts"
169
+ }
170
+ },
171
+ ".": {
172
+ "import": {
173
+ "import": "./dist/core/index.mjs",
174
+ "types": "./dist/core/index.d.ts"
175
+ }
176
+ }
177
+ },
178
+ "main": "./dist/core/index.mjs",
179
+ "module": "./dist/core/index.mjs",
180
+ "types": "./dist/core/index.d.ts"
181
+ }
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import { Container, ContainerProps } from 'react-bootstrap';
3
+ import { useBreakpoint } from './responsiveHelper';
4
+
5
+ export function AdaptiveContainer({ children, ...props }: ContainerProps) {
6
+ const { isMobile } = useBreakpoint();
7
+ return (
8
+ <Container fluid={isMobile} {...props}>
9
+ {children}
10
+ </Container>
11
+ );
12
+ }
@@ -0,0 +1,62 @@
1
+ import React, { cloneElement, useCallback, useState } from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { fn } from '@storybook/test';
4
+
5
+ import { Select, Control, Range, Checkbox } from './controls';
6
+
7
+ const defaultOptions = { num: 1, bool: false, select: 'bar' };
8
+
9
+ // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
10
+ const meta = {
11
+ title: 'Bootstrap / Controls',
12
+ parameters: {
13
+ layout: 'centered',
14
+ },
15
+ tags: ['autodocs'],
16
+ argTypes: {},
17
+ args: { options: { ...defaultOptions }, setOptions: fn(), defaultOptions: { ...defaultOptions }, name: 'unused' },
18
+ render: function Demo(args) {
19
+ const [options, setOptionsRaw] = useState(args.options);
20
+
21
+ const setOptions = useCallback(
22
+ (options) =>
23
+ setOptionsRaw((currentOptions) => {
24
+ return { ...currentOptions, ...options };
25
+ }),
26
+ [],
27
+ );
28
+
29
+ const controlProps = { options, setOptions, defaultOptions };
30
+
31
+ const { children, name, ...safeArgs } = args;
32
+
33
+ return <div style={{ width: '300px' }}>{cloneElement(children as any, { ...safeArgs, ...controlProps })}</div>;
34
+ },
35
+ } satisfies Meta<typeof Control>;
36
+
37
+ export default meta;
38
+
39
+ type Story = StoryObj<typeof meta>;
40
+
41
+ export const RangeControl: Story = {
42
+ args: {
43
+ children: <Range name="Num" min={0} max={1} />,
44
+ },
45
+ };
46
+
47
+ export const CheckboxControl: Story = {
48
+ args: {
49
+ children: <Checkbox name="Bool" />,
50
+ },
51
+ };
52
+
53
+ export const SelectControl: Story = {
54
+ args: {
55
+ children: (
56
+ <Select name="Select">
57
+ <option value="foo">Foo</option>
58
+ <option value="bar">Bar</option>
59
+ </Select>
60
+ ),
61
+ },
62
+ };
@@ -0,0 +1,119 @@
1
+ import React, { HTMLInputTypeAttribute, memo } from 'react';
2
+
3
+ const lcFirst = (str) => str[0].toLowerCase() + str.substring(1, str.length);
4
+
5
+ const toProperty = (str) => lcFirst(str).split(' ').join('');
6
+
7
+ 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
+ type LabelProps = Pick<ControlProps, 'name' | 'value' | 'defaultValue'> & { reset: () => void };
26
+
27
+ const Label = memo<LabelProps>(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 = memo<ControlProps>(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) => 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) => 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 = memo<RangeProps>(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) => parseFloat(e.target.value),
98
+ }}
99
+ {...props}
100
+ />
101
+ );
102
+ });
103
+
104
+ export const Checkbox = memo<ControlProps>(function Checkbox(props) {
105
+ return (
106
+ <Control
107
+ inputProps={{ type: 'checkbox', property: 'checked', valueExtractor: (e) => e.target.checked }}
108
+ {...props}
109
+ />
110
+ );
111
+ });
112
+
113
+ export const Select = memo<ControlProps>(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 = {
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
+ } satisfies Meta<typeof ErrorAlert>;
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'),
33
+ },
34
+ };
@@ -0,0 +1,40 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import { Alert, AlertProps, Button, ButtonProps } from 'react-bootstrap';
5
+
6
+ //TODO Create MUI-specific?
7
+ export function ErrorAlert({
8
+ onRetry,
9
+ children,
10
+ buttonProps,
11
+ ...props
12
+ }: {
13
+ children?: Error | string | any;
14
+ onRetry?: () => any;
15
+ buttonProps?: ButtonProps;
16
+ }) {
17
+ if (!children) return null;
18
+
19
+ return (
20
+ <Alert variant="danger" {...props}>
21
+ <div>{children.message || children.toString()}</div>
22
+ {onRetry && (
23
+ <div>
24
+ <Button
25
+ variant="link"
26
+ className="alert-link"
27
+ {...buttonProps}
28
+ onClick={(e) => {
29
+ e.preventDefault();
30
+ e.stopPropagation();
31
+ onRetry();
32
+ }}
33
+ >
34
+ Retry
35
+ </Button>
36
+ </div>
37
+ )}
38
+ </Alert>
39
+ );
40
+ }