feat: Cluster Modal fully implemented
This commit is contained in:
parent
9c7b895d4c
commit
0c7115a374
12 changed files with 310 additions and 61 deletions
|
|
@ -47,7 +47,7 @@ pub async fn run_app<F: FnOnce(&mut tauri::App) + Send + 'static>(setup: F) {
|
|||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(ext::updater::plugin())
|
||||
.manage(ext::updater::State::default())
|
||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||
// .plugin(tauri_plugin_window_state::Builder::default().build())
|
||||
.menu(tauri::menu::Menu::new)
|
||||
.setup(move |app| {
|
||||
setup(app);
|
||||
|
|
|
|||
4
apps/desktop/src/assets/logos/curseforge.svg
Normal file
4
apps/desktop/src/assets/logos/curseforge.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 53.5">
|
||||
<path fill="currentColor" fill-rule="evenodd"
|
||||
d="M0 6.9082l27.9532.0756L26.269 0h73.7449l-.0164 8.1433C87.5 10.6348 78.6726 16.775 74.825 36.34H72.046l-1.5815 4.359h2.3079l3.9906 12.8087H37.961l3.9184-12.8086h2.325l-1.6467-4.359h-2.7642l-5.1516-13.7535C13.5203 21.3437 2.6748 16.9042 0 6.9082zM58.6772 39.062c-3.082-5.6216-5.1058-1.197-11.4454-6.9496-2.4727-2.2436-3.4588-8.8199 2.097-10.8301-2.5255 5.5749 4.0297 7.8558 5.5898 4.6685.709-1.4486 1.6039-4.1204-.9798-5.9518-1.392-.9867-1.9281-3.9125-.7364-5.5388.3837 1.2093 1.5938 2.5013 3.916 1.5411-4.9192-8.1404 2.934-10.9457 6.7104-10.5817-3.9686.441-6.025 3.977-4.2196 7.8269 1.15 2.452 2.9006 3.6589 3.4097 5.6657-1.6952.1263-2.1852.9065-2.1495 2.362.0268 1.0895 2.5496 2.6646 2.9494-.4456 1.175 2.4245-.2407 3.8496-1.0129 4.9984-1.7824 2.6515-.1036 5.4107 2.1097 3.3067.9547-.9074 1.6155-2.5812.4048-5.318 2.229 1.776 4.5057 6.5447.9079 10.677-1.756 2.017-6.9514 1.391-7.551 4.5693z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1 KiB |
8
apps/desktop/src/assets/logos/modrinth.svg
Normal file
8
apps/desktop/src/assets/logos/modrinth.svg
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 514">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M503.16 323.56c11.39-42.09 12.16-87.65.04-132.8C466.57 54.2299 326.04-26.8001 189.33 9.77991 83.8101 38.0199 11.3899 128.07.689941 230.47H43.99c10.3-83.14 69.75-155.7402 155.76-178.7602 106.3-28.45 215.38 28.9601 253.42 129.6702l-42.14 11.27c-19.39-46.85-58.46-81.2-104.73-95.8302l-7.74 43.8402c36.53 13.47 66.16 43.84 77 84.25 15.8 58.89-13.62 119.23-67 144.26l11.53 42.99c70.16-28.95 112.31-101.86 102.34-177.02l41.98-11.23c4.5 28.71 2.94 57.25-3.86 84.16l42.61 15.49Z"
|
||||
fill="currentColor" />
|
||||
<path
|
||||
d="M321.99 504.22C185.27 540.8 44.7501 459.77 8.11011 323.24 3.84011 307.31 1.17 291.33 0 275.46h43.27c1.09 11.91 3.1999 23.89 6.4099 35.83 3.36 12.51 7.7701 24.46 13.1101 35.78l38.59-23.15c-3.2501-7.5-5.99-15.32-8.17-23.45-24.04-89.6 29.2-181.7 118.92-205.7099 17-4.55 34.1-6.32 50.8-5.61L255.19 133c-10.46.05-21.08 1.42-31.66 4.25-66.22 17.73-105.52 85.7-87.78 151.84 1.1 4.07 2.38 8.04 3.84 11.9l49.35-29.61-14.87-39.43 46.6-47.87 58.9-12.69 17.05 20.99-27.15 27.5-23.68 7.45-16.92 17.39 8.29 23.07s16.79 17.84 16.82 17.85l23.72-6.31 16.88-18.54 36.86-11.67 10.98 24.7-38.03 46.63-63.73 20.18-28.58-31.82-49.82 29.89c25.54 29.08 63.94 45.23 103.75 41.86l11.53 42.99c-59.41 7.86-117.44-16.73-153.49-61.91l-38.4101 23.04C136.25 451.17 223.84 484.11 309.61 461.16c61.74-16.52 109.79-58.6 135.81-111.78l42.64 15.5c-30.89 66.28-89.84 118.94-166.07 139.34Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
20
apps/desktop/src/types/global.d.ts
vendored
20
apps/desktop/src/types/global.d.ts
vendored
|
|
@ -1,6 +1,26 @@
|
|||
declare namespace globalThis {
|
||||
export type WithIndex<T> = T & { index: number }
|
||||
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
|
||||
|
||||
type GrowToSize<T, N extends number, A extends T[]> = A['length'] extends N ? A : GrowToSize<T, N, [...A, T]>;
|
||||
export type FixedArray<T, N extends number> = GrowToSize<T, N, []>;
|
||||
|
||||
// Get size of enum as a type - https://stackoverflow.com/a/68317984/23592613
|
||||
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
||||
type UnionToOvlds<U> = UnionToIntersection<U extends any ? (f: U) => void : never>;
|
||||
type PopUnion<U> = UnionToOvlds<U> extends (a: infer A) => void ? A : never;
|
||||
export type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;
|
||||
export type UnionToArray<T, A extends unknown[] = []> = IsUnion<T> extends true ? UnionToArray<Exclude<T, PopUnion<T>>, [PopUnion<T>, ...A]> : [T, ...A];
|
||||
|
||||
// enum Example {
|
||||
// how = "how",
|
||||
// to = "to",
|
||||
// count = "count",
|
||||
// enum = "enum",
|
||||
// entries = "entries"
|
||||
// }
|
||||
|
||||
// type Result = UnionToArray<keyof typeof Example>['length'] // = 5
|
||||
}
|
||||
|
||||
// TODO remove in place for specta-generated types
|
||||
|
|
|
|||
|
|
@ -1,18 +1,23 @@
|
|||
import type { ParentProps } from 'solid-js';
|
||||
import { Transition } from 'solid-transition-group';
|
||||
import { type ParentProps, mergeProps, splitProps } from 'solid-js';
|
||||
import { Transition, type TransitionProps } from 'solid-transition-group';
|
||||
|
||||
function AnimatedRoutes(props: TransitionProps & ParentProps) {
|
||||
const defaultProps: TransitionProps = {
|
||||
mode: 'outin',
|
||||
enterClass: 'page-animation-enter',
|
||||
enterActiveClass: 'page-animation-enter-active',
|
||||
enterToClass: 'page-animation-enter-to',
|
||||
exitClass: 'page-animation-leave',
|
||||
exitActiveClass: 'page-animation-leave-active',
|
||||
exitToClass: 'page-animation-leave-to',
|
||||
};
|
||||
|
||||
const [split, rest] = splitProps(props, ['children']);
|
||||
const merged = mergeProps(defaultProps, rest);
|
||||
|
||||
function AnimatedRoutes(props: ParentProps) {
|
||||
return (
|
||||
<Transition
|
||||
mode="outin"
|
||||
enterClass="page-animation-enter"
|
||||
enterActiveClass="page-animation-enter-active"
|
||||
enterToClass="page-animation-enter-to"
|
||||
exitClass="page-animation-leave"
|
||||
exitActiveClass="page-animation-leave-active"
|
||||
exitToClass="page-animation-leave-to"
|
||||
>
|
||||
{props.children}
|
||||
<Transition {...merged}>
|
||||
{split.children}
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
.dropdown {
|
||||
@apply relative w-52;
|
||||
@apply relative h-full;
|
||||
|
||||
.list {
|
||||
@apply bg-secondary rounded-lg border border-gray-05 p-1 shadow-md shadow-black/30;
|
||||
@apply flex flex-col gap-2;
|
||||
@apply max-h-46 overflow-auto;
|
||||
}
|
||||
|
||||
.row {
|
||||
@apply w-full flex flex-row justify-start items-center gap-1.5 h-4 text-nowrap overflow-hidden;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { type Accessor, Index, type JSX, Match, type ParentProps, type ResolvedJSXElement, type Setter, Switch, children, createSignal, splitProps } from 'solid-js';
|
||||
import { ChevronDownIcon, ChevronUpIcon } from '@untitled-theme/icons-solid';
|
||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-solid';
|
||||
import Popup from '../overlay/Popup';
|
||||
import Button from './Button';
|
||||
import styles from './Dropdown.module.scss';
|
||||
|
|
@ -71,20 +72,18 @@ function Dropdown(props: DropdownProps) {
|
|||
ref.classList.add(...split.dropdownClass.split(' '));
|
||||
}}
|
||||
>
|
||||
<div class="bg-secondary rounded-lg border border-gray-05 p-1 shadow-md shadow-black/30">
|
||||
<div class="flex flex-col gap-2">
|
||||
<Index each={items()}>
|
||||
{(item, index) => (
|
||||
<div onClick={() => select(index)}>
|
||||
<div class="hover:bg-gray-05 p-2 rounded-lg flex flex-row gap-2 justify-between items-center">
|
||||
{item()}
|
||||
<div class={styles.selected! + (selected() === index ? ` ${styles.visible}` : '')} />
|
||||
</div>
|
||||
<OverlayScrollbarsComponent class={styles.list}>
|
||||
<Index each={items()}>
|
||||
{(item, index) => (
|
||||
<div onClick={() => select(index)}>
|
||||
<div class="hover:bg-gray-05 p-2 rounded-lg flex flex-row gap-2 justify-between items-center">
|
||||
{item()}
|
||||
<div class={styles.selected! + (selected() === index ? ` ${styles.visible}` : '')} />
|
||||
</div>
|
||||
)}
|
||||
</Index>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Index>
|
||||
</OverlayScrollbarsComponent>
|
||||
</Popup>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import type {
|
|||
} from 'solid-js';
|
||||
import { Portal } from 'solid-js/web';
|
||||
|
||||
type FullscreenOverlayProps = {
|
||||
export type FullscreenOverlayProps = {
|
||||
visible: Accessor<boolean>;
|
||||
setVisible: Setter<boolean>;
|
||||
mount?: Node | undefined;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import { type Accessor, type Context, Match, type ParentProps, type Setter, Show, Switch, createContext, createSignal, useContext } from 'solid-js';
|
||||
import { Server01Icon } from '@untitled-theme/icons-solid';
|
||||
import Modal, { type ModalProps } from '../Modal';
|
||||
import { type Accessor, type Context, Match, type ParentProps, type Setter, Switch, createContext, createEffect, createSignal, useContext } from 'solid-js';
|
||||
import { ArrowRightIcon, Server01Icon } from '@untitled-theme/icons-solid';
|
||||
import HeaderImage from '../../../../assets/images/header.png';
|
||||
import FullscreenOverlay, { type FullscreenOverlayProps } from '../FullscreenOverlay';
|
||||
import ClusterStepOne from './ClusterStepOne';
|
||||
import { ClusterStepTwo } from './ClusterStepTwo';
|
||||
import Button from '~ui/components/base/Button';
|
||||
|
||||
// Why the fuck do I need to use a context for all this???
|
||||
// TODO: Rewrite for use with the new Modal stacking system
|
||||
|
|
@ -13,8 +16,15 @@ type ClusterModalContextFunc = [
|
|||
|
||||
const ClusterModalContext = createContext<ClusterModalContextFunc>() as Context<ClusterModalContextFunc>;
|
||||
|
||||
export enum ClusterModalStages {
|
||||
STAGE_1_PROVIDER = 0,
|
||||
STAGE_2_GAME_SETUP = 1,
|
||||
}
|
||||
|
||||
type ClusterModalStagesLength = UnionToArray<keyof typeof ClusterModalStages>['length'];
|
||||
|
||||
export function ClusterModalController(props: ParentProps) {
|
||||
const [step, setStep] = createSignal(1);
|
||||
const [step, setStep] = createSignal<number>(ClusterModalStages.STAGE_1_PROVIDER);
|
||||
const [visible, setVisible] = createSignal(false);
|
||||
|
||||
const stepper: ClusterModalContextFunc = [
|
||||
|
|
@ -23,10 +33,28 @@ export function ClusterModalController(props: ParentProps) {
|
|||
setVisible,
|
||||
];
|
||||
|
||||
createEffect(() => {
|
||||
if (visible() === false)
|
||||
setStep(ClusterModalStages.STAGE_1_PROVIDER);
|
||||
});
|
||||
|
||||
const stepComponents = [
|
||||
ClusterStepOne,
|
||||
ClusterStepTwo,
|
||||
];
|
||||
|
||||
return (
|
||||
<ClusterModalContext.Provider value={stepper}>
|
||||
{props.children}
|
||||
<ClusterCreationModal visible={visible} setVisible={setVisible} step={step()} />
|
||||
<ClusterCreationModal
|
||||
visible={visible}
|
||||
setVisible={setVisible}
|
||||
step={step}
|
||||
setStep={setStep}
|
||||
buttonIsNext={step() !== ((Object.keys(ClusterModalStages).length / 2) - 1)}
|
||||
>
|
||||
{(stepComponents[step()]!)()}
|
||||
</ClusterCreationModal>
|
||||
</ClusterModalContext.Provider>
|
||||
);
|
||||
}
|
||||
|
|
@ -35,33 +63,94 @@ export function useClusterModalController() {
|
|||
return useContext(ClusterModalContext);
|
||||
}
|
||||
|
||||
type ClusterCreationModalProps = ModalProps & {
|
||||
step: number;
|
||||
type ClusterCreationModalProps = FullscreenOverlayProps & {
|
||||
step: Accessor<number>;
|
||||
setStep: Setter<number>;
|
||||
buttonIsNext: boolean;
|
||||
};
|
||||
|
||||
function ClusterCreationModal(props: ClusterCreationModalProps) {
|
||||
// const StepComponent = () => (
|
||||
// <Switch>
|
||||
// <Match when={step() === 1}>
|
||||
// <ClusterStepOne />
|
||||
// </Match>
|
||||
// </Switch>
|
||||
// );
|
||||
// Indexed by the current step
|
||||
const messages: FixedArray<string, ClusterModalStagesLength> = [
|
||||
'Provider selection',
|
||||
'Game Setup',
|
||||
];
|
||||
|
||||
let content!: HTMLDivElement;
|
||||
|
||||
// TODO: For animation
|
||||
function updateHeight() {
|
||||
// content.
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal {...props}>
|
||||
<div class="relative h-22">
|
||||
<div class="absolute top-0 left-0 w-full">
|
||||
<img src={HeaderImage} alt="Header Image" />
|
||||
</div>
|
||||
<div class="absolute top-0 left-0 w-full flex flex-row justify-center items-center gap-x-2">
|
||||
<Server01Icon />
|
||||
<div class="flex flex-col">
|
||||
<h1>New Cluster</h1>
|
||||
<span>agagwag</span>
|
||||
<FullscreenOverlay
|
||||
visible={props.visible}
|
||||
setVisible={props.setVisible}
|
||||
mount={props.mount}
|
||||
zIndex={props.zIndex || 1000}
|
||||
>
|
||||
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
||||
<div class="bg-primary rounded-lg text-center flex flex-col min-w-sm">
|
||||
<div class="flex relative h-25">
|
||||
<div class="absolute top-0 left-0 w-full h-full">
|
||||
<img class="h-full w-full rounded-t-lg" src={HeaderImage} alt="Header Image" />
|
||||
</div>
|
||||
<div
|
||||
class="absolute top-0 left-0 px-10 h-full flex flex-row justify-start items-center gap-x-4 bg-[radial-gradient(at_center,#00000077,transparent)]"
|
||||
>
|
||||
<Server01Icon class="w-8 h-8" />
|
||||
<div class="flex flex-col items-start justify-center">
|
||||
{/** weird positioning?? */}
|
||||
<h1 class="h-10 -mt-2">New Cluster</h1>
|
||||
<span>{messages[props.step()]}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col border border-white/5 rounded-b-lg">
|
||||
<div ref={content} class="p-3">
|
||||
{props.children}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-x-2 justify-end pt-0 p-3">
|
||||
<Switch>
|
||||
<Match when={props.step() === 0}>
|
||||
<Button
|
||||
children="Cancel"
|
||||
buttonStyle="ghost"
|
||||
onClick={() => props.setVisible(false)}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={props.step() >= 1}>
|
||||
<Button
|
||||
children="Previous"
|
||||
buttonStyle="ghost"
|
||||
onClick={() => props.setStep(prev => prev - 1)}
|
||||
/>
|
||||
</Match>
|
||||
</Switch>
|
||||
|
||||
<Switch>
|
||||
<Match when={props.buttonIsNext === true}>
|
||||
<Button
|
||||
children="Next"
|
||||
buttonStyle="primary"
|
||||
iconRight={<ArrowRightIcon />}
|
||||
onClick={() => props.setStep(prev => prev + 1)}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={props.buttonIsNext === false}>
|
||||
<Button
|
||||
children="Create"
|
||||
buttonStyle="primary"
|
||||
iconRight={<ArrowRightIcon />}
|
||||
/>
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</FullscreenOverlay>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,56 @@
|
|||
import type { JSX } from 'solid-js';
|
||||
import { User03Icon } from '@untitled-theme/icons-solid';
|
||||
import { For, type JSX } from 'solid-js';
|
||||
import ModrinthIcon from '~assets/logos/modrinth.svg?component-solid';
|
||||
import CurseforgeIcon from '~assets/logos/curseforge.svg?component-solid';
|
||||
|
||||
const providers: ProviderCardProps[] = [
|
||||
{
|
||||
name: 'New',
|
||||
icon: <User03Icon />,
|
||||
},
|
||||
{
|
||||
name: 'Modrinth',
|
||||
icon: <ModrinthIcon color="#1bd96a" />,
|
||||
},
|
||||
{
|
||||
name: 'Curseforge',
|
||||
icon: <CurseforgeIcon color="#F16436" />,
|
||||
},
|
||||
{
|
||||
name: 'New',
|
||||
icon: <User03Icon />,
|
||||
},
|
||||
{
|
||||
name: 'Modrinth',
|
||||
icon: <ModrinthIcon color="#1bd96a" />,
|
||||
},
|
||||
{
|
||||
name: 'Curseforge',
|
||||
icon: <CurseforgeIcon color="#F16436" />,
|
||||
},
|
||||
];
|
||||
|
||||
export default function ClusterStepOne() {
|
||||
return (
|
||||
<div class="grid grid-cols-3">
|
||||
helo
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<For each={providers}>
|
||||
{provider => <ProviderCard {...provider} />}
|
||||
</For>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface ProviderCardProps {
|
||||
icon: () => JSX.Element;
|
||||
icon: JSX.Element;
|
||||
name: string;
|
||||
};
|
||||
|
||||
function ProviderCard(props: ProviderCardProps) {
|
||||
return (
|
||||
<div class="flex flex-col justify-center items-center gap-y-2">
|
||||
<props.icon />
|
||||
<div class="flex flex-col justify-center items-center gap-y-2 py-2 px-4 hover:bg-component-bg-hover active:bg-component-bg-pressed rounded-lg">
|
||||
<div class="[&>svg]:(w-8 h-8)">
|
||||
{props.icon}
|
||||
</div>
|
||||
<span>{props.name}</span>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
import { TextInputIcon } from '@untitled-theme/icons-solid';
|
||||
import { For, type JSX, splitProps } from 'solid-js';
|
||||
import Dropdown from '~ui/components/base/Dropdown';
|
||||
import TextField from '~ui/components/base/TextField';
|
||||
import VanillaImage from '~assets/logos/vanilla.png';
|
||||
import FabricImage from '~assets/logos/fabric.png';
|
||||
import ForgeImage from '~assets/logos/forge.png';
|
||||
import QuiltImage from '~assets/logos/quilt.png';
|
||||
|
||||
const loaders: {
|
||||
name: string;
|
||||
icon: () => JSX.Element;
|
||||
}[] = [
|
||||
{
|
||||
name: 'Vanilla',
|
||||
icon: () => <img src={VanillaImage} />,
|
||||
},
|
||||
{
|
||||
name: 'Fabric',
|
||||
icon: () => <img src={FabricImage} />,
|
||||
},
|
||||
{
|
||||
name: 'Forge',
|
||||
icon: () => <img src={ForgeImage} />,
|
||||
},
|
||||
{
|
||||
name: 'Quilt',
|
||||
icon: () => <img src={QuiltImage} />,
|
||||
},
|
||||
];
|
||||
|
||||
export function ClusterStepTwo() {
|
||||
return (
|
||||
<div class="flex flex-col gap-y-4">
|
||||
<Option header="Name">
|
||||
<TextField placeholder="Default Name" iconLeft={<TextInputIcon />} />
|
||||
</Option>
|
||||
|
||||
<div class="flex flex-row gap-x-2">
|
||||
<Option class="flex-1" header="Versions">
|
||||
<Dropdown>
|
||||
<For each={Array.from({ length: 300 }, (_, i) => i).reverse()}>
|
||||
{version => (
|
||||
<p>{version}</p>
|
||||
)}
|
||||
</For>
|
||||
</Dropdown>
|
||||
</Option>
|
||||
<Option class="w-32" header="Loader">
|
||||
<Dropdown>
|
||||
<For each={loaders}>
|
||||
{loader => (
|
||||
<div class="flex flex-row gap-x-2">
|
||||
<div class="w-4 h-4">
|
||||
<loader.icon />
|
||||
</div>
|
||||
{loader.name}
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</Dropdown>
|
||||
</Option>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type OptionProps = {
|
||||
header: string;
|
||||
} & JSX.HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
function Option(props: OptionProps) {
|
||||
const [split, rest] = splitProps(props, ['header', 'class']);
|
||||
|
||||
return (
|
||||
<div {...rest} class={`flex flex-col gap-y-2 items-stretch ${split.class || ''}`}>
|
||||
<h3 class="text-md font-semibold uppercase text-fg-secondary text-left">{props.header}</h3>
|
||||
<div class="max-h-8">
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { A } from '@solidjs/router';
|
||||
import { Download01Icon, FileCode01Icon, FilterLinesIcon, SearchMdIcon } from '@untitled-theme/icons-solid';
|
||||
import { For, createSignal } from 'solid-js';
|
||||
import Button from '~ui/components/base/Button';
|
||||
|
|
@ -115,7 +116,7 @@ function ModsRow(props: ModsRowProps) {
|
|||
<div class="flex flex-col flex-1 gap-3">
|
||||
<div class="flex flex-row flex-1 justify-between">
|
||||
<h4>{props.header}</h4>
|
||||
<a class="text-fg-secondary hover:text-fg-secondary-hover active:text-fg-secondary-pressed" href={`/browser/category?category=${props.category}`}>See all</a>
|
||||
<A class="text-fg-secondary hover:text-fg-secondary-hover active:text-fg-secondary-pressed" href={`category?category=${props.category}`}>See all</A>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-2">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue