chore: Typescript: Rewrite utils (#21660)

Co-authored-by: Michael Geers <michael@geers.tv>
This commit is contained in:
Maschga 2025-06-20 17:33:52 +02:00 • committed by GitHub
parent 4e619580ed
commit abf34f1fc4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 154 additions and 111 deletions

View file

@ -258,11 +258,11 @@ import SmartCostLimit from "../Tariff/SmartCostLimit.vue";
import CustomSelect from "../Helper/CustomSelect.vue";
import GenericModal from "../Helper/GenericModal.vue";
import formatter, { POWER_UNIT } from "@/mixins/formatter";
import collector from "@/mixins/collector";
import collector from "@/mixins/collector.js";
import api from "@/api";
import smartCostAvailable from "@/utils/smartCostAvailable";
import { defineComponent, type PropType } from "vue";
import type { Battery, SelectOption } from "@/types/evcc";
import type { Battery, SelectOption, SMART_COST_TYPE } from "@/types/evcc";
export default defineComponent({
name: "BatterySettingsModal",
@ -276,7 +276,7 @@ export default defineComponent({
batteryDischargeControl: Boolean,
battery: { type: Array as PropType<Battery[]>, default: () => [] },
batteryGridChargeLimit: { type: Number, default: null },
smartCostType: String,
smartCostType: String as PropType<SMART_COST_TYPE>,
tariffGrid: Number,
currency: String,
},

View file

@ -111,7 +111,7 @@ import Arrival from "./Arrival.vue";
import formatter from "@/mixins/formatter";
import collector from "@/mixins/collector";
import api from "@/api";
import { optionStep, fmtEnergy } from "@/utils/energyOptions";
import { optionStep, fmtEnergy } from "@/utils/energyOptions.ts";
import { defineComponent, type PropType } from "vue";
import type { CURRENCY, Timeout, Vehicle } from "@/types/evcc";
import type { StaticPlan, StaticSocPlan, StaticEnergyPlan, RepeatingPlan } from "./types";

View file

@ -182,7 +182,7 @@ import "@h2d2/shopicons/es/regular/checkmark";
import { distanceUnit } from "@/units";
import formatter from "@/mixins/formatter";
import { energyOptions } from "@/utils/energyOptions";
import { energyOptions } from "@/utils/energyOptions.ts";
import { defineComponent } from "vue";
import PreconditionSelect from "./PreconditionSelect.vue";
@ -233,10 +233,10 @@ export default defineComponent({
const options = energyOptions(
0,
this.capacity || 100,
this.socPerKwh,
this.fmtWh,
this.fmtPercentage,
"-"
"-",
this.socPerKwh
);
// remove the first entry (0)
return options.slice(1);

View file

@ -1,4 +1,4 @@
import { CURRENCY, type Rate } from "@/types/evcc";
import { CURRENCY, SMART_COST_TYPE, type Rate } from "@/types/evcc";
import Preview from "./Preview.vue";
import type { StoryFn } from "@storybook/vue3";
@ -32,7 +32,7 @@ const co2Data = {
].map((value, i) => createRate(value, i)),
duration: 8695,
plan: [createRate(213, 4), createRate(336, 11), createRate(336, 12)],
smartCostType: "co2",
smartCostType: SMART_COST_TYPE.CO2,
targetTime: createDate(14),
};
@ -40,7 +40,7 @@ const fixedData = {
rates: [createRate(0.442, 0, 50)],
duration: 8695,
plan: [createRate(0.442, 12, 3)],
smartCostType: "price",
smartCostType: SMART_COST_TYPE.PRICE_DYNAMIC,
currency: CURRENCY.EUR,
targetTime: createDate(14),
};
@ -55,7 +55,7 @@ const zonedData = {
],
duration: 8695,
plan: [createRate(2.39, 13, 3)],
smartCostType: "price",
smartCostType: SMART_COST_TYPE.PRICE_FORECAST,
currency: CURRENCY.DKK,
targetTime: createDate(17),
};
@ -64,7 +64,7 @@ const unknownData = {
rates: co2Data.rates.slice(0, 16),
duration: 8695,
plan: [createRate(213, 4), createRate(336, 11), createRate(336, 12)],
smartCostType: "co2",
smartCostType: SMART_COST_TYPE.CO2,
targetTime: createDate(14),
};
@ -74,7 +74,7 @@ const dynamicData = {
),
duration: 8695,
plan: [createRate(0.23, 2, 5)],
smartCostType: "price",
smartCostType: SMART_COST_TYPE.PRICE_DYNAMIC,
currency: CURRENCY.EUR,
targetTime: createDate(13),
};

View file

@ -37,9 +37,8 @@
<script lang="ts">
import { defineComponent, type PropType } from "vue";
import formatter from "@/mixins/formatter";
import { CO2_TYPE } from "@/units";
import TariffChart from "../Tariff/TariffChart.vue";
import type { CURRENCY, Rate, Slot } from "@/types/evcc";
import { SMART_COST_TYPE, type CURRENCY, type Rate, type Slot } from "@/types/evcc";
export default defineComponent({
name: "ChargingPlanPreview",
@ -50,7 +49,7 @@ export default defineComponent({
power: Number,
rates: Array as PropType<Rate[]>,
plan: Array as PropType<Rate[]>,
smartCostType: String,
smartCostType: String as PropType<SMART_COST_TYPE>,
targetTime: [Date, null],
currency: String as PropType<CURRENCY>,
},
@ -81,7 +80,7 @@ export default defineComponent({
return null;
},
isCo2(): boolean {
return this.smartCostType === CO2_TYPE;
return this.smartCostType === SMART_COST_TYPE.CO2;
},
hasTariff(): boolean {
return (this.rates?.length || 0) > 1;

View file

@ -287,10 +287,15 @@ import Entry from "./Entry.vue";
import formatter, { POWER_UNIT } from "@/mixins/formatter";
import AnimatedNumber from "../Helper/AnimatedNumber.vue";
import settings from "@/settings";
import { CO2_TYPE } from "@/units";
import collector from "@/mixins/collector";
import collector from "@/mixins/collector.js";
import { defineComponent, type PropType } from "vue";
import type { Battery, CURRENCY, Forecast, LoadpointCompact } from "@/types/evcc";
import {
SMART_COST_TYPE,
type Battery,
type CURRENCY,
type Forecast,
type LoadpointCompact,
} from "@/types/evcc";
export default defineComponent({
name: "Energyflow",
@ -419,7 +424,7 @@ export default defineComponent({
return (temp: number) => this.fmtTemperature(temp);
},
co2Available() {
return this.smartCostType === CO2_TYPE;
return this.smartCostType === SMART_COST_TYPE.CO2;
},
pvPossible() {
return this.pvConfigured || this.gridConfigured;

View file

@ -102,11 +102,11 @@ import {
removeLocalePreference,
} from "@/i18n.ts";
import { getThemePreference, setThemePreference } from "@/theme.ts";
import { getUnits, setUnits, UNITS, is12hFormat, set12hFormat } from "@/units";
import { getUnits, setUnits, is12hFormat, set12hFormat } from "@/units";
import { getHiddenFeatures, setHiddenFeatures } from "@/featureflags.ts";
import { isApp } from "@/utils/native";
import { defineComponent, type PropType } from "vue";
import { THEME, type Sponsor } from "@/types/evcc";
import { LENGTH_UNIT, THEME, type Sponsor } from "@/types/evcc";
const TIME_12H = "12";
const TIME_24H = "24";
@ -126,7 +126,7 @@ export default defineComponent({
hiddenFeatures: getHiddenFeatures(),
fullscreenActive: false,
THEMES: Object.values(THEME),
UNITS,
UNITS: Object.values(LENGTH_UNIT),
TIME_FORMATS: [TIME_24H, TIME_12H],
};
},

View file

@ -100,7 +100,7 @@ import VehicleComponent from "../Vehicles/Vehicle.vue";
import Phases from "./Phases.vue";
import LabelAndValue from "../Helper/LabelAndValue.vue";
import formatter, { POWER_UNIT } from "@/mixins/formatter";
import collector from "@/mixins/collector";
import collector from "@/mixins/collector.js";
import SettingsButton from "./SettingsButton.vue";
import SettingsModal from "./SettingsModal.vue";
import VehicleIcon from "../VehicleIcon";
@ -108,7 +108,14 @@ import SessionInfo from "./SessionInfo.vue";
import smartCostAvailable from "@/utils/smartCostAvailable";
import Modal from "bootstrap/js/dist/modal";
import { defineComponent, type PropType } from "vue";
import type { CHARGE_MODE, PHASES, Timeout, Vehicle, Forecast } from "@/types/evcc";
import type {
CHARGE_MODE,
PHASES,
Timeout,
Vehicle,
Forecast,
SMART_COST_TYPE,
} from "@/types/evcc";
export default defineComponent({
name: "Loadpoint",
@ -199,7 +206,7 @@ export default defineComponent({
pvRemaining: { type: Number, default: 0 },
pvAction: String,
smartCostLimit: { type: Number, default: null },
smartCostType: String,
smartCostType: String as PropType<SMART_COST_TYPE>,
smartCostActive: Boolean,
smartCostNextStart: String,
tariffGrid: Number,

View file

@ -61,7 +61,7 @@ import "@h2d2/shopicons/es/filled/lightning";
import Loadpoint from "./Loadpoint.vue";
import { defineComponent, type PropType } from "vue";
import type { LoadpointCompact, Timeout, Vehicle } from "@/types/evcc";
import type { LoadpointCompact, SMART_COST_TYPE, Timeout, Vehicle } from "@/types/evcc";
export default defineComponent({
name: "Loadpoints",
@ -69,7 +69,7 @@ export default defineComponent({
props: {
loadpoints: { type: Array as PropType<LoadpointCompact[]>, default: () => [] },
vehicles: { type: Array as PropType<Vehicle[]> },
smartCostType: String,
smartCostType: String as PropType<SMART_COST_TYPE>,
tariffGrid: Number,
tariffCo2: Number,
currency: String,

View file

@ -157,13 +157,13 @@
</template>
<script lang="ts">
import collector from "@/mixins/collector";
import collector from "@/mixins/collector.ts";
import formatter from "@/mixins/formatter";
import SmartCostLimit from "../Tariff/SmartCostLimit.vue";
import smartCostAvailable from "@/utils/smartCostAvailable";
import SettingsBatteryBoost from "./SettingsBatteryBoost.vue";
import { defineComponent, type PropType } from "vue";
import { CURRENCY, PHASES } from "@/types/evcc";
import { PHASES, CURRENCY, SMART_COST_TYPE } from "@/types/evcc";
const V = 230;
@ -198,7 +198,7 @@ export default defineComponent({
minCurrent: { type: Number, default: 0 },
title: String,
smartCostLimit: { type: Number as PropType<number | null>, default: null },
smartCostType: String,
smartCostType: String as PropType<SMART_COST_TYPE>,
tariffGrid: Number,
currency: String as PropType<CURRENCY>,
multipleLoadpoints: Boolean,

View file

@ -74,7 +74,7 @@ import Energyflow from "../Energyflow/Energyflow.vue";
import Loadpoints from "../Loadpoints/Loadpoints.vue";
import Footer from "../Footer/Footer.vue";
import formatter from "@/mixins/formatter";
import collector from "@/mixins/collector";
import collector from "@/mixins/collector.ts";
import WelcomeIcons from "./WelcomeIcons.vue";
import { defineComponent, type PropType } from "vue";
import type {
@ -84,6 +84,7 @@ import type {
Forecast,
LoadpointCompact,
Notification,
SMART_COST_TYPE,
Sponsor,
} from "@/types/evcc";
import type { Grid } from "./types";
@ -144,7 +145,7 @@ export default defineComponent({
uploadMessage: String,
uploadProgress: Number,
sponsor: { type: Object as PropType<Sponsor>, default: () => ({}) },
smartCostType: String,
smartCostType: String as PropType<SMART_COST_TYPE>,
fatal: Object,
forecast: Object as PropType<Forecast>,
},

View file

@ -85,11 +85,17 @@
<script lang="ts">
import formatter from "@/mixins/formatter";
import TariffChart from "./TariffChart.vue";
import { CO2_TYPE } from "@/units";
import api, { allowClientError } from "@/api";
import api, { allowClientError } from "@/api.ts";
import convertRates from "@/utils/convertRates";
import { defineComponent, type PropType } from "vue";
import type { Tariff, CURRENCY, Rate, SelectOption, Slot } from "@/types/evcc";
import {
type Tariff,
type CURRENCY,
type Rate,
type SelectOption,
type Slot,
SMART_COST_TYPE,
} from "@/types/evcc";
export default defineComponent({
name: "SmartCostLimit",
@ -100,7 +106,7 @@ export default defineComponent({
type: [Number, null] as PropType<number | null>,
required: true,
},
smartCostType: String,
smartCostType: String as PropType<SMART_COST_TYPE>,
tariffGrid: Number,
currency: String as PropType<CURRENCY>,
loadpointId: Number,
@ -118,7 +124,7 @@ export default defineComponent({
},
computed: {
isCo2(): boolean {
return this.smartCostType === CO2_TYPE;
return this.smartCostType === SMART_COST_TYPE.CO2;
},
costOptions(): SelectOption<number>[] {
const { max } = this.costRange(this.totalSlots);

View file

@ -37,7 +37,7 @@
import LabelAndValue from "../Helper/LabelAndValue.vue";
import AnimatedNumber from "../Helper/AnimatedNumber.vue";
import formatter from "@/mixins/formatter";
import { estimatedSoc, energyOptions, optionStep, fmtEnergy } from "@/utils/energyOptions";
import { estimatedSoc, energyOptions, optionStep, fmtEnergy } from "@/utils/energyOptions.ts";
import { defineComponent } from "vue";
export default defineComponent({
@ -45,9 +45,9 @@ export default defineComponent({
components: { LabelAndValue, AnimatedNumber },
mixins: [formatter],
props: {
limitEnergy: Number,
limitEnergy: { type: Number, default: 0 },
socPerKwh: Number,
chargedEnergy: Number,
chargedEnergy: { type: Number, required: true },
capacity: Number,
},
emits: ["limit-energy-updated"],
@ -56,10 +56,10 @@ export default defineComponent({
return energyOptions(
this.chargedEnergy,
this.capacity || 100,
this.socPerKwh,
this.fmtWh,
this.fmtPercentage,
this.$t("main.targetEnergy.noLimit")
this.$t("main.targetEnergy.noLimit"),
this.socPerKwh
);
},
step() {

View file

@ -183,9 +183,8 @@ import "@h2d2/shopicons/es/regular/eco1";
import "@h2d2/shopicons/es/regular/angledoublerightsmall";
import "@h2d2/shopicons/es/regular/clock";
import DynamicPriceIcon from "../MaterialIcon/DynamicPrice.vue";
import { DEFAULT_LOCALE } from "@/i18n";
import { DEFAULT_LOCALE } from "@/i18n.ts";
import formatter from "@/mixins/formatter";
import { CO2_TYPE } from "@/units";
import ClimaterIcon from "../MaterialIcon/Climater.vue";
import PlanEndIcon from "../MaterialIcon/PlanEnd.vue";
import PlanStartIcon from "../MaterialIcon/PlanStart.vue";
@ -202,7 +201,7 @@ import VehicleMinSocIcon from "../MaterialIcon/VehicleMinSoc.vue";
import WelcomeIcon from "../MaterialIcon/Welcome.vue";
import BatteryBoostIcon from "../MaterialIcon/BatteryBoost.vue";
import { defineComponent, type PropType } from "vue";
import type { CURRENCY, Timeout } from "@/types/evcc";
import { SMART_COST_TYPE, type CURRENCY, type Timeout } from "@/types/evcc";
const REASON_AUTH = "waitingforauthorization";
const REASON_DISCONNECT = "disconnectrequired";
@ -455,7 +454,7 @@ export default defineComponent({
return this.$t(`${prefix}EnergySet`);
},
smartCostPrice() {
return this.smartCostType !== CO2_TYPE;
return this.smartCostType !== SMART_COST_TYPE.CO2;
},
smartCostNowVisible() {
if (this.smartCostPrice) {

View file

@ -68,7 +68,7 @@
</template>
<script lang="ts">
import collector from "@/mixins/collector";
import collector from "@/mixins/collector.ts";
import formatter, { POWER_UNIT } from "@/mixins/formatter";
import LabelAndValue from "../Helper/LabelAndValue.vue";
import Title from "./Title.vue";
@ -77,7 +77,7 @@ import Status from "./Status.vue";
import ChargingPlan from "../ChargingPlans/ChargingPlan.vue";
import LimitSocSelect from "./LimitSocSelect.vue";
import LimitEnergySelect from "./LimitEnergySelect.vue";
import { distanceUnit, distanceValue } from "@/units";
import { distanceUnit, distanceValue } from "@/units.ts";
import { defineComponent, type PropType } from "vue";
import { CHARGE_MODE, type Forecast, type Vehicle } from "@/types/evcc";
@ -136,7 +136,7 @@ export default defineComponent({
vehicle: Object as PropType<Vehicle>,
vehicleDetectionActive: Boolean,
vehicleName: String,
vehicleRange: Number,
vehicleRange: { type: Number, default: 0 },
vehicles: Array,
vehicleSoc: { type: Number, default: 0 },
vehicleLimitSoc: Number,

View file

@ -1,6 +1,11 @@
import type { StaticPlan, RepeatingPlan } from "../components/ChargingPlans/types";
import type { ForecastSlot, SolarDetails } from "../components/Forecast/types";
// react-native-webview
interface WebView {
postMessage: (message: string) => void;
}
declare global {
interface Window {
app: any;
@ -10,6 +15,9 @@ declare global {
customCss: string;
};
}
interface Window {
ReactNativeWebView?: WebView;
}
}
export interface Auth {
@ -34,6 +42,17 @@ export interface State {
vehicles: Vehicle[];
}
export enum SMART_COST_TYPE {
CO2 = "co2",
PRICE_DYNAMIC = "pricedynamic",
PRICE_FORECAST = "priceforecast",
}
export enum LENGTH_UNIT {
KM = "km",
MILES = "mi",
}
export interface LoadpointCompact {
icon: string;
title: string;

View file

@ -1,39 +0,0 @@
import settings from "./settings";
const KM = "km";
const MILES = "mi";
export const UNITS = [KM, MILES];
export const CO2_TYPE = "co2";
export const PRICE_DYNAMIC_TYPE = "pricedynamic";
export const PRICE_FORECAST_TYPE = "priceforecast";
const MILES_FACTOR = 0.6213711922;
function isMiles() {
return settings.unit === MILES;
}
export function distanceValue(value) {
return isMiles() ? value * MILES_FACTOR : value;
}
export function distanceUnit() {
return isMiles() ? "mi" : "km";
}
export function getUnits() {
return isMiles() ? MILES : KM;
}
export function setUnits(value) {
settings.unit = value;
}
export function is12hFormat() {
return settings.is12hFormat;
}
export function set12hFormat(value) {
settings.is12hFormat = value;
}

32
assets/js/units.ts Normal file
View file

@ -0,0 +1,32 @@
import settings from "./settings";
import { LENGTH_UNIT } from "./types/evcc";
const MILES_FACTOR = 0.6213711922;
function isMiles() {
return settings.unit === LENGTH_UNIT.MILES;
}
export function distanceValue(value: number) {
return isMiles() ? value * MILES_FACTOR : value;
}
export function distanceUnit() {
return isMiles() ? "mi" : "km";
}
export function getUnits() {
return isMiles() ? LENGTH_UNIT.MILES : LENGTH_UNIT.KM;
}
export function setUnits(value: LENGTH_UNIT) {
settings.unit = value;
}
export function is12hFormat() {
return settings.is12hFormat;
}
export function set12hFormat(value: boolean) {
settings.is12hFormat = value;
}

View file

@ -1,3 +1,3 @@
export default function (obj1, obj2) {
export default function (obj1: any, obj2: any): boolean {
return JSON.stringify(obj1) === JSON.stringify(obj2);
}

View file

@ -1,6 +1,6 @@
import { POWER_UNIT } from "../mixins/formatter";
import formatter, { POWER_UNIT } from "../mixins/formatter";
export function optionStep(maxEnergy) {
export function optionStep(maxEnergy: number) {
if (maxEnergy < 0.1) return 0.005;
if (maxEnergy < 1) return 0.05;
if (maxEnergy < 2) return 0.1;
@ -11,7 +11,12 @@ export function optionStep(maxEnergy) {
return 5;
}
export function fmtEnergy(energy, step, fmtWh, zeroText) {
export function fmtEnergy(
energy: number = 0,
step: number,
fmtWh: InstanceType<typeof formatter>["fmtWh"],
zeroText: any
) {
if (energy === 0) {
return zeroText;
}
@ -20,12 +25,19 @@ export function fmtEnergy(energy, step, fmtWh, zeroText) {
return fmtWh(energy * 1e3, inKWh ? POWER_UNIT.KW : POWER_UNIT.W, true, digits);
}
export function estimatedSoc(energy, socPerKwh) {
export function estimatedSoc(energy: number, socPerKwh?: number) {
if (!socPerKwh) return null;
return Math.round(energy * socPerKwh);
}
export function energyOptions(fromEnergy, maxEnergy, socPerKwh, fmtWh, fmtPercentage, zeroText) {
export function energyOptions(
fromEnergy: number,
maxEnergy: number,
fmtWh: InstanceType<typeof formatter>["fmtWh"],
fmtPercentage: InstanceType<typeof formatter>["fmtPercentage"],
zeroText: string,
socPerKwh?: number
) {
const step = optionStep(maxEnergy);
const result = [];
for (let energy = 0; energy <= maxEnergy; energy += step) {
@ -36,7 +48,7 @@ export function energyOptions(fromEnergy, maxEnergy, socPerKwh, fmtWh, fmtPercen
text += ` (+${fmtPercentage(soc)})`;
}
// prevent rounding errors
const energyNormal = energy.toFixed(3) * 1;
const energyNormal = parseFloat(energy.toFixed(3));
result.push({ energy: energyNormal, text, disabled });
}
return result;

View file

@ -1,10 +1,12 @@
import type { FatalError } from "@/types/evcc";
const FATALS = ["configfile", "database"];
function isError(fatal) {
function isError(fatal?: FatalError) {
return !!fatal?.error;
}
export function isUserConfigError(fatal) {
export function isUserConfigError(fatal?: FatalError) {
if (!isError(fatal)) {
return false;
}
@ -16,6 +18,6 @@ export function isUserConfigError(fatal) {
return true;
}
export function isSystemError(fatal) {
export function isSystemError(fatal?: FatalError) {
return isError(fatal) && !isUserConfigError(fatal);
}

View file

@ -5,10 +5,10 @@ export function isApp() {
export function appDetection() {
if (isApp()) {
const $html = document.querySelector("html");
$html.classList.add("app");
$html?.classList.add("app");
}
}
export function sendToApp(data) {
export function sendToApp(data: { type: string }) {
window.ReactNativeWebView?.postMessage(JSON.stringify(data));
}

View file

@ -1,5 +0,0 @@
import { CO2_TYPE, PRICE_DYNAMIC_TYPE, PRICE_FORECAST_TYPE } from "../units";
export default function (smartCostType) {
return [CO2_TYPE, PRICE_DYNAMIC_TYPE, PRICE_FORECAST_TYPE].includes(smartCostType);
}

View file

@ -0,0 +1,5 @@
import { SMART_COST_TYPE } from "@/types/evcc";
export default function (smartCostType?: SMART_COST_TYPE) {
return smartCostType && Object.values(SMART_COST_TYPE).includes(smartCostType);
}

6
package-lock.json generated
View file

@ -455,9 +455,9 @@
}
},
"node_modules/@babel/parser": {
"version": "7.27.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.3.tgz",
"integrity": "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw==",
"version": "7.27.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz",
"integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.27.3"

View file

@ -91,8 +91,8 @@
"@storybook/vue3": "^8.6.4",
"@storybook/vue3-vite": "^8.6.4",
"@types/body-parser": "^1.19.6",
"@types/wait-on": "^5.3.4",
"@types/smoothscroll-polyfill": "^0.3.4",
"@types/wait-on": "^5.3.4",
"cross-env": "^7.0.3",
"eslint-plugin-storybook": "^0.12.0",
"prettier-plugin-sort-json": "^4.1.1",