mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-22 19:15:08 +03:00
Redesign the download table, finish download dialog
This commit is contained in:
@@ -40,7 +40,6 @@ export function SplitButton({
|
|||||||
top?: number;
|
top?: number;
|
||||||
bottom?: number;
|
bottom?: number;
|
||||||
left: number;
|
left: number;
|
||||||
transform: string;
|
|
||||||
maxHeight: number;
|
maxHeight: number;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
const { width: windowWidth, height: windowHeight } = useWindowSize();
|
const { width: windowWidth, height: windowHeight } = useWindowSize();
|
||||||
@@ -58,9 +57,8 @@ export function SplitButton({
|
|||||||
const menuWidth = menuEl?.offsetWidth ?? 220;
|
const menuWidth = menuEl?.offsetWidth ?? 220;
|
||||||
const menuHeight = menuEl?.offsetHeight ?? 320;
|
const menuHeight = menuEl?.offsetHeight ?? 320;
|
||||||
|
|
||||||
const anchorX = rect.left + rect.width / 2;
|
const anchorCenterX = rect.left + rect.width / 2;
|
||||||
let left: number;
|
let left: number;
|
||||||
let transform: string;
|
|
||||||
let top: number | undefined;
|
let top: number | undefined;
|
||||||
let bottom: number | undefined;
|
let bottom: number | undefined;
|
||||||
let maxHeight: number;
|
let maxHeight: number;
|
||||||
@@ -82,18 +80,15 @@ export function SplitButton({
|
|||||||
maxHeight = Math.max(100, availableBelow);
|
maxHeight = Math.max(100, availableBelow);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (anchorX - menuWidth / 2 < EDGE_PAD) {
|
if (anchorCenterX - menuWidth / 2 < EDGE_PAD) {
|
||||||
left = EDGE_PAD;
|
left = EDGE_PAD;
|
||||||
transform = "none";
|
} else if (anchorCenterX + menuWidth / 2 > vw - EDGE_PAD) {
|
||||||
} else if (anchorX + menuWidth / 2 > vw - EDGE_PAD) {
|
|
||||||
left = vw - menuWidth - EDGE_PAD;
|
left = vw - menuWidth - EDGE_PAD;
|
||||||
transform = "none";
|
|
||||||
} else {
|
} else {
|
||||||
left = anchorX;
|
left = anchorCenterX - menuWidth / 2;
|
||||||
transform = "translateX(-50%)";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setMenuPosition({ top, bottom, left, transform, maxHeight });
|
setMenuPosition({ top, bottom, left, maxHeight });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const closeMenu = useCallback(() => {
|
const closeMenu = useCallback(() => {
|
||||||
@@ -245,7 +240,6 @@ export function SplitButton({
|
|||||||
? { bottom: menuPosition.bottom }
|
? { bottom: menuPosition.bottom }
|
||||||
: { top: menuPosition.top }),
|
: { top: menuPosition.top }),
|
||||||
left: menuPosition.left,
|
left: menuPosition.left,
|
||||||
transform: menuPosition.transform,
|
|
||||||
maxHeight: menuPosition.maxHeight,
|
maxHeight: menuPosition.maxHeight,
|
||||||
overflowY: "auto",
|
overflowY: "auto",
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -224,4 +224,8 @@ $menu-padding: 8px;
|
|||||||
|
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
scrollbar-color: rgba($color-dark-on-surface, 0.25) transparent;
|
scrollbar-color: rgba($color-dark-on-surface, 0.25) transparent;
|
||||||
|
|
||||||
|
mdui-list {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+4
-5
@@ -1,8 +1,8 @@
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { StyledDialog } from "./StyledDialog";
|
import { StyledDialog } from "@/core/components/StyledDialog";
|
||||||
import { MaterialButton, MaterialIcon } from "@/utils/material";
|
import { MaterialButton, MaterialIcon } from "@/utils/material";
|
||||||
import { OS_CONFIG, type DownloadOs } from "@/core/downloads/os";
|
import { OS_CONFIG, type DownloadOs } from "@/pages/home/os";
|
||||||
import styles from "./css/download-dialog.module.scss";
|
import styles from "@/pages/home/download-dialog.module.scss";
|
||||||
|
|
||||||
interface DownloadDialogProps {
|
interface DownloadDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -98,7 +98,7 @@ export function DownloadDialog({ open, onOpenChange, os }: DownloadDialogProps)
|
|||||||
<MaterialIcon name="download" className={styles.icon} />
|
<MaterialIcon name="download" className={styles.icon} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.titleBlock}>
|
<div className={styles.titleBlock}>
|
||||||
<h2 className={styles.title}>Thanks for downloading</h2>
|
<h2 className={styles.title}>Спасибо за скачивание!</h2>
|
||||||
<p className={styles.subtitle}>
|
<p className={styles.subtitle}>
|
||||||
FromChat для
|
FromChat для
|
||||||
<span className={styles.osName}>{osInfo.label}</span>
|
<span className={styles.osName}>{osInfo.label}</span>
|
||||||
@@ -110,4 +110,3 @@ export function DownloadDialog({ open, onOpenChange, os }: DownloadDialogProps)
|
|||||||
</StyledDialog>
|
</StyledDialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { MaterialIcon } from "@/utils/material";
|
import { MaterialIcon } from "@/utils/material";
|
||||||
import { GitHubLink, GITHUB_WEB, GITHUB_APP, GITHUB_LICENSE } from "./homeLinks";
|
import { GitHubLink, GITHUB_WEB, GITHUB_APP, GITHUB_LICENSE } from "@/pages/home/homeLinks";
|
||||||
import styles from "./home-footer.module.scss";
|
import styles from "@/pages/home/home-footer.module.scss";
|
||||||
|
|
||||||
export function HomeFooter() {
|
export function HomeFooter() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import { useUserStore } from "@/state/user";
|
import { useUserStore } from "@/state/user";
|
||||||
import useDownloadAppScreen from "@/core/hooks/useDownloadAppScreen";
|
import useDownloadAppScreen from "@/core/hooks/useDownloadAppScreen";
|
||||||
import { MaterialButton, MaterialIconButton } from "@/utils/material";
|
import { MaterialButton, MaterialIconButton } from "@/utils/material";
|
||||||
import { GitHubLink, SupportLink } from "./homeLinks";
|
import { GitHubLink, SupportLink } from "@/pages/home/homeLinks";
|
||||||
import styles from "./home-header.module.scss";
|
import styles from "@/pages/home/home-header.module.scss";
|
||||||
|
|
||||||
export function HomeHeader() {
|
export function HomeHeader() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useState, type ReactNode } from "react";
|
import { useState, type ReactNode } from "react";
|
||||||
import styles from "./home.module.scss";
|
import styles from "@/pages/home/home.module.scss";
|
||||||
import useDownloadAppScreen from "@/core/hooks/useDownloadAppScreen";
|
import useDownloadAppScreen from "@/core/hooks/useDownloadAppScreen";
|
||||||
import { MaterialButton, MaterialIcon, MaterialList, MaterialListItem } from "@/utils/material";
|
import { MaterialButton, MaterialIcon, MaterialIconButton, MaterialList, MaterialListItem } from "@/utils/material";
|
||||||
import generalChatScreenshot from "../../images/screenshots/general-chat.png";
|
import generalChatScreenshot from "@/images/screenshots/general-chat.png";
|
||||||
import dmScreenshot from "../../images/screenshots/dm.png";
|
import dmScreenshot from "@/images/screenshots/dm.png";
|
||||||
import windowsIcon from "../../images/windows.svg";
|
import windowsIcon from "@/images/windows.svg";
|
||||||
import linuxIcon from "../../images/linux.svg";
|
import linuxIcon from "@/images/linux.svg";
|
||||||
import macIcon from "../../images/mac.svg";
|
import macIcon from "@/images/mac.svg";
|
||||||
import { HomeHeader } from "./HomeHeader";
|
import { HomeHeader } from "@/pages/home/HomeHeader";
|
||||||
import { HomeFooter } from "./HomeFooter";
|
import { HomeFooter } from "@/pages/home/HomeFooter";
|
||||||
import { SplitButton } from "@/core/components/SplitButton";
|
import { SplitButton } from "@/core/components/SplitButton";
|
||||||
import { DownloadDialog } from "@/core/components/DownloadDialog";
|
import { DownloadDialog } from "@/pages/home/DownloadDialog";
|
||||||
import { OS_CONFIG, ALL_OS, detectOs, type DownloadOs } from "@/core/downloads/os";
|
import { OS_CONFIG, ALL_OS, detectOs, type DownloadOs } from "@/pages/home/os";
|
||||||
|
|
||||||
interface FeatureSectionProps {
|
interface FeatureSectionProps {
|
||||||
title: ReactNode;
|
title: ReactNode;
|
||||||
@@ -74,6 +74,14 @@ export default function HomePage() {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getButtonVariant = (os: DownloadOs): "filled" | "tonal" | "outlined" => {
|
||||||
|
const detectedOs = detectOs();
|
||||||
|
if (os === detectedOs) return "filled";
|
||||||
|
if (!isMobile && ["windows", "linux", "macos"].includes(os)) return "tonal";
|
||||||
|
if (isMobile && (os === "android" || os === "ios") && os !== detectedOs) return "tonal";
|
||||||
|
return "outlined";
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.homepage}>
|
<div className={styles.homepage}>
|
||||||
<HomeHeader />
|
<HomeHeader />
|
||||||
@@ -156,36 +164,63 @@ export default function HomePage() {
|
|||||||
Настольное приложение с уведомлениями и автономной работой
|
Настольное приложение с уведомлениями и автономной работой
|
||||||
или мобильное приложение для Android и iOS.
|
или мобильное приложение для Android и iOS.
|
||||||
</p>
|
</p>
|
||||||
<div className={styles.downloadTable}>
|
<table className={styles.downloadTable}>
|
||||||
<div className={styles.downloadTableHeader}>
|
<thead>
|
||||||
<span>Платформа</span>
|
<tr>
|
||||||
<span>Описание</span>
|
<th>Платформа</th>
|
||||||
<span />
|
<th>Описание</th>
|
||||||
</div>
|
<th />
|
||||||
{ALL_OS.map((os) => (
|
</tr>
|
||||||
<div key={os} className={styles.downloadTableRow}>
|
</thead>
|
||||||
<div className={styles.downloadTableOs}>
|
<tbody>
|
||||||
<MaterialIcon
|
{ALL_OS.map((os) => (
|
||||||
name={OS_CONFIG[os].icon}
|
<tr key={os}>
|
||||||
className={styles.downloadTableIcon}
|
<td className={styles.downloadTableOs}>
|
||||||
/>
|
<span className={styles.downloadTableOsContent}>
|
||||||
<span>{OS_CONFIG[os].label}</span>
|
{["windows", "linux", "macos"].includes(os) ? (
|
||||||
</div>
|
<span
|
||||||
<span className={styles.downloadTableDesc}>
|
className={styles.tableOsIcon}
|
||||||
{OS_CONFIG[os].description}
|
style={{
|
||||||
</span>
|
"--table-os-icon-url": `url("${os === "windows" ? windowsIcon : os === "linux" ? linuxIcon : macIcon}")`,
|
||||||
<div className={styles.downloadTableAction}>
|
} as React.CSSProperties}
|
||||||
<MaterialButton
|
/>
|
||||||
variant="outlined"
|
) : (
|
||||||
icon="download"
|
<MaterialIcon
|
||||||
onClick={() => triggerDownload(os)}
|
name={OS_CONFIG[os].icon}
|
||||||
>
|
className={styles.downloadTableIcon}
|
||||||
Скачать
|
/>
|
||||||
</MaterialButton>
|
)}
|
||||||
</div>
|
<span>{OS_CONFIG[os].label}</span>
|
||||||
</div>
|
</span>
|
||||||
))}
|
</td>
|
||||||
</div>
|
<td className={styles.downloadTableDesc}>
|
||||||
|
<span className={styles.downloadTableDescInner}>
|
||||||
|
{OS_CONFIG[os].description}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className={styles.downloadTableAction}>
|
||||||
|
<span className={styles.downloadTableActionInner}>
|
||||||
|
<MaterialButton
|
||||||
|
variant={getButtonVariant(os)}
|
||||||
|
icon="download"
|
||||||
|
className={styles.downloadButton}
|
||||||
|
onClick={() => triggerDownload(os)}
|
||||||
|
>
|
||||||
|
Скачать
|
||||||
|
</MaterialButton>
|
||||||
|
<MaterialIconButton
|
||||||
|
variant={getButtonVariant(os)}
|
||||||
|
icon="download"
|
||||||
|
className={styles.downloadButtonIcon}
|
||||||
|
onClick={() => triggerDownload(os)}
|
||||||
|
title={`Скачать ${OS_CONFIG[os].label}`}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@use "../../css/material" as *;
|
@use "@/css/material" as *;
|
||||||
|
|
||||||
$gradient-rainbow: linear-gradient(45deg, #9333EA, #6366F1, #3B82F6, #A855F7, #D946EF, #EC4899, #7E22CE);
|
$gradient-rainbow: linear-gradient(45deg, #9333EA, #6366F1, #3B82F6, #A855F7, #D946EF, #EC4899, #7E22CE);
|
||||||
$glow-purple: rgba(147, 51, 234, 0.5);
|
$glow-purple: rgba(147, 51, 234, 0.5);
|
||||||
|
|||||||
+2
-3
@@ -1,5 +1,5 @@
|
|||||||
@use "../../../css/material" as *;
|
@use "@/css/material" as *;
|
||||||
@use "../../../css/colors" as *;
|
@use "@/css/colors" as *;
|
||||||
@use "sass:color";
|
@use "sass:color";
|
||||||
|
|
||||||
.downloadDialog {
|
.downloadDialog {
|
||||||
@@ -111,4 +111,3 @@
|
|||||||
padding-inline: 16px;
|
padding-inline: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@use "../../css/material" as *;
|
@use "@/css/material" as *;
|
||||||
@use "home-shared" as shared;
|
@use "home-shared" as shared;
|
||||||
|
|
||||||
.homepageFooter {
|
.homepageFooter {
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
.footerLogo {
|
.footerLogo {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
background-image: url('../../images/logo_square.svg');
|
background-image: url('@/images/logo_square.svg');
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
@@ -111,11 +111,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.footerLinkIconSvgTelegram {
|
.footerLinkIconSvgTelegram {
|
||||||
mask-image: url('../../images/telegram.svg');
|
mask-image: url('@/images/telegram.svg');
|
||||||
-webkit-mask-image: url('../../images/telegram.svg');
|
-webkit-mask-image: url('@/images/telegram.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
.footerLinkIconSvgMax {
|
.footerLinkIconSvgMax {
|
||||||
mask-image: url('../../images/max.svg');
|
mask-image: url('@/images/max.svg');
|
||||||
-webkit-mask-image: url('../../images/max.svg');
|
-webkit-mask-image: url('@/images/max.svg');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@use "../../css/material" as *;
|
@use "@/css/material" as *;
|
||||||
@use "home-shared" as shared;
|
@use "home-shared" as shared;
|
||||||
|
|
||||||
.homepageHeader {
|
.homepageHeader {
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
.logoIcon {
|
.logoIcon {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
background-image: url('../../images/logo_square.svg');
|
background-image: url('@/images/logo_square.svg');
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@use "../../css/material" as *;
|
@use "@/css/material" as *;
|
||||||
@use "home-shared" as shared;
|
@use "home-shared" as shared;
|
||||||
|
|
||||||
// Shared variables (home-specific)
|
// Shared variables (home-specific)
|
||||||
@@ -15,7 +15,18 @@ $gradient-conic: conic-gradient(
|
|||||||
rgba(147, 51, 234, 0.5) 100%
|
rgba(147, 51, 234, 0.5) 100%
|
||||||
);
|
);
|
||||||
$radius-card: 20px;
|
$radius-card: 20px;
|
||||||
$radius-pill: 9999px;
|
$radius-pill: 25px;
|
||||||
|
|
||||||
|
// Download table variables
|
||||||
|
$download-cell-padding: 14px 20px;
|
||||||
|
$download-cell-gap: 12px;
|
||||||
|
$download-icon-size: 24px;
|
||||||
|
$download-col1-w: 35%;
|
||||||
|
$download-col2-w: 30%;
|
||||||
|
$download-col3-w: 35%;
|
||||||
|
$download-row-bg-hover: rgba($color-dark-surface-container-high, 0.6);
|
||||||
|
$download-header-bg: rgba($color-dark-surface-container-high, 0.4);
|
||||||
|
$download-text-color: $color-dark-on-surface-variant;
|
||||||
|
|
||||||
// Mixins
|
// Mixins
|
||||||
@mixin section-heading {
|
@mixin section-heading {
|
||||||
@@ -44,6 +55,30 @@ $radius-pill: 9999px;
|
|||||||
padding: 0 32px;
|
padding: 0 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin download-cell-inner($justify) {
|
||||||
|
display: flex;
|
||||||
|
justify-content: $justify;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin masked-icon($url-var) {
|
||||||
|
display: inline-block;
|
||||||
|
width: $download-icon-size;
|
||||||
|
height: $download-icon-size;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background-color: $download-text-color;
|
||||||
|
mask-image: var(#{$url-var});
|
||||||
|
-webkit-mask-image: var(#{$url-var});
|
||||||
|
mask-size: contain;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
-webkit-mask-size: contain;
|
||||||
|
-webkit-mask-repeat: no-repeat;
|
||||||
|
-webkit-mask-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
.homepage {
|
.homepage {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -96,7 +131,7 @@ $radius-pill: 9999px;
|
|||||||
.titleLogo {
|
.titleLogo {
|
||||||
width: 120px;
|
width: 120px;
|
||||||
height: 120px;
|
height: 120px;
|
||||||
background-image: url('../../images/logo_square.svg');
|
background-image: url('@/images/logo_square.svg');
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
@@ -212,66 +247,169 @@ $radius-pill: 9999px;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.downloadTable {
|
.downloadTable {
|
||||||
|
display: table;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 720px;
|
max-width: 720px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
border-radius: 20px;
|
border-radius: 16px;
|
||||||
background: rgba($color-dark-surface-container, 0.8);
|
border-collapse: separate;
|
||||||
border: 1px solid rgba($color-dark-outline, 0.3);
|
border-spacing: 0 6px;
|
||||||
padding: 8px 16px;
|
background: transparent;
|
||||||
display: flex;
|
border: none;
|
||||||
flex-direction: column;
|
table-layout: auto;
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.downloadTableHeader {
|
th:first-child,
|
||||||
display: grid;
|
td:first-child {
|
||||||
grid-template-columns: 2fr 2fr auto;
|
width: 1%;
|
||||||
padding: 8px 4px;
|
white-space: nowrap;
|
||||||
font-size: 12px;
|
}
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
color: $color-dark-on-surface-variant;
|
|
||||||
}
|
|
||||||
|
|
||||||
.downloadTableRow {
|
th:nth-child(2),
|
||||||
display: grid;
|
td:nth-child(2) {
|
||||||
grid-template-columns: 2fr 2fr auto;
|
width: 1%;
|
||||||
align-items: center;
|
white-space: nowrap;
|
||||||
padding: 8px 4px;
|
}
|
||||||
border-radius: 12px;
|
|
||||||
&:hover {
|
th:last-child,
|
||||||
background-color: rgba($color-dark-surface-container-high, 0.8);
|
td:last-child {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
display: table-header-group;
|
||||||
|
|
||||||
|
tr th {
|
||||||
|
padding: $download-cell-padding;
|
||||||
|
display: table-cell;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: $download-text-color;
|
||||||
|
background: $download-header-bg;
|
||||||
|
vertical-align: middle;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-radius: 12px 0 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-radius: 0 12px 12px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody {
|
||||||
|
display: table-row-group;
|
||||||
|
|
||||||
|
tr {
|
||||||
|
display: table-row;
|
||||||
|
background: $color-dark-surface-container-low;
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: $download-cell-padding;
|
||||||
|
display: table-cell;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
text-align: left;
|
||||||
|
border-radius: $radius-pill 0 0 $radius-pill;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
text-align: right;
|
||||||
|
border-radius: 0 $radius-pill $radius-pill 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover td {
|
||||||
|
background: $download-row-bg-hover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.downloadTableOs {
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
.downloadTableOsContent {
|
||||||
|
@include download-cell-inner(flex-start);
|
||||||
|
gap: $download-cell-gap;
|
||||||
|
min-height: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.downloadTableIcon {
|
||||||
|
font-size: $download-icon-size;
|
||||||
|
width: $download-icon-size;
|
||||||
|
height: $download-icon-size;
|
||||||
|
color: $download-text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableOsIcon {
|
||||||
|
@include masked-icon("--table-os-icon-url");
|
||||||
|
}
|
||||||
|
|
||||||
|
.downloadTableDesc {
|
||||||
|
font-size: 14px;
|
||||||
|
color: $download-text-color;
|
||||||
|
|
||||||
|
.downloadTableDescInner {
|
||||||
|
@include download-cell-inner(flex-start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.downloadTableAction {
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
.downloadTableActionInner {
|
||||||
|
@include download-cell-inner(flex-end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.downloadButton {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.downloadButtonIcon {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.downloadTableOs {
|
@media (max-width: 490px) {
|
||||||
display: flex;
|
.downloadContent .downloadTable {
|
||||||
align-items: center;
|
.downloadButton {
|
||||||
gap: 8px;
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.downloadButtonIcon {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.downloadTableIcon {
|
@media (max-width: 405px) {
|
||||||
font-size: 20px;
|
.downloadContent .downloadTable {
|
||||||
color: $color-dark-primary;
|
th:nth-child(2),
|
||||||
opacity: 0.9;
|
td:nth-child(2) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.downloadButton {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.downloadButtonIcon {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.downloadTableDesc {
|
|
||||||
font-size: 14px;
|
|
||||||
color: $color-dark-on-surface-variant;
|
|
||||||
}
|
|
||||||
|
|
||||||
.downloadTableAction {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.downloadTableActionLink {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,18 +479,7 @@ $radius-pill: 9999px;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.menuCustomIcon {
|
.menuCustomIcon {
|
||||||
display: inline-block;
|
@include masked-icon("--menu-custom-icon-url");
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
background-color: $color-dark-on-surface-variant;
|
|
||||||
mask-image: var(--menu-custom-icon-url);
|
|
||||||
-webkit-mask-image: var(--menu-custom-icon-url);
|
|
||||||
mask-size: contain;
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
mask-position: center;
|
|
||||||
-webkit-mask-size: contain;
|
|
||||||
-webkit-mask-repeat: no-repeat;
|
|
||||||
-webkit-mask-position: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keyframes for animations
|
// Keyframes for animations
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
export type DownloadOs = "windows" | "linux" | "macos" | "android" | "ios";
|
||||||
|
|
||||||
|
export const ALL_OS: DownloadOs[] = [
|
||||||
|
"windows",
|
||||||
|
"linux",
|
||||||
|
"macos",
|
||||||
|
"android",
|
||||||
|
"ios",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export interface OsInfo {
|
||||||
|
id: DownloadOs;
|
||||||
|
label: string;
|
||||||
|
description: string;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OS_CONFIG: Record<DownloadOs, OsInfo> = {
|
||||||
|
windows: { id: "windows", label: "Windows", description: "ПК", icon: "computer" },
|
||||||
|
linux: { id: "linux", label: "Linux", description: "ПК", icon: "computer" },
|
||||||
|
macos: { id: "macos", label: "macOS", description: "Apple", icon: "computer" },
|
||||||
|
android: { id: "android", label: "Android", description: "APK", icon: "android" },
|
||||||
|
ios: { id: "ios", label: "iOS", description: "iPhone, iPad", icon: "phone_iphone" },
|
||||||
|
};
|
||||||
|
|
||||||
|
export function detectOs(): DownloadOs {
|
||||||
|
if (typeof navigator === "undefined") {
|
||||||
|
return "android";
|
||||||
|
}
|
||||||
|
|
||||||
|
const ua = (navigator.userAgent || navigator.platform || "").toLowerCase();
|
||||||
|
const platform = (navigator as any).userAgentData?.platform?.toLowerCase?.() ?? "";
|
||||||
|
const haystack = `${ua} ${platform}`;
|
||||||
|
|
||||||
|
if (haystack.includes("android")) {
|
||||||
|
return "android";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (haystack.includes("iphone") || haystack.includes("ipad") || haystack.includes("ipod")) {
|
||||||
|
return "ios";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (haystack.includes("win")) {
|
||||||
|
return "windows";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (haystack.includes("mac")) {
|
||||||
|
return "macos";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (haystack.includes("linux")) {
|
||||||
|
return "linux";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "android";
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user