@iobroker/gui-components 10.3.14 → 10.3.16
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/README.md +12 -1
- package/build/Components/AnimatedIcons/IconBell.d.ts +21 -0
- package/build/Components/AnimatedIcons/IconBell.js +50 -0
- package/build/Components/AnimatedIcons/IconBell.js.map +1 -0
- package/build/Components/AnimatedIcons/IconConnectionState.d.ts +19 -0
- package/build/Components/AnimatedIcons/IconConnectionState.js +25 -0
- package/build/Components/AnimatedIcons/IconConnectionState.js.map +1 -0
- package/build/Components/AnimatedIcons/IconExpertMode.d.ts +22 -0
- package/build/Components/AnimatedIcons/IconExpertMode.js +47 -0
- package/build/Components/AnimatedIcons/IconExpertMode.js.map +1 -0
- package/build/Components/AnimatedIcons/IconFolder.d.ts +22 -0
- package/build/Components/AnimatedIcons/IconFolder.js +29 -0
- package/build/Components/AnimatedIcons/IconFolder.js.map +1 -0
- package/build/Components/AnimatedIcons/IconMenuArrow.d.ts +23 -0
- package/build/Components/AnimatedIcons/IconMenuArrow.js +26 -0
- package/build/Components/AnimatedIcons/IconMenuArrow.js.map +1 -0
- package/build/Components/AnimatedIcons/IconPlayPause.d.ts +23 -0
- package/build/Components/AnimatedIcons/IconPlayPause.js +25 -0
- package/build/Components/AnimatedIcons/IconPlayPause.js.map +1 -0
- package/build/Components/AnimatedIcons/IconRefresh.d.ts +23 -0
- package/build/Components/AnimatedIcons/IconRefresh.js +86 -0
- package/build/Components/AnimatedIcons/IconRefresh.js.map +1 -0
- package/build/Components/AnimatedIcons/IconResult.d.ts +21 -0
- package/build/Components/AnimatedIcons/IconResult.js +36 -0
- package/build/Components/AnimatedIcons/IconResult.js.map +1 -0
- package/build/Components/AnimatedIcons/IconStatusLed.d.ts +25 -0
- package/build/Components/AnimatedIcons/IconStatusLed.js +58 -0
- package/build/Components/AnimatedIcons/IconStatusLed.js.map +1 -0
- package/build/Components/AnimatedIcons/IconTrash.d.ts +20 -0
- package/build/Components/AnimatedIcons/IconTrash.js +23 -0
- package/build/Components/AnimatedIcons/IconTrash.js.map +1 -0
- package/build/Components/AnimatedIcons/common.d.ts +31 -0
- package/build/Components/AnimatedIcons/common.js +164 -0
- package/build/Components/AnimatedIcons/common.js.map +1 -0
- package/build/Components/FileBrowser.js +4 -5
- package/build/Components/FileBrowser.js.map +1 -1
- package/build/Components/Loaders/Community.d.ts +13 -2
- package/build/Components/Loaders/Community.js +176 -243
- package/build/Components/Loaders/Community.js.map +1 -1
- package/build/Components/Loaders/CommunityLegacy.d.ts +32 -0
- package/build/Components/Loaders/CommunityLegacy.js +266 -0
- package/build/Components/Loaders/CommunityLegacy.js.map +1 -0
- package/build/Components/ObjectBrowser/ObjectBrowserClass.d.ts +18 -0
- package/build/Components/ObjectBrowser/ObjectBrowserClass.js +131 -3
- package/build/Components/ObjectBrowser/ObjectBrowserClass.js.map +1 -1
- package/build/Components/ObjectBrowser/dialogs.js +2 -2
- package/build/Components/ObjectBrowser/dialogs.js.map +1 -1
- package/build/Components/ObjectBrowser/renderLeaf.js +48 -8
- package/build/Components/ObjectBrowser/renderLeaf.js.map +1 -1
- package/build/Components/ObjectBrowser/rowAnimation.d.ts +9 -0
- package/build/Components/ObjectBrowser/rowAnimation.js +122 -0
- package/build/Components/ObjectBrowser/rowAnimation.js.map +1 -0
- package/build/Components/ObjectBrowser/toolbar.js +5 -4
- package/build/Components/ObjectBrowser/toolbar.js.map +1 -1
- package/build/Components/ObjectBrowser/utils.d.ts +2 -0
- package/build/Components/ObjectBrowser/utils.js +2 -0
- package/build/Components/ObjectBrowser/utils.js.map +1 -1
- package/build/Components/SaveCloseButtons.d.ts +12 -1
- package/build/Components/SaveCloseButtons.js +38 -2
- package/build/Components/SaveCloseButtons.js.map +1 -1
- package/build/index.d.ts +13 -0
- package/build/index.js +12 -0
- package/build/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IconResult.js","sourceRoot":"./src/","sources":["Components/AnimatedIcons/IconResult.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAA0B,SAAS,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AASpF;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,KAAsB;IAC7C,qBAAqB,EAAE,CAAC;IACxB,IAAI,IAAuB,CAAC;IAC5B,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC3B,IAAI,GAAG,CACH,8BACI,SAAS,EAAC,kBAAkB,EAC5B,CAAC,EAAC,qBAAqB,EACvB,UAAU,EAAE,CAAC,GACf,CACL,CAAC;IACN,CAAC;SAAM,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAClC,IAAI,GAAG,CACH;YACI,8BACI,SAAS,EAAC,kBAAkB,EAC5B,CAAC,EAAC,WAAW,EACb,UAAU,EAAE,CAAC,GACf;YACF,8BACI,SAAS,EAAC,mBAAmB,EAC7B,CAAC,EAAC,aAAa,EACf,UAAU,EAAE,CAAC,GACf,CACH,CACN,CAAC;IACN,CAAC;SAAM,CAAC;QACJ,IAAI,GAAG,CACH;YACI,8BACI,SAAS,EAAC,kBAAkB,EAC5B,CAAC,EAAC,kBAAkB,EACpB,UAAU,EAAE,CAAC,GACf;YACF,8BACI,SAAS,EAAC,mBAAmB,EAC7B,CAAC,EAAC,oBAAoB,EACtB,UAAU,EAAE,CAAC,GACf,CACH,CACN,CAAC;IACN,CAAC;IAED,OAAO,CACH,iCAAU,SAAS,CAAC,KAAK,EAAE,2BAA2B,KAAK,CAAC,IAAI,EAAE,CAAC;QAC/D,6BACI,GAAG,EAAE,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,MAAM,IAAI,EAAE,EAAE,EAC1C,OAAO,EAAC,WAAW;YAEnB,gCACI,SAAS,EAAC,oBAAoB,EAC9B,EAAE,EAAC,IAAI,EACP,EAAE,EAAC,IAAI,EACP,CAAC,EAAC,IAAI,EACN,UAAU,EAAE,CAAC,GACf;YACD,IAAI,CACH,CACH,CACV,CAAC;AACN,CAAC","sourcesContent":["/**\n * Copyright 2026 Denis Haev (bluefox) <dogafox@gmail.com>\n *\n * MIT License\n */\nimport React from 'react';\nimport { type AnimatedIconProps, rootProps, useAnimatedIconStyles } from './common';\n\nexport interface IconResultProps extends AnimatedIconProps {\n /** success: check mark (green), warning: exclamation mark (orange), error: cross (red). The color can be changed with `style.color`. */\n type: 'success' | 'warning' | 'error';\n /** Every change of this value draws the icon again, e.g. a counter of the save clicks */\n replay?: string | number;\n}\n\n/**\n * Result of an action: a circle and the mark draw themselves, a warning or an error shakes once.\n *\n * ```tsx\n * {saved ? <IconResult type=\"success\" replay={saveCount} /> : null}\n * ```\n */\nexport function IconResult(props: IconResultProps): React.JSX.Element {\n useAnimatedIconStyles();\n let mark: React.JSX.Element;\n if (props.type === 'success') {\n mark = (\n <path\n className=\"iob-ani-res-mark\"\n d=\"M7.5 12.5l3 3 6-6.5\"\n pathLength={1}\n />\n );\n } else if (props.type === 'warning') {\n mark = (\n <>\n <path\n className=\"iob-ani-res-mark\"\n d=\"M12 7.5v5\"\n pathLength={1}\n />\n <path\n className=\"iob-ani-res-mark2\"\n d=\"M12 16.4v.1\"\n pathLength={1}\n />\n </>\n );\n } else {\n mark = (\n <>\n <path\n className=\"iob-ani-res-mark\"\n d=\"M8.7 8.7l6.6 6.6\"\n pathLength={1}\n />\n <path\n className=\"iob-ani-res-mark2\"\n d=\"M15.3 8.7l-6.6 6.6\"\n pathLength={1}\n />\n </>\n );\n }\n\n return (\n <span {...rootProps(props, `iob-ani-res iob-ani-res-${props.type}`)}>\n <svg\n key={`${props.type}-${props.replay ?? ''}`}\n viewBox=\"0 0 24 24\"\n >\n <circle\n className=\"iob-ani-res-circle\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n pathLength={1}\n />\n {mark}\n </svg>\n </span>\n );\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 Denis Haev (bluefox) <dogafox@gmail.com>
|
|
3
|
+
*
|
|
4
|
+
* MIT License
|
|
5
|
+
*/
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { type AnimatedIconProps } from './common';
|
|
8
|
+
export interface IconStatusLedProps extends AnimatedIconProps {
|
|
9
|
+
/** 'green', 'orange', 'yellow', 'red', 'blue', 'grey' or any CSS color */
|
|
10
|
+
color: string;
|
|
11
|
+
/** Every change of this value sends one pulse, e.g. the timestamp of the last `alive` update */
|
|
12
|
+
beat?: string | number | boolean | null;
|
|
13
|
+
/** Waiting, e.g. while the instance starts: the LED breathes */
|
|
14
|
+
blink?: boolean;
|
|
15
|
+
/** Own status symbol instead of the round LED. Pulse, pop and shake stay, the pulse has the size of the icon. */
|
|
16
|
+
children?: React.ReactNode;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Status LED. It pops when its color changes, shakes when it turns red and sends a pulse on every `beat`.
|
|
20
|
+
*
|
|
21
|
+
* ```tsx
|
|
22
|
+
* <IconStatusLed color={alive ? 'green' : 'red'} beat={aliveState?.ts} />
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function IconStatusLed(props: IconStatusLedProps): React.JSX.Element;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 Denis Haev (bluefox) <dogafox@gmail.com>
|
|
3
|
+
*
|
|
4
|
+
* MIT License
|
|
5
|
+
*/
|
|
6
|
+
import React, { useRef } from 'react';
|
|
7
|
+
import { play, rootProps, useAnimatedIconStyles, useChange } from './common';
|
|
8
|
+
/** Colors of the named states; any other CSS color can be given too */
|
|
9
|
+
const COLORS = {
|
|
10
|
+
green: '#4CAF50',
|
|
11
|
+
orange: '#FF9800',
|
|
12
|
+
yellow: '#FFC107',
|
|
13
|
+
red: '#F44336',
|
|
14
|
+
blue: '#2196F3',
|
|
15
|
+
grey: '#9E9E9E',
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Status LED. It pops when its color changes, shakes when it turns red and sends a pulse on every `beat`.
|
|
19
|
+
*
|
|
20
|
+
* ```tsx
|
|
21
|
+
* <IconStatusLed color={alive ? 'green' : 'red'} beat={aliveState?.ts} />
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function IconStatusLed(props) {
|
|
25
|
+
useAnimatedIconStyles();
|
|
26
|
+
const root = useRef(null);
|
|
27
|
+
const dot = useRef(null);
|
|
28
|
+
const ping = useRef(null);
|
|
29
|
+
const color = COLORS[props.color] || props.color;
|
|
30
|
+
useChange(props.beat, () => play(ping.current, [
|
|
31
|
+
{ transform: 'scale(1)', opacity: 0.55 },
|
|
32
|
+
// an own symbol fills the icon, its pulse grows less
|
|
33
|
+
{ transform: `scale(${props.children ? 1.7 : 2.3})`, opacity: 0 },
|
|
34
|
+
], { duration: 750, easing: 'cubic-bezier(0, 0, 0.2, 1)' }));
|
|
35
|
+
useChange(color, () => {
|
|
36
|
+
if (color === COLORS.red) {
|
|
37
|
+
play(root.current, [
|
|
38
|
+
{ transform: 'translateX(0)' },
|
|
39
|
+
{ transform: 'translateX(-0.09em)' },
|
|
40
|
+
{ transform: 'translateX(0.09em)' },
|
|
41
|
+
{ transform: 'translateX(-0.06em)' },
|
|
42
|
+
{ transform: 'translateX(0.06em)' },
|
|
43
|
+
{ transform: 'translateX(0)' },
|
|
44
|
+
], { duration: 450, easing: 'ease-in-out' });
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
play(dot.current, [{ transform: 'scale(0.55)' }, { transform: 'scale(1.25)' }, { transform: 'scale(1)' }], {
|
|
48
|
+
duration: 380,
|
|
49
|
+
easing: 'ease-out',
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
const ledProps = rootProps(props, `iob-ani-led${props.blink ? ' iob-ani-led-blink' : ''}`);
|
|
54
|
+
return (React.createElement("span", { ...ledProps, ref: root, style: { ...ledProps.style, '--iob-ani-led': color } },
|
|
55
|
+
React.createElement("span", { className: props.children ? 'iob-ani-led-ping iob-ani-led-full' : 'iob-ani-led-ping', ref: ping }),
|
|
56
|
+
props.children ? (React.createElement("span", { className: "iob-ani-layer iob-ani-led-own", ref: dot }, props.children)) : (React.createElement("span", { className: "iob-ani-led-dot", ref: dot }))));
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=IconStatusLed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IconStatusLed.js","sourceRoot":"./src/","sources":["Components/AnimatedIcons/IconStatusLed.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * Copyright 2026 Denis Haev (bluefox) <dogafox@gmail.com>\n *\n * MIT License\n */\nimport React, { useRef, type CSSProperties } from 'react';\nimport { type AnimatedIconProps, play, rootProps, useAnimatedIconStyles, useChange } from './common';\n\n/** Colors of the named states; any other CSS color can be given too */\nconst COLORS: Record<string, string> = {\n green: '#4CAF50',\n orange: '#FF9800',\n yellow: '#FFC107',\n red: '#F44336',\n blue: '#2196F3',\n grey: '#9E9E9E',\n};\n\nexport interface IconStatusLedProps extends AnimatedIconProps {\n /** 'green', 'orange', 'yellow', 'red', 'blue', 'grey' or any CSS color */\n color: string;\n /** Every change of this value sends one pulse, e.g. the timestamp of the last `alive` update */\n beat?: string | number | boolean | null;\n /** Waiting, e.g. while the instance starts: the LED breathes */\n blink?: boolean;\n /** Own status symbol instead of the round LED. Pulse, pop and shake stay, the pulse has the size of the icon. */\n children?: React.ReactNode;\n}\n\n/**\n * Status LED. It pops when its color changes, shakes when it turns red and sends a pulse on every `beat`.\n *\n * ```tsx\n * <IconStatusLed color={alive ? 'green' : 'red'} beat={aliveState?.ts} />\n * ```\n */\nexport function IconStatusLed(props: IconStatusLedProps): React.JSX.Element {\n useAnimatedIconStyles();\n const root = useRef<HTMLSpanElement>(null);\n const dot = useRef<HTMLSpanElement>(null);\n const ping = useRef<HTMLSpanElement>(null);\n const color = COLORS[props.color] || props.color;\n\n useChange(props.beat, () =>\n play(\n ping.current,\n [\n { transform: 'scale(1)', opacity: 0.55 },\n // an own symbol fills the icon, its pulse grows less\n { transform: `scale(${props.children ? 1.7 : 2.3})`, opacity: 0 },\n ],\n { duration: 750, easing: 'cubic-bezier(0, 0, 0.2, 1)' },\n ),\n );\n\n useChange(color, () => {\n if (color === COLORS.red) {\n play(\n root.current,\n [\n { transform: 'translateX(0)' },\n { transform: 'translateX(-0.09em)' },\n { transform: 'translateX(0.09em)' },\n { transform: 'translateX(-0.06em)' },\n { transform: 'translateX(0.06em)' },\n { transform: 'translateX(0)' },\n ],\n { duration: 450, easing: 'ease-in-out' },\n );\n } else {\n play(dot.current, [{ transform: 'scale(0.55)' }, { transform: 'scale(1.25)' }, { transform: 'scale(1)' }], {\n duration: 380,\n easing: 'ease-out',\n });\n }\n });\n\n const ledProps = rootProps(props, `iob-ani-led${props.blink ? ' iob-ani-led-blink' : ''}`);\n return (\n <span\n {...ledProps}\n ref={root}\n style={{ ...ledProps.style, '--iob-ani-led': color } as CSSProperties}\n >\n <span\n className={props.children ? 'iob-ani-led-ping iob-ani-led-full' : 'iob-ani-led-ping'}\n ref={ping}\n />\n {props.children ? (\n <span\n className=\"iob-ani-layer iob-ani-led-own\"\n ref={dot}\n >\n {props.children}\n </span>\n ) : (\n <span\n className=\"iob-ani-led-dot\"\n ref={dot}\n />\n )}\n </span>\n );\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 Denis Haev (bluefox) <dogafox@gmail.com>
|
|
3
|
+
*
|
|
4
|
+
* MIT License
|
|
5
|
+
*/
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { type AnimatedIconProps } from './common';
|
|
8
|
+
export interface IconTrashProps extends AnimatedIconProps {
|
|
9
|
+
/** Keeps the lid open, e.g. while a delete confirmation is shown */
|
|
10
|
+
open?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Trash can for delete buttons. The lid opens when the mouse is over the icon or over the button around it
|
|
14
|
+
* (`IconButton`, the start icon of a `Button`, a link...), or when that button has the keyboard focus.
|
|
15
|
+
*
|
|
16
|
+
* ```tsx
|
|
17
|
+
* <IconButton onClick={...}><IconTrash /></IconButton>
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function IconTrash(props: IconTrashProps): React.JSX.Element;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 Denis Haev (bluefox) <dogafox@gmail.com>
|
|
3
|
+
*
|
|
4
|
+
* MIT License
|
|
5
|
+
*/
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { rootProps, useAnimatedIconStyles } from './common';
|
|
8
|
+
/**
|
|
9
|
+
* Trash can for delete buttons. The lid opens when the mouse is over the icon or over the button around it
|
|
10
|
+
* (`IconButton`, the start icon of a `Button`, a link...), or when that button has the keyboard focus.
|
|
11
|
+
*
|
|
12
|
+
* ```tsx
|
|
13
|
+
* <IconButton onClick={...}><IconTrash /></IconButton>
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export function IconTrash(props) {
|
|
17
|
+
useAnimatedIconStyles();
|
|
18
|
+
return (React.createElement("span", { ...rootProps(props, `iob-ani-trash${props.open ? ' iob-ani-trash-open' : ''}`) },
|
|
19
|
+
React.createElement("svg", { viewBox: "0 0 24 24" },
|
|
20
|
+
React.createElement("path", { className: "iob-ani-trash-lid", fill: "currentColor", d: "M19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" }),
|
|
21
|
+
React.createElement("path", { fill: "currentColor", d: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12z" }))));
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=IconTrash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IconTrash.js","sourceRoot":"./src/","sources":["Components/AnimatedIcons/IconTrash.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAA0B,SAAS,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAOpF;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,KAAqB;IAC3C,qBAAqB,EAAE,CAAC;IACxB,OAAO,CACH,iCAAU,SAAS,CAAC,KAAK,EAAE,gBAAgB,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QACjF,6BAAK,OAAO,EAAC,WAAW;YACpB,8BACI,SAAS,EAAC,mBAAmB,EAC7B,IAAI,EAAC,cAAc,EACnB,CAAC,EAAC,mCAAmC,GACvC;YACF,8BACI,IAAI,EAAC,cAAc,EACnB,CAAC,EAAC,8CAA8C,GAClD,CACA,CACH,CACV,CAAC;AACN,CAAC","sourcesContent":["/**\n * Copyright 2026 Denis Haev (bluefox) <dogafox@gmail.com>\n *\n * MIT License\n */\nimport React from 'react';\nimport { type AnimatedIconProps, rootProps, useAnimatedIconStyles } from './common';\n\nexport interface IconTrashProps extends AnimatedIconProps {\n /** Keeps the lid open, e.g. while a delete confirmation is shown */\n open?: boolean;\n}\n\n/**\n * Trash can for delete buttons. The lid opens when the mouse is over the icon or over the button around it\n * (`IconButton`, the start icon of a `Button`, a link...), or when that button has the keyboard focus.\n *\n * ```tsx\n * <IconButton onClick={...}><IconTrash /></IconButton>\n * ```\n */\nexport function IconTrash(props: IconTrashProps): React.JSX.Element {\n useAnimatedIconStyles();\n return (\n <span {...rootProps(props, `iob-ani-trash${props.open ? ' iob-ani-trash-open' : ''}`)}>\n <svg viewBox=\"0 0 24 24\">\n <path\n className=\"iob-ani-trash-lid\"\n fill=\"currentColor\"\n d=\"M19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z\"\n />\n <path\n fill=\"currentColor\"\n d=\"M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12z\"\n />\n </svg>\n </span>\n );\n}\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 Denis Haev (bluefox) <dogafox@gmail.com>
|
|
3
|
+
*
|
|
4
|
+
* MIT License
|
|
5
|
+
*
|
|
6
|
+
* Shared parts of the animated icons: props, size, the style sheet and helpers.
|
|
7
|
+
* The icons are plain CSS/SVG (no animation library). They use `currentColor`, so they take the color of the
|
|
8
|
+
* surrounding text or button, and they have the size of the MUI icons.
|
|
9
|
+
*/
|
|
10
|
+
import type React from 'react';
|
|
11
|
+
import { type CSSProperties } from 'react';
|
|
12
|
+
export interface AnimatedIconProps {
|
|
13
|
+
/** Size like the MUI icons: 'small' (20 px), 'medium' (24 px, default), 'large' (35 px), 'inherit', pixels or e.g. '2em' */
|
|
14
|
+
fontSize?: 'inherit' | 'small' | 'medium' | 'large' | number | `${number}${'px' | 'em' | 'rem' | '%'}`;
|
|
15
|
+
/** Class name for the root element */
|
|
16
|
+
className?: string;
|
|
17
|
+
/** Styles for the root element, e.g. `color` */
|
|
18
|
+
style?: CSSProperties;
|
|
19
|
+
/** Accessible name. Without it, the icon is decorative and hidden from screen readers. */
|
|
20
|
+
ariaLabel?: string;
|
|
21
|
+
}
|
|
22
|
+
/** Puts the style sheet of the animated icons into the page, once and before the first paint */
|
|
23
|
+
export declare function useAnimatedIconStyles(): void;
|
|
24
|
+
/** Properties of the root element: class, size and accessibility */
|
|
25
|
+
export declare function rootProps(props: AnimatedIconProps, className: string): React.HTMLAttributes<HTMLSpanElement>;
|
|
26
|
+
/** True if the user asked the system for less motion */
|
|
27
|
+
export declare function prefersReducedMotion(): boolean;
|
|
28
|
+
/** Plays a one-time animation (Web Animations API), unless the user wants less motion */
|
|
29
|
+
export declare function play(element: Element | null | undefined, keyframes: Keyframe[], options: KeyframeAnimationOptions): Animation | null;
|
|
30
|
+
/** Calls `onChange` when `value` changes, but not on the first render */
|
|
31
|
+
export declare function useChange<T>(value: T, onChange: (value: T, previous: T) => void): void;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { useEffect, useInsertionEffect, useRef } from 'react';
|
|
2
|
+
const SIZES = {
|
|
3
|
+
small: '1.25rem',
|
|
4
|
+
medium: '1.5rem',
|
|
5
|
+
large: '2.1875rem',
|
|
6
|
+
inherit: 'inherit',
|
|
7
|
+
};
|
|
8
|
+
const STYLE_ID = 'iob-animated-icons';
|
|
9
|
+
const styles = `
|
|
10
|
+
/* zero specificity: MUI may set the size, e.g. 20 px for the start icon of a Button, like for its own icons */
|
|
11
|
+
:where(.iob-ani) { font-size: 1.5rem; }
|
|
12
|
+
.iob-ani { display: inline-block; position: relative; width: 1em; height: 1em; flex-shrink: 0; vertical-align: middle; line-height: 0; user-select: none; }
|
|
13
|
+
.iob-ani > svg, .iob-ani-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow: visible; }
|
|
14
|
+
|
|
15
|
+
/* Play / pause: two halves of the triangle become the two bars */
|
|
16
|
+
.iob-ani-pp-a, .iob-ani-pp-b { position: absolute; inset: 0; background: currentColor; transition: clip-path 0.32s cubic-bezier(0.4, 0, 0.2, 1), transform 0.25s ease; }
|
|
17
|
+
.iob-ani-pp-a { clip-path: polygon(33.33% 20.83%, 56.25% 35.42%, 56.25% 64.58%, 33.33% 79.17%); }
|
|
18
|
+
.iob-ani-pp-b { clip-path: polygon(56.25% 35.42%, 79.17% 50%, 79.17% 50%, 56.25% 64.58%); }
|
|
19
|
+
.iob-ani-on .iob-ani-pp-a { clip-path: polygon(25% 20.83%, 41.67% 20.83%, 41.67% 79.17%, 25% 79.17%); }
|
|
20
|
+
.iob-ani-on .iob-ani-pp-b { clip-path: polygon(58.33% 20.83%, 75% 20.83%, 75% 79.17%, 58.33% 79.17%); }
|
|
21
|
+
.iob-ani-pp-ring { opacity: 0; transition: opacity 0.2s; }
|
|
22
|
+
.iob-ani-pp-ring circle { fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-dasharray: 0.22 0.78; }
|
|
23
|
+
.iob-ani-pp-pending .iob-ani-pp-ring { opacity: 1; animation: iob-ani-spin 0.9s linear infinite; }
|
|
24
|
+
.iob-ani-pp-pending .iob-ani-pp-a, .iob-ani-pp-pending .iob-ani-pp-b { transform: scale(0.72); }
|
|
25
|
+
|
|
26
|
+
/* Status LED */
|
|
27
|
+
.iob-ani-led-dot, .iob-ani-led-ping { position: absolute; left: 20%; top: 20%; width: 60%; height: 60%; border-radius: 50%; background-color: var(--iob-ani-led); }
|
|
28
|
+
.iob-ani-led-dot { transition: background-color 0.3s; box-shadow: inset 0 -0.05em 0.1em rgba(0, 0, 0, 0.28); }
|
|
29
|
+
.iob-ani-led-ping { opacity: 0; }
|
|
30
|
+
.iob-ani-led-blink .iob-ani-led-dot, .iob-ani-led-blink .iob-ani-led-own { animation: iob-ani-breathe 1.6s ease-in-out infinite; }
|
|
31
|
+
.iob-ani-led-ping.iob-ani-led-full { left: 0; top: 0; width: 100%; height: 100%; }
|
|
32
|
+
.iob-ani-led-own { display: flex; align-items: center; justify-content: center; line-height: normal; }
|
|
33
|
+
|
|
34
|
+
/* Result: circle and mark draw themselves */
|
|
35
|
+
:where(.iob-ani-res-success) { color: #4CAF50; }
|
|
36
|
+
:where(.iob-ani-res-warning) { color: #FF9800; }
|
|
37
|
+
:where(.iob-ani-res-error) { color: #F44336; }
|
|
38
|
+
.iob-ani-res svg * { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 1 1; stroke-dashoffset: 1; }
|
|
39
|
+
.iob-ani-res-circle { transform: rotate(-90deg); transform-origin: 12px 12px; animation: iob-ani-draw 0.45s cubic-bezier(0.65, 0, 0.35, 1) forwards; }
|
|
40
|
+
.iob-ani-res-mark { animation: iob-ani-draw 0.3s cubic-bezier(0.65, 0, 0.35, 1) 0.35s forwards; }
|
|
41
|
+
.iob-ani-res-mark2 { animation: iob-ani-draw 0.3s cubic-bezier(0.65, 0, 0.35, 1) 0.5s forwards; }
|
|
42
|
+
.iob-ani-res-error svg, .iob-ani-res-warning svg { animation: iob-ani-shake 0.4s 0.75s; }
|
|
43
|
+
|
|
44
|
+
/* Refresh */
|
|
45
|
+
.iob-ani-rf-done { opacity: 0; }
|
|
46
|
+
.iob-ani-rf-done path { fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 1 1; stroke-dashoffset: 1; }
|
|
47
|
+
|
|
48
|
+
/* Bell */
|
|
49
|
+
.iob-ani-bell-body { transform-origin: 12px 3px; }
|
|
50
|
+
|
|
51
|
+
/* Menu / arrow: three bars, the icon turns by 180 degrees */
|
|
52
|
+
.iob-ani-menu-rot { transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1); }
|
|
53
|
+
.iob-ani-menu-open .iob-ani-menu-rot { transform: rotate(180deg); }
|
|
54
|
+
.iob-ani-menu-right .iob-ani-menu-flip { transform: scaleX(-1); }
|
|
55
|
+
.iob-ani-menu-bar { position: absolute; left: 0.125em; width: 0.75em; height: 0.0834em; margin-top: -0.0417em; border-radius: 0.02em; background: currentColor; transform-origin: 100% 50%; transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1); }
|
|
56
|
+
.iob-ani-menu-b1 { top: 0.2917em; }
|
|
57
|
+
.iob-ani-menu-b2 { top: 0.5em; }
|
|
58
|
+
.iob-ani-menu-b3 { top: 0.7083em; }
|
|
59
|
+
.iob-ani-menu-open .iob-ani-menu-b1 { transform: translate(-0.0417em, 0.2083em) rotate(45deg) scaleX(0.63); }
|
|
60
|
+
.iob-ani-menu-open .iob-ani-menu-b2 { transform: translateX(-0.0417em) scaleX(0.889); }
|
|
61
|
+
.iob-ani-menu-open .iob-ani-menu-b3 { transform: translate(-0.0417em, -0.2083em) rotate(-45deg) scaleX(0.63); }
|
|
62
|
+
|
|
63
|
+
/* Trash: the lid opens on hover of the icon or of its button */
|
|
64
|
+
.iob-ani-trash-lid { transform-origin: 5px 6px; transition: transform 0.25s cubic-bezier(0.3, 1.5, 0.6, 1); }
|
|
65
|
+
.iob-ani-trash-open .iob-ani-trash-lid,
|
|
66
|
+
.iob-ani-trash:hover .iob-ani-trash-lid,
|
|
67
|
+
:is(button, a, [role="button"]):hover .iob-ani-trash .iob-ani-trash-lid,
|
|
68
|
+
:is(button, a, [role="button"]):focus-visible .iob-ani-trash .iob-ani-trash-lid { transform: translateY(-1.5px) rotate(-18deg); }
|
|
69
|
+
|
|
70
|
+
/* Expert mode: mortarboard, outlined when off and filled when on */
|
|
71
|
+
.iob-ani-expert-hat { transform-origin: 12px 12px; }
|
|
72
|
+
.iob-ani-expert-shape { fill: currentColor; fill-opacity: 0; stroke: currentColor; stroke-width: 1.6; stroke-linejoin: round; transition: fill-opacity 0.18s ease; }
|
|
73
|
+
.iob-ani-expert-on .iob-ani-expert-shape { fill-opacity: 1; transition-delay: 0.12s; }
|
|
74
|
+
.iob-ani-expert-tassel { transform-origin: 21.2px 9.1px; }
|
|
75
|
+
.iob-ani-expert-cord { fill: none; stroke: currentColor; stroke-width: 1.3; stroke-linecap: round; }
|
|
76
|
+
.iob-ani-expert-tuft { fill: currentColor; stroke: currentColor; stroke-width: 0.8; stroke-linejoin: round; }
|
|
77
|
+
|
|
78
|
+
/* Folder: closed, the front flap lies on the back like a closed folder; open, it folds down. The closed
|
|
79
|
+
shape covers both until the flap starts to move, so the closed folder looks exactly like IconClosed */
|
|
80
|
+
.iob-ani-folder path { fill: currentColor; }
|
|
81
|
+
.iob-ani-folder-front { transform-origin: 104px 448px; transform: scaleY(1.1667) skewX(29.1deg); transition: transform 0.22s cubic-bezier(0.4, 0, 0.2, 1); }
|
|
82
|
+
.iob-ani-folder-open .iob-ani-folder-front { transform: none; transition: transform 0.32s cubic-bezier(0.3, 1.35, 0.5, 1); }
|
|
83
|
+
.iob-ani-folder-closed { transition: opacity 0.06s linear 0.18s; }
|
|
84
|
+
.iob-ani-folder-open .iob-ani-folder-closed { opacity: 0; transition-delay: 0s; }
|
|
85
|
+
|
|
86
|
+
/* Connection */
|
|
87
|
+
.iob-ani-wifi path { fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; transition: opacity 0.3s; }
|
|
88
|
+
.iob-ani-wifi circle { fill: currentColor; transition: opacity 0.3s; }
|
|
89
|
+
.iob-ani-wifi .iob-ani-wifi-slash { stroke-dasharray: 1 1; stroke-dashoffset: 1; transition: stroke-dashoffset 0.35s cubic-bezier(0.65, 0, 0.35, 1); }
|
|
90
|
+
.iob-ani-wifi-disconnected .iob-ani-wifi-slash { stroke-dashoffset: 0; }
|
|
91
|
+
.iob-ani-wifi-disconnected .iob-ani-wifi-w { opacity: 0.35; }
|
|
92
|
+
.iob-ani-wifi-connecting .iob-ani-wifi-w { animation: iob-ani-wave 1.5s ease-in-out infinite; }
|
|
93
|
+
.iob-ani-wifi-connecting .iob-ani-wifi-w1 { animation-delay: 0.18s; }
|
|
94
|
+
.iob-ani-wifi-connecting .iob-ani-wifi-w2 { animation-delay: 0.36s; }
|
|
95
|
+
.iob-ani-wifi-connecting .iob-ani-wifi-w3 { animation-delay: 0.54s; }
|
|
96
|
+
|
|
97
|
+
@keyframes iob-ani-spin { to { transform: rotate(360deg); } }
|
|
98
|
+
@keyframes iob-ani-draw { to { stroke-dashoffset: 0; } }
|
|
99
|
+
@keyframes iob-ani-breathe { 50% { opacity: 0.35; } }
|
|
100
|
+
@keyframes iob-ani-wave { 0%, 100% { opacity: 0.25; } 30% { opacity: 1; } }
|
|
101
|
+
@keyframes iob-ani-shake { 20% { transform: translateX(-1.5px); } 40% { transform: translateX(1.5px); } 60% { transform: translateX(-1px); } 80% { transform: translateX(1px); } }
|
|
102
|
+
|
|
103
|
+
@media (prefers-reduced-motion: reduce) {
|
|
104
|
+
.iob-ani-pp-a, .iob-ani-pp-b, .iob-ani-menu-rot, .iob-ani-menu-bar, .iob-ani-trash-lid, .iob-ani-expert-shape, .iob-ani-folder-front, .iob-ani-folder-closed { transition-duration: 0.01ms; transition-delay: 0s; }
|
|
105
|
+
.iob-ani-pp-pending .iob-ani-pp-ring { animation: iob-ani-breathe 1.2s ease-in-out infinite; }
|
|
106
|
+
.iob-ani-res svg, .iob-ani-res svg * { animation: none !important; stroke-dashoffset: 0; }
|
|
107
|
+
}
|
|
108
|
+
`;
|
|
109
|
+
/** Puts the style sheet of the animated icons into the page, once and before the first paint */
|
|
110
|
+
export function useAnimatedIconStyles() {
|
|
111
|
+
useInsertionEffect(() => {
|
|
112
|
+
if (!window.document.getElementById(STYLE_ID)) {
|
|
113
|
+
const style = window.document.createElement('style');
|
|
114
|
+
style.setAttribute('id', STYLE_ID);
|
|
115
|
+
style.innerHTML = styles;
|
|
116
|
+
window.document.head.appendChild(style);
|
|
117
|
+
}
|
|
118
|
+
}, []);
|
|
119
|
+
}
|
|
120
|
+
/** Properties of the root element: class, size and accessibility */
|
|
121
|
+
export function rootProps(props, className) {
|
|
122
|
+
// Without fontSize the size comes from the style sheet (24 px), so that MUI can change it like for its own icons
|
|
123
|
+
let fontSize;
|
|
124
|
+
if (typeof props.fontSize === 'number') {
|
|
125
|
+
fontSize = props.fontSize;
|
|
126
|
+
}
|
|
127
|
+
else if (props.fontSize) {
|
|
128
|
+
fontSize = SIZES[props.fontSize] || props.fontSize;
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
className: `iob-ani ${className}${props.className ? ` ${props.className}` : ''}`,
|
|
132
|
+
style: fontSize === undefined ? props.style : { fontSize, ...props.style },
|
|
133
|
+
role: props.ariaLabel ? 'img' : undefined,
|
|
134
|
+
'aria-label': props.ariaLabel,
|
|
135
|
+
'aria-hidden': props.ariaLabel ? undefined : true,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/** True if the user asked the system for less motion */
|
|
139
|
+
export function prefersReducedMotion() {
|
|
140
|
+
return !!window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
|
|
141
|
+
}
|
|
142
|
+
/** Plays a one-time animation (Web Animations API), unless the user wants less motion */
|
|
143
|
+
export function play(element, keyframes, options) {
|
|
144
|
+
if (!element?.animate || prefersReducedMotion()) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
return element.animate(keyframes, options);
|
|
148
|
+
}
|
|
149
|
+
/** Calls `onChange` when `value` changes, but not on the first render */
|
|
150
|
+
export function useChange(value, onChange) {
|
|
151
|
+
const previous = useRef(value);
|
|
152
|
+
const callback = useRef(onChange);
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
callback.current = onChange;
|
|
155
|
+
});
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
if (!Object.is(previous.current, value)) {
|
|
158
|
+
const old = previous.current;
|
|
159
|
+
previous.current = value;
|
|
160
|
+
callback.current(value, old);
|
|
161
|
+
}
|
|
162
|
+
}, [value]);
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=common.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"common.js","sourceRoot":"./src/","sources":["Components/AnimatedIcons/common.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * Copyright 2026 Denis Haev (bluefox) <dogafox@gmail.com>\n *\n * MIT License\n *\n * Shared parts of the animated icons: props, size, the style sheet and helpers.\n * The icons are plain CSS/SVG (no animation library). They use `currentColor`, so they take the color of the\n * surrounding text or button, and they have the size of the MUI icons.\n */\nimport type React from 'react';\nimport { useEffect, useInsertionEffect, useRef, type CSSProperties } from 'react';\n\nexport interface AnimatedIconProps {\n /** Size like the MUI icons: 'small' (20 px), 'medium' (24 px, default), 'large' (35 px), 'inherit', pixels or e.g. '2em' */\n fontSize?: 'inherit' | 'small' | 'medium' | 'large' | number | `${number}${'px' | 'em' | 'rem' | '%'}`;\n /** Class name for the root element */\n className?: string;\n /** Styles for the root element, e.g. `color` */\n style?: CSSProperties;\n /** Accessible name. Without it, the icon is decorative and hidden from screen readers. */\n ariaLabel?: string;\n}\n\nconst SIZES: Record<string, string> = {\n small: '1.25rem',\n medium: '1.5rem',\n large: '2.1875rem',\n inherit: 'inherit',\n};\n\nconst STYLE_ID = 'iob-animated-icons';\n\nconst styles = `\n/* zero specificity: MUI may set the size, e.g. 20 px for the start icon of a Button, like for its own icons */\n:where(.iob-ani) { font-size: 1.5rem; }\n.iob-ani { display: inline-block; position: relative; width: 1em; height: 1em; flex-shrink: 0; vertical-align: middle; line-height: 0; user-select: none; }\n.iob-ani > svg, .iob-ani-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow: visible; }\n\n/* Play / pause: two halves of the triangle become the two bars */\n.iob-ani-pp-a, .iob-ani-pp-b { position: absolute; inset: 0; background: currentColor; transition: clip-path 0.32s cubic-bezier(0.4, 0, 0.2, 1), transform 0.25s ease; }\n.iob-ani-pp-a { clip-path: polygon(33.33% 20.83%, 56.25% 35.42%, 56.25% 64.58%, 33.33% 79.17%); }\n.iob-ani-pp-b { clip-path: polygon(56.25% 35.42%, 79.17% 50%, 79.17% 50%, 56.25% 64.58%); }\n.iob-ani-on .iob-ani-pp-a { clip-path: polygon(25% 20.83%, 41.67% 20.83%, 41.67% 79.17%, 25% 79.17%); }\n.iob-ani-on .iob-ani-pp-b { clip-path: polygon(58.33% 20.83%, 75% 20.83%, 75% 79.17%, 58.33% 79.17%); }\n.iob-ani-pp-ring { opacity: 0; transition: opacity 0.2s; }\n.iob-ani-pp-ring circle { fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-dasharray: 0.22 0.78; }\n.iob-ani-pp-pending .iob-ani-pp-ring { opacity: 1; animation: iob-ani-spin 0.9s linear infinite; }\n.iob-ani-pp-pending .iob-ani-pp-a, .iob-ani-pp-pending .iob-ani-pp-b { transform: scale(0.72); }\n\n/* Status LED */\n.iob-ani-led-dot, .iob-ani-led-ping { position: absolute; left: 20%; top: 20%; width: 60%; height: 60%; border-radius: 50%; background-color: var(--iob-ani-led); }\n.iob-ani-led-dot { transition: background-color 0.3s; box-shadow: inset 0 -0.05em 0.1em rgba(0, 0, 0, 0.28); }\n.iob-ani-led-ping { opacity: 0; }\n.iob-ani-led-blink .iob-ani-led-dot, .iob-ani-led-blink .iob-ani-led-own { animation: iob-ani-breathe 1.6s ease-in-out infinite; }\n.iob-ani-led-ping.iob-ani-led-full { left: 0; top: 0; width: 100%; height: 100%; }\n.iob-ani-led-own { display: flex; align-items: center; justify-content: center; line-height: normal; }\n\n/* Result: circle and mark draw themselves */\n:where(.iob-ani-res-success) { color: #4CAF50; }\n:where(.iob-ani-res-warning) { color: #FF9800; }\n:where(.iob-ani-res-error) { color: #F44336; }\n.iob-ani-res svg * { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 1 1; stroke-dashoffset: 1; }\n.iob-ani-res-circle { transform: rotate(-90deg); transform-origin: 12px 12px; animation: iob-ani-draw 0.45s cubic-bezier(0.65, 0, 0.35, 1) forwards; }\n.iob-ani-res-mark { animation: iob-ani-draw 0.3s cubic-bezier(0.65, 0, 0.35, 1) 0.35s forwards; }\n.iob-ani-res-mark2 { animation: iob-ani-draw 0.3s cubic-bezier(0.65, 0, 0.35, 1) 0.5s forwards; }\n.iob-ani-res-error svg, .iob-ani-res-warning svg { animation: iob-ani-shake 0.4s 0.75s; }\n\n/* Refresh */\n.iob-ani-rf-done { opacity: 0; }\n.iob-ani-rf-done path { fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 1 1; stroke-dashoffset: 1; }\n\n/* Bell */\n.iob-ani-bell-body { transform-origin: 12px 3px; }\n\n/* Menu / arrow: three bars, the icon turns by 180 degrees */\n.iob-ani-menu-rot { transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1); }\n.iob-ani-menu-open .iob-ani-menu-rot { transform: rotate(180deg); }\n.iob-ani-menu-right .iob-ani-menu-flip { transform: scaleX(-1); }\n.iob-ani-menu-bar { position: absolute; left: 0.125em; width: 0.75em; height: 0.0834em; margin-top: -0.0417em; border-radius: 0.02em; background: currentColor; transform-origin: 100% 50%; transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1); }\n.iob-ani-menu-b1 { top: 0.2917em; }\n.iob-ani-menu-b2 { top: 0.5em; }\n.iob-ani-menu-b3 { top: 0.7083em; }\n.iob-ani-menu-open .iob-ani-menu-b1 { transform: translate(-0.0417em, 0.2083em) rotate(45deg) scaleX(0.63); }\n.iob-ani-menu-open .iob-ani-menu-b2 { transform: translateX(-0.0417em) scaleX(0.889); }\n.iob-ani-menu-open .iob-ani-menu-b3 { transform: translate(-0.0417em, -0.2083em) rotate(-45deg) scaleX(0.63); }\n\n/* Trash: the lid opens on hover of the icon or of its button */\n.iob-ani-trash-lid { transform-origin: 5px 6px; transition: transform 0.25s cubic-bezier(0.3, 1.5, 0.6, 1); }\n.iob-ani-trash-open .iob-ani-trash-lid,\n.iob-ani-trash:hover .iob-ani-trash-lid,\n:is(button, a, [role=\"button\"]):hover .iob-ani-trash .iob-ani-trash-lid,\n:is(button, a, [role=\"button\"]):focus-visible .iob-ani-trash .iob-ani-trash-lid { transform: translateY(-1.5px) rotate(-18deg); }\n\n/* Expert mode: mortarboard, outlined when off and filled when on */\n.iob-ani-expert-hat { transform-origin: 12px 12px; }\n.iob-ani-expert-shape { fill: currentColor; fill-opacity: 0; stroke: currentColor; stroke-width: 1.6; stroke-linejoin: round; transition: fill-opacity 0.18s ease; }\n.iob-ani-expert-on .iob-ani-expert-shape { fill-opacity: 1; transition-delay: 0.12s; }\n.iob-ani-expert-tassel { transform-origin: 21.2px 9.1px; }\n.iob-ani-expert-cord { fill: none; stroke: currentColor; stroke-width: 1.3; stroke-linecap: round; }\n.iob-ani-expert-tuft { fill: currentColor; stroke: currentColor; stroke-width: 0.8; stroke-linejoin: round; }\n\n/* Folder: closed, the front flap lies on the back like a closed folder; open, it folds down. The closed\n shape covers both until the flap starts to move, so the closed folder looks exactly like IconClosed */\n.iob-ani-folder path { fill: currentColor; }\n.iob-ani-folder-front { transform-origin: 104px 448px; transform: scaleY(1.1667) skewX(29.1deg); transition: transform 0.22s cubic-bezier(0.4, 0, 0.2, 1); }\n.iob-ani-folder-open .iob-ani-folder-front { transform: none; transition: transform 0.32s cubic-bezier(0.3, 1.35, 0.5, 1); }\n.iob-ani-folder-closed { transition: opacity 0.06s linear 0.18s; }\n.iob-ani-folder-open .iob-ani-folder-closed { opacity: 0; transition-delay: 0s; }\n\n/* Connection */\n.iob-ani-wifi path { fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; transition: opacity 0.3s; }\n.iob-ani-wifi circle { fill: currentColor; transition: opacity 0.3s; }\n.iob-ani-wifi .iob-ani-wifi-slash { stroke-dasharray: 1 1; stroke-dashoffset: 1; transition: stroke-dashoffset 0.35s cubic-bezier(0.65, 0, 0.35, 1); }\n.iob-ani-wifi-disconnected .iob-ani-wifi-slash { stroke-dashoffset: 0; }\n.iob-ani-wifi-disconnected .iob-ani-wifi-w { opacity: 0.35; }\n.iob-ani-wifi-connecting .iob-ani-wifi-w { animation: iob-ani-wave 1.5s ease-in-out infinite; }\n.iob-ani-wifi-connecting .iob-ani-wifi-w1 { animation-delay: 0.18s; }\n.iob-ani-wifi-connecting .iob-ani-wifi-w2 { animation-delay: 0.36s; }\n.iob-ani-wifi-connecting .iob-ani-wifi-w3 { animation-delay: 0.54s; }\n\n@keyframes iob-ani-spin { to { transform: rotate(360deg); } }\n@keyframes iob-ani-draw { to { stroke-dashoffset: 0; } }\n@keyframes iob-ani-breathe { 50% { opacity: 0.35; } }\n@keyframes iob-ani-wave { 0%, 100% { opacity: 0.25; } 30% { opacity: 1; } }\n@keyframes iob-ani-shake { 20% { transform: translateX(-1.5px); } 40% { transform: translateX(1.5px); } 60% { transform: translateX(-1px); } 80% { transform: translateX(1px); } }\n\n@media (prefers-reduced-motion: reduce) {\n .iob-ani-pp-a, .iob-ani-pp-b, .iob-ani-menu-rot, .iob-ani-menu-bar, .iob-ani-trash-lid, .iob-ani-expert-shape, .iob-ani-folder-front, .iob-ani-folder-closed { transition-duration: 0.01ms; transition-delay: 0s; }\n .iob-ani-pp-pending .iob-ani-pp-ring { animation: iob-ani-breathe 1.2s ease-in-out infinite; }\n .iob-ani-res svg, .iob-ani-res svg * { animation: none !important; stroke-dashoffset: 0; }\n}\n`;\n\n/** Puts the style sheet of the animated icons into the page, once and before the first paint */\nexport function useAnimatedIconStyles(): void {\n useInsertionEffect(() => {\n if (!window.document.getElementById(STYLE_ID)) {\n const style = window.document.createElement('style');\n style.setAttribute('id', STYLE_ID);\n style.innerHTML = styles;\n window.document.head.appendChild(style);\n }\n }, []);\n}\n\n/** Properties of the root element: class, size and accessibility */\nexport function rootProps(props: AnimatedIconProps, className: string): React.HTMLAttributes<HTMLSpanElement> {\n // Without fontSize the size comes from the style sheet (24 px), so that MUI can change it like for its own icons\n let fontSize: string | number | undefined;\n if (typeof props.fontSize === 'number') {\n fontSize = props.fontSize;\n } else if (props.fontSize) {\n fontSize = SIZES[props.fontSize] || props.fontSize;\n }\n return {\n className: `iob-ani ${className}${props.className ? ` ${props.className}` : ''}`,\n style: fontSize === undefined ? props.style : { fontSize, ...props.style },\n role: props.ariaLabel ? 'img' : undefined,\n 'aria-label': props.ariaLabel,\n 'aria-hidden': props.ariaLabel ? undefined : true,\n };\n}\n\n/** True if the user asked the system for less motion */\nexport function prefersReducedMotion(): boolean {\n return !!window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n}\n\n/** Plays a one-time animation (Web Animations API), unless the user wants less motion */\nexport function play(\n element: Element | null | undefined,\n keyframes: Keyframe[],\n options: KeyframeAnimationOptions,\n): Animation | null {\n if (!element?.animate || prefersReducedMotion()) {\n return null;\n }\n return element.animate(keyframes, options);\n}\n\n/** Calls `onChange` when `value` changes, but not on the first render */\nexport function useChange<T>(value: T, onChange: (value: T, previous: T) => void): void {\n const previous = useRef(value);\n const callback = useRef(onChange);\n useEffect(() => {\n callback.current = onChange;\n });\n useEffect(() => {\n if (!Object.is(previous.current, value)) {\n const old = previous.current;\n previous.current = value;\n callback.current(value, old);\n }\n }, [value]);\n}\n"]}
|
|
@@ -13,9 +13,9 @@ import { DialogError } from '../Dialogs/Error';
|
|
|
13
13
|
import { Utils } from './Utils';
|
|
14
14
|
import { DialogTextInput } from '../Dialogs/TextInput';
|
|
15
15
|
// Custom Icons
|
|
16
|
-
import {
|
|
16
|
+
import { IconExpertMode } from './AnimatedIcons/IconExpertMode';
|
|
17
|
+
import { IconFolder } from './AnimatedIcons/IconFolder';
|
|
17
18
|
import { IconClosed } from '../icons/IconClosed';
|
|
18
|
-
import { IconOpen } from '../icons/IconOpen';
|
|
19
19
|
import { IconNoIcon } from '../icons/IconNoIcon';
|
|
20
20
|
import { Icon } from './Icon';
|
|
21
21
|
import { withWidth } from './withWidth';
|
|
@@ -1005,7 +1005,6 @@ export class FileBrowserClass extends Component {
|
|
|
1005
1005
|
!item.temp) {
|
|
1006
1006
|
return null;
|
|
1007
1007
|
}
|
|
1008
|
-
const IconEl = expanded ? IconOpen : IconClosed;
|
|
1009
1008
|
const padding = this.state.viewType === TABLE ? item.level * this.levelPadding : 0;
|
|
1010
1009
|
const isUserData = item.name === USER_DATA;
|
|
1011
1010
|
const isSpecialData = isUserData || item.name === 'vis.0' || item.name === 'vis-2.0';
|
|
@@ -1013,7 +1012,7 @@ export class FileBrowserClass extends Component {
|
|
|
1013
1012
|
return (React.createElement(Box, { component: "div", key: item.id, id: item.id, style: this.state.viewType === TABLE
|
|
1014
1013
|
? { marginLeft: padding, width: `calc(100% - ${padding}px)` }
|
|
1015
1014
|
: undefined, onClick: e => (this.state.viewType === TABLE ? this.select(item.id, e) : this.changeFolder(e, item.id)), onDoubleClick: e => this.state.viewType === TABLE && this.toggleFolder(item, e), title: this.getText(item.title), className: "browserItem", sx: Utils.getStyle(this.props.theme, styles[`item${this.state.viewType}`], styles[`itemFolder${this.state.viewType}`], this.state.selected === item.id ? styles.itemSelected : {}, item.temp ? styles.itemFolderTemp : {}) },
|
|
1016
|
-
React.createElement(
|
|
1015
|
+
React.createElement(IconFolder, { open: !!expanded, style: iconStyle, onClick: this.state.viewType === TABLE ? (e) => this.toggleFolder(item, e) : undefined }),
|
|
1017
1016
|
React.createElement(Box, { component: "div", sx: Utils.getStyle(this.props.theme, styles[`itemName${this.state.viewType}`], styles[`itemNameFolder${this.state.viewType}`]) }, isUserData ? this.props.t('ra_User files') : item.name),
|
|
1018
1017
|
React.createElement(Box, { component: "div", style: styles[`itemSize${this.state.viewType}`], sx: { display: { md: 'inline-block', sm: 'none' } } }, this.state.viewType === TABLE && this.state.folders[item.id]
|
|
1019
1018
|
? this.state.folders[item.id].length
|
|
@@ -1263,7 +1262,7 @@ export class FileBrowserClass extends Component {
|
|
|
1263
1262
|
...styles.menuButton,
|
|
1264
1263
|
...(this.state.expertMode ? styles.menuButtonExpertActive : undefined),
|
|
1265
1264
|
}, "aria-label": "expert mode", onClick: () => this.setState({ expertMode: !this.state.expertMode }), size: "small" },
|
|
1266
|
-
React.createElement(
|
|
1265
|
+
React.createElement(IconExpertMode, { active: !!this.state.expertMode }))) : null,
|
|
1267
1266
|
this.props.showViewTypeButton ? (React.createElement(IconButton, { edge: "start", title: this.props.t('ra_Toggle view mode'), style: styles.menuButton, "aria-label": "view mode", onClick: () => {
|
|
1268
1267
|
const viewType = this.state.viewType === TABLE ? TILE : TABLE;
|
|
1269
1268
|
this.localStorage.setItem('files.viewType', viewType);
|