mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-08 14:43:24 +00:00
feat: create tab and selector components
This commit is contained in:
79
inertia/components/common/form/selector.tsx
Normal file
79
inertia/components/common/form/selector.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { InputHTMLAttributes, ReactNode, useEffect, useState } from 'react';
|
||||
import Select, {
|
||||
FormatOptionLabelMeta,
|
||||
GroupBase,
|
||||
OptionsOrGroups,
|
||||
} from 'react-select';
|
||||
import FormField from '~/components/common/form/_form_field';
|
||||
|
||||
type Option = { label: string | number; value: string | number };
|
||||
|
||||
interface SelectorProps
|
||||
extends Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange'> {
|
||||
label: string;
|
||||
name: string;
|
||||
errors?: string[];
|
||||
options: OptionsOrGroups<Option, GroupBase<Option>>;
|
||||
value: number | string;
|
||||
onChangeCallback?: (value: number | string) => void;
|
||||
formatOptionLabel?: (
|
||||
data: Option,
|
||||
formatOptionLabelMeta: FormatOptionLabelMeta<Option>
|
||||
) => ReactNode;
|
||||
}
|
||||
|
||||
export default function Selector({
|
||||
name,
|
||||
label,
|
||||
value,
|
||||
errors = [],
|
||||
options,
|
||||
onChangeCallback,
|
||||
formatOptionLabel,
|
||||
required = false,
|
||||
...props
|
||||
}: SelectorProps): JSX.Element {
|
||||
const theme = useTheme();
|
||||
const [selectorValue, setSelectorValue] = useState<Option>();
|
||||
|
||||
useEffect(() => {
|
||||
if (options.length === 0) return;
|
||||
|
||||
const option = options.find((o: any) => o.value === value);
|
||||
if (option) {
|
||||
setSelectorValue(option as Option);
|
||||
}
|
||||
}, [options, value]);
|
||||
|
||||
const handleChange = (selectedOption: Option) => {
|
||||
setSelectorValue(selectedOption);
|
||||
if (onChangeCallback) {
|
||||
onChangeCallback(selectedOption.value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FormField required={required}>
|
||||
{label && (
|
||||
<label htmlFor={name} title={`${name} field`}>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<Select
|
||||
value={selectorValue}
|
||||
onChange={(newValue) => handleChange(newValue as Option)}
|
||||
options={options}
|
||||
isDisabled={props.disabled}
|
||||
menuPlacement="auto"
|
||||
formatOptionLabel={
|
||||
formatOptionLabel
|
||||
? (val, formatOptionLabelMeta) =>
|
||||
formatOptionLabel(val, formatOptionLabelMeta)
|
||||
: undefined
|
||||
}
|
||||
css={{ color: theme.colors.black }}
|
||||
/>
|
||||
</FormField>
|
||||
);
|
||||
}
|
||||
@@ -6,7 +6,6 @@ const ModalBody = styled.div({
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
overflow: 'auto',
|
||||
});
|
||||
|
||||
export default ModalBody;
|
||||
|
||||
@@ -3,7 +3,7 @@ import TransitionLayout from '~/components/layouts/_transition_layout';
|
||||
|
||||
const ModalContainer = styled(TransitionLayout)(({ theme }) => ({
|
||||
minWidth: '500px',
|
||||
background: theme.colors.secondary,
|
||||
background: theme.colors.background,
|
||||
padding: '1em',
|
||||
borderRadius: theme.border.radius,
|
||||
marginTop: '6em',
|
||||
|
||||
24
inertia/components/common/tabs/tab_item.tsx
Normal file
24
inertia/components/common/tabs/tab_item.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { rgba } from '~/lib/color';
|
||||
|
||||
const TabItem = styled.li<{ active?: boolean; danger?: boolean }>(
|
||||
({ theme, active, danger }) => {
|
||||
const activeColor = !danger ? theme.colors.primary : theme.colors.lightRed;
|
||||
return {
|
||||
userSelect: 'none',
|
||||
cursor: 'pointer',
|
||||
backgroundColor: active
|
||||
? rgba(activeColor, 0.15)
|
||||
: theme.colors.secondary,
|
||||
padding: '10px 20px',
|
||||
border: `1px solid ${active ? rgba(activeColor, 0.1) : theme.colors.secondary}`,
|
||||
borderBottom: `1px solid ${active ? rgba(activeColor, 0.25) : theme.colors.secondary}`,
|
||||
display: 'flex',
|
||||
gap: '0.35em',
|
||||
alignItems: 'center',
|
||||
transition: '.075s',
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export default TabItem;
|
||||
10
inertia/components/common/tabs/tab_list.tsx
Normal file
10
inertia/components/common/tabs/tab_list.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const TabList = styled.ul({
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
display: 'flex',
|
||||
listStyle: 'none',
|
||||
});
|
||||
|
||||
export default TabList;
|
||||
12
inertia/components/common/tabs/tab_panel.tsx
Normal file
12
inertia/components/common/tabs/tab_panel.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { rgba } from '~/lib/color';
|
||||
|
||||
const TabPanel = styled.div(({ theme }) => ({
|
||||
zIndex: 1,
|
||||
position: 'relative',
|
||||
border: `1px solid ${rgba(theme.colors.primary, 0.25)}`,
|
||||
padding: '20px',
|
||||
marginTop: '-1px',
|
||||
}));
|
||||
|
||||
export default TabPanel;
|
||||
42
inertia/components/common/tabs/tabs.tsx
Normal file
42
inertia/components/common/tabs/tabs.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { ReactNode, useState } from 'react';
|
||||
import { IconType } from 'react-icons/lib';
|
||||
import TabItem from '~/components/common/tabs/tab_item';
|
||||
import TabList from '~/components/common/tabs/tab_list';
|
||||
import TabPanel from '~/components/common/tabs/tab_panel';
|
||||
import TransitionLayout from '~/components/layouts/_transition_layout';
|
||||
|
||||
export interface Tab {
|
||||
title: string;
|
||||
content: ReactNode;
|
||||
icon?: IconType;
|
||||
danger?: boolean;
|
||||
}
|
||||
|
||||
export default function Tabs({ tabs }: { tabs: Tab[] }) {
|
||||
const [activeTabIndex, setActiveTabIndex] = useState<number>(0);
|
||||
|
||||
const handleTabClick = (index: number) => {
|
||||
setActiveTabIndex(index);
|
||||
};
|
||||
|
||||
return (
|
||||
<div css={{ width: '100%' }}>
|
||||
<TabList>
|
||||
{tabs.map(({ title, icon: Icon, danger }, index) => (
|
||||
<TabItem
|
||||
key={index}
|
||||
active={index === activeTabIndex}
|
||||
onClick={() => handleTabClick(index)}
|
||||
danger={danger ?? false}
|
||||
>
|
||||
{!!Icon && <Icon size={20} />}
|
||||
{title}
|
||||
</TabItem>
|
||||
))}
|
||||
</TabList>
|
||||
<TabPanel key={tabs[activeTabIndex].title}>
|
||||
<TransitionLayout>{tabs[activeTabIndex].content}</TransitionLayout>
|
||||
</TabPanel>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { FormEvent } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Checkbox from '~/components/common/form/checkbox';
|
||||
import Selector from '~/components/common/form/selector';
|
||||
import TextBox from '~/components/common/form/textbox';
|
||||
import BackToDashboard from '~/components/common/navigation/back_to_dashboard';
|
||||
import FormLayout from '~/components/layouts/form_layout';
|
||||
@@ -91,17 +92,18 @@ export default function FormLink({
|
||||
errors={errors?.description}
|
||||
disabled={disableInputs}
|
||||
/>
|
||||
<select
|
||||
onChange={({ target }) => setData('collectionId', target.value)}
|
||||
defaultValue={data.collectionId}
|
||||
disabled={disableInputs}
|
||||
>
|
||||
{collections?.map((collection) => (
|
||||
<option key={collection.id} value={collection.id}>
|
||||
{collection.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<Selector
|
||||
label={t('collection.collections')}
|
||||
name="collection"
|
||||
placeholder={t('collection.collections')}
|
||||
value={data.collectionId}
|
||||
onChangeCallback={(value) => setData('collectionId', value)}
|
||||
options={collections.map(({ id, name }) => ({
|
||||
label: name,
|
||||
value: id,
|
||||
}))}
|
||||
required
|
||||
/>
|
||||
<Checkbox
|
||||
label={t('favorite')}
|
||||
name="favorite"
|
||||
|
||||
@@ -1,32 +1,53 @@
|
||||
import dayjs from 'dayjs';
|
||||
import { ChangeEvent } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Selector from '~/components/common/form/selector';
|
||||
import { LS_LANG_KEY } from '~/constants';
|
||||
import { languages } from '~/i18n';
|
||||
|
||||
export default function LangSelector() {
|
||||
type Country = 'fr' | 'en';
|
||||
|
||||
export default function LangSelector({
|
||||
onSelected,
|
||||
}: {
|
||||
onSelected?: (country: Country) => void;
|
||||
}) {
|
||||
const { t, i18n } = useTranslation('common');
|
||||
|
||||
const onToggleLanguageClick = ({
|
||||
target,
|
||||
}: ChangeEvent<HTMLSelectElement>) => {
|
||||
dayjs.locale(target.value);
|
||||
i18n.changeLanguage(target.value);
|
||||
localStorage.setItem(LS_LANG_KEY, target.value);
|
||||
const onToggleLanguageClick = (newLocale: string) => {
|
||||
dayjs.locale(newLocale);
|
||||
i18n.changeLanguage(newLocale);
|
||||
localStorage.setItem(LS_LANG_KEY, newLocale);
|
||||
};
|
||||
|
||||
return (
|
||||
<select
|
||||
onChange={onToggleLanguageClick}
|
||||
name="lang-selector"
|
||||
id="lang-selector"
|
||||
defaultValue={i18n.language}
|
||||
>
|
||||
{languages.map((lang) => (
|
||||
<option value={lang} key={lang}>
|
||||
{t(`language.${lang}`)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<Selector
|
||||
name="lng-select"
|
||||
label={t('select-your-lang')}
|
||||
value={i18n.language}
|
||||
onChangeCallback={(value) => {
|
||||
onToggleLanguageClick(value.toString());
|
||||
if (onSelected) {
|
||||
setTimeout(() => onSelected(value.toString() as Country), 150);
|
||||
}
|
||||
}}
|
||||
options={languages.map((lang: Country) => ({
|
||||
label: t(`language.${lang}`),
|
||||
value: lang,
|
||||
}))}
|
||||
formatOptionLabel={(country) => (
|
||||
<div
|
||||
className="country-option"
|
||||
style={{ display: 'flex', gap: '.5em', alignItems: 'center' }}
|
||||
>
|
||||
<img
|
||||
src={`/icons/${country.value}.svg`}
|
||||
alt="country-image"
|
||||
height={24}
|
||||
width={24}
|
||||
/>
|
||||
<span>{country.label}</span>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -67,9 +67,10 @@ function GlobalStyles() {
|
||||
},
|
||||
|
||||
hr: {
|
||||
color: localTheme.colors.secondary,
|
||||
width: '100%',
|
||||
marginBlock: '1em',
|
||||
border: 0,
|
||||
borderTop: `1px solid ${localTheme.colors.background}`,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
114
package-lock.json
generated
114
package-lock.json
generated
@@ -40,6 +40,7 @@
|
||||
"react-hotkeys-hook": "^4.5.0",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-icons": "^5.2.1",
|
||||
"react-select": "^5.8.0",
|
||||
"react-swipeable": "^7.0.1",
|
||||
"react-toggle": "^4.1.3",
|
||||
"reflect-metadata": "^0.2.2"
|
||||
@@ -58,6 +59,7 @@
|
||||
"@types/node": "^20.12.12",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react-select": "^5.0.1",
|
||||
"@types/react-toggle": "^4.0.5",
|
||||
"@typescript-eslint/eslint-plugin": "^7.10.0",
|
||||
"@vitejs/plugin-react": "^4.3.0",
|
||||
@@ -2680,6 +2682,28 @@
|
||||
"npm": ">=6.14.13"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.2.tgz",
|
||||
"integrity": "sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg==",
|
||||
"dependencies": {
|
||||
"@floating-ui/utils": "^0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.5.tgz",
|
||||
"integrity": "sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw==",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.0.0",
|
||||
"@floating-ui/utils": "^0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/utils": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.2.tgz",
|
||||
"integrity": "sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw=="
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.11.14",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
|
||||
@@ -4230,14 +4254,11 @@
|
||||
"integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/@types/prop-types": {},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz",
|
||||
"integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
@@ -4252,6 +4273,16 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-select": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-select/-/react-select-5.0.1.tgz",
|
||||
"integrity": "sha512-h5Im0AP0dr4AVeHtrcvQrLV+gmPa7SA0AGdxl2jOhtwiE6KgXBFSogWw8az32/nusE6AQHlCOHQWjP1S/+oMWA==",
|
||||
"deprecated": "This is a stub types definition. react-select provides its own type definitions, so you do not need this installed.",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"react-select": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-toggle": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-toggle/-/react-toggle-4.0.5.tgz",
|
||||
@@ -4261,6 +4292,14 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-transition-group": {
|
||||
"version": "4.4.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz",
|
||||
"integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==",
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/semver": {
|
||||
"version": "7.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
|
||||
@@ -7650,6 +7689,15 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-helpers": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
||||
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.8.7",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/edge-error": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/edge-error/-/edge-error-4.0.1.tgz",
|
||||
@@ -9639,6 +9687,11 @@
|
||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/memoize-one": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
|
||||
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
|
||||
},
|
||||
"node_modules/merge2": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
@@ -10278,7 +10331,6 @@
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
@@ -10289,7 +10341,6 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -10297,8 +10348,7 @@
|
||||
"node_modules/prop-types/node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"peer": true
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/property-information": {
|
||||
"version": "6.5.0",
|
||||
@@ -10639,6 +10689,26 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-select": {
|
||||
"version": "5.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.8.0.tgz",
|
||||
"integrity": "sha512-TfjLDo58XrhP6VG5M/Mi56Us0Yt8X7xD6cDybC7yoRMUNm7BGO7qk8J0TLQOua/prb8vUOtsfnXZwfm30HGsAA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.0",
|
||||
"@emotion/cache": "^11.4.0",
|
||||
"@emotion/react": "^11.8.1",
|
||||
"@floating-ui/dom": "^1.0.1",
|
||||
"@types/react-transition-group": "^4.4.0",
|
||||
"memoize-one": "^6.0.0",
|
||||
"prop-types": "^15.6.0",
|
||||
"react-transition-group": "^4.3.0",
|
||||
"use-isomorphic-layout-effect": "^1.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-swipeable": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-swipeable/-/react-swipeable-7.0.1.tgz",
|
||||
@@ -10660,6 +10730,21 @@
|
||||
"react-dom": ">= 15.3.0 < 19"
|
||||
}
|
||||
},
|
||||
"node_modules/react-transition-group": {
|
||||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"dom-helpers": "^5.0.1",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.6.0",
|
||||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read-package-up": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz",
|
||||
@@ -12046,6 +12131,19 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-isomorphic-layout-effect": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
|
||||
"integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/v8-compile-cache-lib": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
"prettier": "^3.2.5",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.5",
|
||||
"@types/react-select": "^5.0.1",
|
||||
"vite": "^5.2.11"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -94,6 +95,7 @@
|
||||
"react-hotkeys-hook": "^4.5.0",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-icons": "^5.2.1",
|
||||
"react-select": "^5.8.0",
|
||||
"react-swipeable": "^7.0.1",
|
||||
"react-toggle": "^4.1.3",
|
||||
"reflect-metadata": "^0.2.2"
|
||||
@@ -120,4 +122,4 @@
|
||||
"lint-staged": {
|
||||
"*.js,*.ts,*.jsx,*.tsx": "eslint --cache --fix"
|
||||
}
|
||||
}
|
||||
}
|
||||
1
public/icons/en.svg
Normal file
1
public/icons/en.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 30"><clipPath id="a"><path d="M0 0v30h60V0z"/></clipPath><clipPath id="b"><path d="M30 15h30v15zv15H0zH0V0zV0h30z"/></clipPath><g clip-path="url(#a)"><path d="M0 0v30h60V0z" fill="#012169"/><path d="M0 0l60 30m0-30L0 30" stroke="#fff" stroke-width="6"/><path d="M0 0l60 30m0-30L0 30" clip-path="url(#b)" stroke="#C8102E" stroke-width="4"/><path d="M30 0v30M0 15h60" stroke="#fff" stroke-width="10"/><path d="M30 0v30M0 15h60" stroke="#C8102E" stroke-width="6"/></g></svg>
|
||||
|
After Width: | Height: | Size: 527 B |
1
public/icons/fr.svg
Normal file
1
public/icons/fr.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3 2"><path fill="#EC1920" d="M0 0h3v2H0z"/><path fill="#fff" d="M0 0h2v2H0z"/><path fill="#051440" d="M0 0h1v2H0z"/></svg>
|
||||
|
After Width: | Height: | Size: 175 B |
Reference in New Issue
Block a user