feat: rebuild onboarding experience
This commit is contained in:
1 parent
d091362db2
commit
e41605640b
249 files changed
+8216
-14060
No files matched your search
@@ -0,0 +1,68 @@
|
||||
.canvas {
|
||||
min-height: 8rem;
|
||||
aspect-ratio: 16 / 7;
|
||||
overflow: hidden;
|
||||
color: var(--mantine-color-dimmed);
|
||||
background:
|
||||
radial-gradient(
|
||||
circle at 15% 10%,
|
||||
color-mix(in srgb, var(--mantine-primary-color-filled) 12%, transparent),
|
||||
transparent 35%
|
||||
),
|
||||
var(--mantine-color-body);
|
||||
}
|
||||
|
||||
.lanes {
|
||||
display: grid;
|
||||
height: 100%;
|
||||
gap: rem(8px);
|
||||
padding: rem(12px);
|
||||
}
|
||||
|
||||
.lane {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
gap: rem(3px);
|
||||
padding: rem(4px);
|
||||
overflow: hidden;
|
||||
border: rem(1px) solid var(--mantine-color-default-border);
|
||||
border-radius: var(--mantine-radius-sm);
|
||||
background-image:
|
||||
linear-gradient(
|
||||
to right,
|
||||
color-mix(in srgb, var(--mantine-color-dimmed) 8%, transparent) rem(1px),
|
||||
transparent rem(1px)
|
||||
),
|
||||
linear-gradient(
|
||||
to bottom,
|
||||
color-mix(in srgb, var(--mantine-color-dimmed) 8%, transparent) rem(1px),
|
||||
transparent rem(1px)
|
||||
);
|
||||
background-size: rem(12px) rem(12px);
|
||||
}
|
||||
|
||||
.lane[data-lane="left"],
|
||||
.lane[data-lane="right"] {
|
||||
background-color: color-mix(in srgb, var(--mantine-primary-color-light) 40%, transparent);
|
||||
}
|
||||
|
||||
.tile {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
border: rem(1px) solid color-mix(in srgb, var(--mantine-primary-color-filled) 32%, transparent);
|
||||
border-radius: max(rem(2px), calc(var(--mantine-radius-default) / 2));
|
||||
background: color-mix(in srgb, var(--mantine-primary-color-filled) 18%, var(--mantine-color-body));
|
||||
box-shadow: 0 rem(1px) rem(3px) rgb(0 0 0 / 8%);
|
||||
}
|
||||
|
||||
.container {
|
||||
border-style: dashed;
|
||||
border-color: color-mix(in srgb, var(--mantine-color-teal-filled) 45%, transparent);
|
||||
background: color-mix(in srgb, var(--mantine-color-teal-light) 55%, var(--mantine-color-body));
|
||||
}
|
||||
|
||||
@media (max-width: $mantine-breakpoint-xs) {
|
||||
.canvas {
|
||||
min-height: 7rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { Box, Center } from "@mantine/core";
|
||||
import { IconLayoutGrid } from "@tabler/icons-react";
|
||||
|
||||
import type { BoardPreviewData } from "@homarr/boards/layout-preview";
|
||||
import { projectBoardLayout } from "@homarr/boards/layout-preview";
|
||||
import { boardLanes, getBoardLaneColumnCount, getRootSectionLane } from "@homarr/definitions";
|
||||
|
||||
import classes from "./board-layout-thumbnail.module.css";
|
||||
|
||||
interface BoardLayoutThumbnailProps {
|
||||
preview: BoardPreviewData | null;
|
||||
label: string;
|
||||
}
|
||||
|
||||
const maxThumbnailRows = 12;
|
||||
|
||||
export const BoardLayoutThumbnail = ({ preview, label }: BoardLayoutThumbnailProps) => {
|
||||
const layout = preview?.layouts.at(0);
|
||||
if (!preview || !layout) {
|
||||
return (
|
||||
<Center className={classes.canvas} role="img" aria-label={label}>
|
||||
<IconLayoutGrid size={28} stroke={1.4} aria-hidden />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
const elements = projectBoardLayout(preview, layout, layout);
|
||||
const roots = preview.sections.filter((section) => section.kind === "empty");
|
||||
const lanes = boardLanes.flatMap((lane) => {
|
||||
const root = roots.find((section) => getRootSectionLane(section.xOffset) === lane);
|
||||
const columnCount = getBoardLaneColumnCount(layout, lane);
|
||||
return root && columnCount > 0 ? [{ lane, root, columnCount }] : [];
|
||||
});
|
||||
if (lanes.length === 0) {
|
||||
return (
|
||||
<Center className={classes.canvas} role="img" aria-label={label}>
|
||||
<IconLayoutGrid size={28} stroke={1.4} aria-hidden />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box className={classes.canvas} role="img" aria-label={label}>
|
||||
<div
|
||||
className={classes.lanes}
|
||||
style={{ gridTemplateColumns: lanes.map(({ columnCount }) => `${columnCount}fr`).join(" ") }}
|
||||
>
|
||||
{lanes.map(({ lane, root, columnCount }) => {
|
||||
const laneElements = elements.filter(
|
||||
(element) =>
|
||||
element.sectionId === root.id &&
|
||||
element.xOffset >= 0 &&
|
||||
element.xOffset < columnCount &&
|
||||
element.yOffset >= 0 &&
|
||||
element.yOffset < maxThumbnailRows &&
|
||||
element.width > 0 &&
|
||||
element.height > 0,
|
||||
);
|
||||
const rowCount = Math.min(
|
||||
maxThumbnailRows,
|
||||
Math.max(4, ...laneElements.map((element) => element.yOffset + element.height)),
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={lane}
|
||||
className={classes.lane}
|
||||
data-lane={lane}
|
||||
style={{
|
||||
gridTemplateColumns: `repeat(${columnCount}, minmax(0, 1fr))`,
|
||||
gridTemplateRows: `repeat(${rowCount}, minmax(0, 1fr))`,
|
||||
}}
|
||||
>
|
||||
{laneElements.map((element) => (
|
||||
<span
|
||||
key={`${element.type}-${element.id}`}
|
||||
aria-hidden
|
||||
className={`${classes.tile} ${element.type === "section" ? classes.container : ""}`}
|
||||
style={{
|
||||
gridColumn: `${element.xOffset + 1} / span ${Math.min(element.width, columnCount - element.xOffset)}`,
|
||||
gridRow: `${element.yOffset + 1} / span ${Math.min(element.height, rowCount - element.yOffset)}`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -196,6 +196,7 @@ const ItemSelectModalContent = ({
|
||||
search={search}
|
||||
onSearchChange={setSearch}
|
||||
placeholder={`${t("item.create.search")}...`}
|
||||
ariaLabel={t("item.create.search")}
|
||||
onSearchKeyDown={(event) => {
|
||||
if (event.key === "Enter" && loadingSelection === null && filteredItems.length === 1) {
|
||||
const [item] = filteredItems;
|
||||
|
||||
Reference in new issue
Block a user