mirror of
https://github.com/umami-software/umami.git
synced 2026-02-04 04:37:11 +01:00
Merged nav menus.
This commit is contained in:
parent
2f5e69229c
commit
554054d3a1
12 changed files with 164 additions and 47 deletions
|
|
@ -3,15 +3,16 @@ import pkg from './package.json' assert { type: 'json' };
|
||||||
|
|
||||||
const TRACKER_SCRIPT = '/script.js';
|
const TRACKER_SCRIPT = '/script.js';
|
||||||
|
|
||||||
const basePath = process.env.BASE_PATH;
|
const basePath = process.env.BASE_PATH || '';
|
||||||
const collectApiEndpoint = process.env.COLLECT_API_ENDPOINT;
|
const cloudMode = process.env.CLOUD_MODE || '';
|
||||||
const cloudMode = !!process.env.CLOUD_MODE;
|
const cloudUrl = process.env.CLOUD_URL || '';
|
||||||
const corsMaxAge = process.env.CORS_MAX_AGE;
|
const collectApiEndpoint = process.env.COLLECT_API_ENDPOINT || '';
|
||||||
const defaultLocale = process.env.DEFAULT_LOCALE;
|
const corsMaxAge = process.env.CORS_MAX_AGE || '';
|
||||||
const forceSSL = process.env.FORCE_SSL;
|
const defaultLocale = process.env.DEFAULT_LOCALE || '';
|
||||||
const frameAncestors = process.env.ALLOWED_FRAME_URLS ?? '';
|
const forceSSL = process.env.FORCE_SSL || '';
|
||||||
const trackerScriptName = process.env.TRACKER_SCRIPT_NAME;
|
const frameAncestors = process.env.ALLOWED_FRAME_URLS || '';
|
||||||
const trackerScriptURL = process.env.TRACKER_SCRIPT_URL;
|
const trackerScriptName = process.env.TRACKER_SCRIPT_NAME || '';
|
||||||
|
const trackerScriptURL = process.env.TRACKER_SCRIPT_URL || '';
|
||||||
|
|
||||||
const contentSecurityPolicy = [
|
const contentSecurityPolicy = [
|
||||||
`default-src 'self'`,
|
`default-src 'self'`,
|
||||||
|
|
@ -163,6 +164,7 @@ export default {
|
||||||
env: {
|
env: {
|
||||||
basePath,
|
basePath,
|
||||||
cloudMode,
|
cloudMode,
|
||||||
|
cloudUrl,
|
||||||
currentVersion: pkg.version,
|
currentVersion: pkg.version,
|
||||||
defaultLocale,
|
defaultLocale,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
48
pnpm-lock.yaml
generated
48
pnpm-lock.yaml
generated
|
|
@ -367,7 +367,44 @@ importers:
|
||||||
specifier: ^5.9.2
|
specifier: ^5.9.2
|
||||||
version: 5.9.2
|
version: 5.9.2
|
||||||
|
|
||||||
dist: {}
|
dist:
|
||||||
|
dependencies:
|
||||||
|
chart.js:
|
||||||
|
specifier: ^4.5.0
|
||||||
|
version: 4.5.0
|
||||||
|
chartjs-adapter-date-fns:
|
||||||
|
specifier: ^3.0.0
|
||||||
|
version: 3.0.0(chart.js@4.5.0)(date-fns@2.30.0)
|
||||||
|
colord:
|
||||||
|
specifier: ^2.9.2
|
||||||
|
version: 2.9.3
|
||||||
|
jsonwebtoken:
|
||||||
|
specifier: ^9.0.2
|
||||||
|
version: 9.0.2
|
||||||
|
lucide-react:
|
||||||
|
specifier: ^0.542.0
|
||||||
|
version: 0.542.0(react@19.1.1)
|
||||||
|
pure-rand:
|
||||||
|
specifier: ^7.0.1
|
||||||
|
version: 7.0.1
|
||||||
|
react-simple-maps:
|
||||||
|
specifier: ^2.3.0
|
||||||
|
version: 2.3.0(prop-types@15.8.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
react-use-measure:
|
||||||
|
specifier: ^2.0.4
|
||||||
|
version: 2.1.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
react-window:
|
||||||
|
specifier: ^1.8.6
|
||||||
|
version: 1.8.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
serialize-error:
|
||||||
|
specifier: ^12.0.0
|
||||||
|
version: 12.0.0
|
||||||
|
thenby:
|
||||||
|
specifier: ^1.3.4
|
||||||
|
version: 1.3.4
|
||||||
|
uuid:
|
||||||
|
specifier: ^11.1.0
|
||||||
|
version: 11.1.0
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
|
|
@ -5246,6 +5283,11 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
|
||||||
|
lucide-react@0.542.0:
|
||||||
|
resolution: {integrity: sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
|
||||||
lucide-react@0.543.0:
|
lucide-react@0.543.0:
|
||||||
resolution: {integrity: sha512-fpVfuOQO0V3HBaOA1stIiP/A2fPCXHIleRZL16Mx3HmjTYwNSbimhnFBygs2CAfU1geexMX5ItUcWBGUaqw5CA==}
|
resolution: {integrity: sha512-fpVfuOQO0V3HBaOA1stIiP/A2fPCXHIleRZL16Mx3HmjTYwNSbimhnFBygs2CAfU1geexMX5ItUcWBGUaqw5CA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -13376,6 +13418,10 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.1.1
|
react: 19.1.1
|
||||||
|
|
||||||
|
lucide-react@0.542.0(react@19.1.1):
|
||||||
|
dependencies:
|
||||||
|
react: 19.1.1
|
||||||
|
|
||||||
lucide-react@0.543.0(react@19.1.1):
|
lucide-react@0.543.0(react@19.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.1.1
|
react: 19.1.1
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,25 @@
|
||||||
'use client';
|
'use client';
|
||||||
import { Grid, Loading, Column } from '@umami/react-zen';
|
import { Grid, Loading, Column } from '@umami/react-zen';
|
||||||
import Script from 'next/script';
|
import Script from 'next/script';
|
||||||
import { usePathname } from 'next/navigation';
|
|
||||||
import { UpdateNotice } from './UpdateNotice';
|
import { UpdateNotice } from './UpdateNotice';
|
||||||
import { SideNav } from '@/app/(main)/SideNav';
|
import { SideNav } from '@/app/(main)/SideNav';
|
||||||
import { useLoginQuery, useConfig } from '@/components/hooks';
|
import { useLoginQuery, useConfig, useNavigation } from '@/components/hooks';
|
||||||
|
|
||||||
export function App({ children }) {
|
export function App({ children }) {
|
||||||
const { user, isLoading, error } = useLoginQuery();
|
const { user, isLoading, error } = useLoginQuery();
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
const pathname = usePathname();
|
const { pathname, router } = useNavigation();
|
||||||
|
|
||||||
if (isLoading || !config) {
|
if (isLoading || !config) {
|
||||||
return <Loading placement="absolute" />;
|
return <Loading placement="absolute" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
window.location.href = `${process.env.basePath || ''}/login`;
|
if (process.env.cloudMode) {
|
||||||
|
window.location.href = '/login';
|
||||||
|
} else {
|
||||||
|
router.push('/login');
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Key } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
|
|
@ -12,8 +13,7 @@ import { Globe, LinkIcon, LogoSvg, Grid2x2, PanelLeft } from '@/components/icons
|
||||||
import { useMessages, useNavigation, useGlobalState } from '@/components/hooks';
|
import { useMessages, useNavigation, useGlobalState } from '@/components/hooks';
|
||||||
import { NavButton } from '@/components/input/NavButton';
|
import { NavButton } from '@/components/input/NavButton';
|
||||||
import { PanelButton } from '@/components/input/PanelButton';
|
import { PanelButton } from '@/components/input/PanelButton';
|
||||||
import { Key } from 'react';
|
import { LanguageButton } from '@/components/input/LanguageButton';
|
||||||
import { SettingsButton } from '@/components/input/SettingsButton';
|
|
||||||
|
|
||||||
export function SideNav(props: SidebarProps) {
|
export function SideNav(props: SidebarProps) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
@ -77,9 +77,9 @@ export function SideNav(props: SidebarProps) {
|
||||||
})}
|
})}
|
||||||
</SidebarSection>
|
</SidebarSection>
|
||||||
<SidebarSection justifyContent="flex-start">
|
<SidebarSection justifyContent="flex-start">
|
||||||
<Row>
|
<Row wrap="wrap">
|
||||||
<SettingsButton />
|
<LanguageButton />
|
||||||
{!isCollapsed && !hasNav && <ThemeButton />}
|
<ThemeButton />
|
||||||
</Row>
|
</Row>
|
||||||
</SidebarSection>
|
</SidebarSection>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
|
|
|
||||||
1
src/assets/switch.svg
Normal file
1
src/assets/switch.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" height="200px" width="200px" xmlns="http://www.w3.org/2000/svg"><path d="M16 3l4 4l-4 4"></path><path d="M10 7l10 0"></path><path d="M8 13l-4 4l4 4"></path><path d="M4 17l9 0"></path></svg>
|
||||||
|
After Width: | Height: | Size: 313 B |
|
|
@ -1,14 +1,14 @@
|
||||||
export * from 'lucide-react';
|
export * from 'lucide-react';
|
||||||
export {
|
export {
|
||||||
Logo as LogoSvg,
|
|
||||||
Compare as CompareSvg,
|
Compare as CompareSvg,
|
||||||
Funnel as FunnelSvg,
|
Funnel as FunnelSvg,
|
||||||
Lightning as LightningSvg,
|
Lightning as LightningSvg,
|
||||||
Location as LocationSvg,
|
Location as LocationSvg,
|
||||||
|
Logo as LogoSvg,
|
||||||
Magnet as MagnetSvg,
|
Magnet as MagnetSvg,
|
||||||
Money as MoneySvg,
|
Money as MoneySvg,
|
||||||
Network as NetworkSvg,
|
Network as NetworkSvg,
|
||||||
Path as PathSvg,
|
Path as PathSvg,
|
||||||
|
Switch as SwitchSvg,
|
||||||
Target as TargetSvg,
|
Target as TargetSvg,
|
||||||
AddUser as AddUserSvg,
|
|
||||||
} from '@/components/svg';
|
} from '@/components/svg';
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,10 @@ import {
|
||||||
Settings,
|
Settings,
|
||||||
User,
|
User,
|
||||||
Users,
|
Users,
|
||||||
|
SwitchSvg,
|
||||||
} from '@/components/icons';
|
} from '@/components/icons';
|
||||||
import { DOCS_URL } from '@/lib/constants';
|
import { DOCS_URL } from '@/lib/constants';
|
||||||
import * as url from 'node:url';
|
import { ArrowRight } from 'lucide-react';
|
||||||
|
|
||||||
export interface TeamsButtonProps {
|
export interface TeamsButtonProps {
|
||||||
showText?: boolean;
|
showText?: boolean;
|
||||||
|
|
@ -43,7 +44,7 @@ export function NavButton({ showText = true }: TeamsButtonProps) {
|
||||||
const label = teamId ? team?.name : user.username;
|
const label = teamId ? team?.name : user.username;
|
||||||
|
|
||||||
const getUrl = (url: string) => {
|
const getUrl = (url: string) => {
|
||||||
return cloudMode ? `${process.env.cloudUrl}/${url}` : url;
|
return cloudMode ? `${process.env.cloudUrl}${url}` : url;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAction = async () => {};
|
const handleAction = async () => {};
|
||||||
|
|
@ -75,42 +76,52 @@ export function NavButton({ showText = true }: TeamsButtonProps) {
|
||||||
</Pressable>
|
</Pressable>
|
||||||
<Popover placement="bottom start">
|
<Popover placement="bottom start">
|
||||||
<Column minWidth="300px">
|
<Column minWidth="300px">
|
||||||
<Menu
|
<Menu autoFocus="last" onAction={handleAction}>
|
||||||
selectionMode="single"
|
|
||||||
selectedKeys={selectedKeys}
|
|
||||||
autoFocus="last"
|
|
||||||
onAction={handleAction}
|
|
||||||
>
|
|
||||||
<MenuSection title={formatMessage(labels.myAccount)}>
|
|
||||||
<MenuItem id="user">
|
|
||||||
<IconLabel icon={<User />} label={user.username} />
|
|
||||||
</MenuItem>
|
|
||||||
</MenuSection>
|
|
||||||
<MenuSeparator />
|
|
||||||
<SubmenuTrigger>
|
<SubmenuTrigger>
|
||||||
<MenuItem id="teams" showChecked={false} showSubMenuIcon>
|
<MenuItem id="teams" showChecked={false} showSubMenuIcon>
|
||||||
<IconLabel icon={<Users />} label={formatMessage(labels.teams)} />
|
<IconLabel icon={<SwitchSvg />} label={formatMessage(labels.switchAccount)} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<Popover placement="right top">
|
<Popover placement="right top">
|
||||||
<Column minWidth="300px">
|
<Column minWidth="300px">
|
||||||
<Menu>
|
<Menu selectionMode="single" selectedKeys={selectedKeys}>
|
||||||
|
<MenuSection title={formatMessage(labels.myAccount)}>
|
||||||
|
<MenuItem id="user" href={getUrl('/')}>
|
||||||
|
<IconLabel icon={<User />} label={user.username} />
|
||||||
|
</MenuItem>
|
||||||
|
</MenuSection>
|
||||||
|
<MenuSeparator />
|
||||||
<MenuSection title={formatMessage(labels.teams)}>
|
<MenuSection title={formatMessage(labels.teams)}>
|
||||||
{user?.teams?.map(({ id, name }) => (
|
{user?.teams?.map(({ id, name }) => (
|
||||||
<MenuItem key={id} id={id}>
|
<MenuItem key={id} id={id} href={getUrl(`/teams/${id}`)}>
|
||||||
<Row alignItems="center" gap>
|
<IconLabel icon={<Users />}>
|
||||||
<Icon size="sm">
|
|
||||||
<Users />
|
|
||||||
</Icon>
|
|
||||||
<Text wrap="nowrap">{name}</Text>
|
<Text wrap="nowrap">{name}</Text>
|
||||||
</Row>
|
</IconLabel>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
|
{user?.teams?.length === 0 && (
|
||||||
|
<MenuItem id="manage-teams">
|
||||||
|
<a href="/settings/teams" style={{ width: '100%' }}>
|
||||||
|
<Row alignItems="center" justifyContent="space-between" gap>
|
||||||
|
<Text align="center">Manage teams</Text>
|
||||||
|
<Icon>
|
||||||
|
<ArrowRight />
|
||||||
|
</Icon>
|
||||||
|
</Row>
|
||||||
|
</a>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
</MenuSection>
|
</MenuSection>
|
||||||
</Menu>
|
</Menu>
|
||||||
</Column>
|
</Column>
|
||||||
</Popover>
|
</Popover>
|
||||||
</SubmenuTrigger>
|
</SubmenuTrigger>
|
||||||
<MenuItem id="settings" icon={<Settings />} label={formatMessage(labels.settings)} />
|
<MenuSeparator />
|
||||||
|
<MenuItem
|
||||||
|
id="settings"
|
||||||
|
href={getUrl('/settings')}
|
||||||
|
icon={<Settings />}
|
||||||
|
label={formatMessage(labels.settings)}
|
||||||
|
/>
|
||||||
{cloudMode && (
|
{cloudMode && (
|
||||||
<>
|
<>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
|
@ -132,14 +143,24 @@ export function NavButton({ showText = true }: TeamsButtonProps) {
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{user.isAdmin && (
|
{!cloudMode && user.isAdmin && (
|
||||||
<>
|
<>
|
||||||
<MenuSeparator />
|
<MenuSeparator />
|
||||||
<MenuItem id="/admin" icon={<LockKeyhole />} label={formatMessage(labels.admin)} />
|
<MenuItem
|
||||||
|
id="/admin"
|
||||||
|
href="/admin"
|
||||||
|
icon={<LockKeyhole />}
|
||||||
|
label={formatMessage(labels.admin)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<MenuSeparator />
|
<MenuSeparator />
|
||||||
<MenuItem id="/logout" icon={<LogOut />} label={formatMessage(labels.logout)} />
|
<MenuItem
|
||||||
|
id="logout"
|
||||||
|
href={getUrl('/logout')}
|
||||||
|
icon={<LogOut />}
|
||||||
|
label={formatMessage(labels.logout)}
|
||||||
|
/>
|
||||||
</Menu>
|
</Menu>
|
||||||
</Column>
|
</Column>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
|
||||||
|
|
@ -362,6 +362,7 @@ export const labels = defineMessages({
|
||||||
share: { id: 'label.share', defaultMessage: 'Share' },
|
share: { id: 'label.share', defaultMessage: 'Share' },
|
||||||
support: { id: 'label.support', defaultMessage: 'Support' },
|
support: { id: 'label.support', defaultMessage: 'Support' },
|
||||||
documentation: { id: 'label.documentation', defaultMessage: 'Documentation' },
|
documentation: { id: 'label.documentation', defaultMessage: 'Documentation' },
|
||||||
|
switchAccount: { id: 'label.switch-account', defaultMessage: 'Switch account' },
|
||||||
});
|
});
|
||||||
|
|
||||||
export const messages = defineMessages({
|
export const messages = defineMessages({
|
||||||
|
|
|
||||||
9
src/components/svg/Download.tsx
Normal file
9
src/components/svg/Download.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import type { SVGProps } from 'react';
|
||||||
|
const SvgDownload = (props: SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" {...props}>
|
||||||
|
<path d="M97.5 82.656V71.357a3.545 3.545 0 0 0-3.545-3.544H89.17a3.545 3.545 0 0 0-3.545 3.544v11.3c0 1.639-1.33 2.968-2.969 2.968H17.344a2.97 2.97 0 0 1-2.969-2.969V71.357a3.545 3.545 0 0 0-3.545-3.545H6.045A3.545 3.545 0 0 0 2.5 71.357v11.3C2.5 90.853 9.146 97.5 17.344 97.5h65.312c8.198 0 14.844-6.646 14.844-14.844" />
|
||||||
|
<path d="m29.68 44.105-3.387 3.388a3.545 3.545 0 0 0 0 5.014l19.506 19.506a5.94 5.94 0 0 0 8.397.005l.005-.005 19.506-19.506a3.545 3.545 0 0 0 0-5.014l-3.388-3.388a3.545 3.545 0 0 0-5.013 0l-9.368 9.368V6.045A3.545 3.545 0 0 0 52.393 2.5h-4.786a3.545 3.545 0 0 0-3.544 3.545v47.428l-9.369-9.368a3.545 3.545 0 0 0-5.013 0" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
export default SvgDownload;
|
||||||
12
src/components/svg/Export.tsx
Normal file
12
src/components/svg/Export.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import type { SVGProps } from 'react';
|
||||||
|
const SvgExport = (props: SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width={512} height={512} viewBox="0 0 24 24" {...props}>
|
||||||
|
<switch>
|
||||||
|
<g>
|
||||||
|
<path d="M8.7 7.7 11 5.4V15c0 .6.4 1 1 1s1-.4 1-1V5.4l2.3 2.3c.4.4 1 .4 1.4 0s.4-1 0-1.4l-4-4c-.1-.1-.2-.2-.3-.2-.2-.1-.5-.1-.8 0-.1 0-.2.1-.3.2l-4 4c-.4.4-.4 1 0 1.4s1 .4 1.4 0M21 14c-.6 0-1 .4-1 1v4c0 .6-.4 1-1 1H5c-.6 0-1-.4-1-1v-4c0-.6-.4-1-1-1s-1 .4-1 1v4c0 1.7 1.3 3 3 3h14c1.7 0 3-1.3 3-3v-4c0-.6-.4-1-1-1" />
|
||||||
|
</g>
|
||||||
|
</switch>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
export default SvgExport;
|
||||||
19
src/components/svg/Switch.tsx
Normal file
19
src/components/svg/Switch.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import type { SVGProps } from 'react';
|
||||||
|
const SvgSwitch = (props: SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={200}
|
||||||
|
height={200}
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="m16 3 4 4-4 4M10 7h10M8 13l-4 4 4 4M4 17h9" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
export default SvgSwitch;
|
||||||
|
|
@ -6,7 +6,9 @@ export { default as Bookmark } from './Bookmark';
|
||||||
export { default as Change } from './Change';
|
export { default as Change } from './Change';
|
||||||
export { default as Compare } from './Compare';
|
export { default as Compare } from './Compare';
|
||||||
export { default as Dashboard } from './Dashboard';
|
export { default as Dashboard } from './Dashboard';
|
||||||
|
export { default as Download } from './Download';
|
||||||
export { default as Expand } from './Expand';
|
export { default as Expand } from './Expand';
|
||||||
|
export { default as Export } from './Export';
|
||||||
export { default as Flag } from './Flag';
|
export { default as Flag } from './Flag';
|
||||||
export { default as Funnel } from './Funnel';
|
export { default as Funnel } from './Funnel';
|
||||||
export { default as Gear } from './Gear';
|
export { default as Gear } from './Gear';
|
||||||
|
|
@ -28,6 +30,7 @@ export { default as Redo } from './Redo';
|
||||||
export { default as Reports } from './Reports';
|
export { default as Reports } from './Reports';
|
||||||
export { default as Security } from './Security';
|
export { default as Security } from './Security';
|
||||||
export { default as Speaker } from './Speaker';
|
export { default as Speaker } from './Speaker';
|
||||||
|
export { default as Switch } from './Switch';
|
||||||
export { default as Tag } from './Tag';
|
export { default as Tag } from './Tag';
|
||||||
export { default as Target } from './Target';
|
export { default as Target } from './Target';
|
||||||
export { default as Visitor } from './Visitor';
|
export { default as Visitor } from './Visitor';
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue