mirror of
https://github.com/MbinOrg/mbin-website.git
synced 2025-06-29 14:48:57 +00:00
FEAT: initial setup and servers page
This commit is contained in:
parent
ca3e55be5d
commit
d3e53e7c47
26 changed files with 7773 additions and 0 deletions
28
.gitignore
vendored
Normal file
28
.gitignore
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
|
||||||
|
dist
|
||||||
|
.solid
|
||||||
|
.output
|
||||||
|
.vercel
|
||||||
|
.netlify
|
||||||
|
.vinxi
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
/.idea
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
|
||||||
|
# Temp
|
||||||
|
gitignore
|
||||||
|
|
||||||
|
# System Files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
32
README.md
Normal file
32
README.md
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
# SolidStart
|
||||||
|
|
||||||
|
Everything you need to build a Solid project, powered by [`solid-start`](https://start.solidjs.com);
|
||||||
|
|
||||||
|
## Creating a project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# create a new project in the current directory
|
||||||
|
npm init solid@latest
|
||||||
|
|
||||||
|
# create a new project in my-app
|
||||||
|
npm init solid@latest my-app
|
||||||
|
```
|
||||||
|
|
||||||
|
## Developing
|
||||||
|
|
||||||
|
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# or start the server and open the app in a new browser tab
|
||||||
|
npm run dev -- --open
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
Solid apps are built with _presets_, which optimise your project for deployment to different environments.
|
||||||
|
|
||||||
|
By default, `npm run build` will generate a Node app that you can run with `npm start`. To use a different preset, add it to the `devDependencies` in `package.json` and specify in your `app.config.js`.
|
||||||
|
|
||||||
|
## This project was created with the [Solid CLI](https://solid-cli.netlify.app)
|
8
app.config.ts
Normal file
8
app.config.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { defineConfig } from '@solidjs/start/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
ssr: false,
|
||||||
|
// server: {
|
||||||
|
// static: true,
|
||||||
|
// },
|
||||||
|
});
|
29
package.json
Normal file
29
package.json
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"name": "example-with-tailwindcss",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vinxi dev",
|
||||||
|
"build": "vinxi build",
|
||||||
|
"start": "vinxi start"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@iconify-icon/solid": "^2.1.1",
|
||||||
|
"@kobalte/core": "^0.13.3",
|
||||||
|
"@solidjs/router": "^0.13.6",
|
||||||
|
"@solidjs/start": "^1.0.2",
|
||||||
|
"autoprefixer": "^10.4.19",
|
||||||
|
"class-variance-authority": "^0.7.0",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"postcss": "^8.4.38",
|
||||||
|
"solid-js": "^1.8.17",
|
||||||
|
"solid-markdown": "^2.0.13",
|
||||||
|
"tailwind-merge": "^2.3.0",
|
||||||
|
"tailwindcss": "^3.4.4",
|
||||||
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"vinxi": "^0.3.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@9.4.0+sha512.f549b8a52c9d2b8536762f99c0722205efc5af913e77835dbccc3b0b0b2ca9e7dc8022b78062c17291c48e88749c70ce88eb5a74f1fa8c4bf5e18bb46c8bd83a"
|
||||||
|
}
|
6695
pnpm-lock.yaml
generated
Normal file
6695
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load diff
6
postcss.config.cjs
Normal file
6
postcss.config.cjs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 664 B |
98
src/app.css
Normal file
98
src/app.css
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--background: 240 10% 3.9%;
|
||||||
|
--foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--muted: 240 3.7% 15.9%;
|
||||||
|
--muted-foreground: 240 5% 64.9%;
|
||||||
|
|
||||||
|
--accent: 240 3.7% 15.9%;
|
||||||
|
--accent-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--popover: 240 10% 3.9%;
|
||||||
|
--popover-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--border: 240 3.7% 15.9%;
|
||||||
|
--input: 240 3.7% 15.9%;
|
||||||
|
|
||||||
|
--card: 240 10% 3.9%;
|
||||||
|
--card-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--primary: 0 0% 98%;
|
||||||
|
--primary-foreground: 240 5.9% 10%;
|
||||||
|
|
||||||
|
--secondary: 240 3.7% 15.9%;
|
||||||
|
--secondary-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--destructive: 0 62.8% 30.6%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--info: 204 94% 94%;
|
||||||
|
--info-foreground: 199 89% 48%;
|
||||||
|
|
||||||
|
--success: 149 80% 90%;
|
||||||
|
--success-foreground: 160 84% 39%;
|
||||||
|
|
||||||
|
--warning: 48 96% 89%;
|
||||||
|
--warning-foreground: 25 95% 53%;
|
||||||
|
|
||||||
|
--error: 0 93% 94%;
|
||||||
|
--error-foreground: 0 84% 60%;
|
||||||
|
|
||||||
|
--ring: 240 4.9% 83.9%;
|
||||||
|
|
||||||
|
--radius: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
font-feature-settings: 'rlig' 1, 'calt' 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.container {
|
||||||
|
@apply px-4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #020617;
|
||||||
|
color: rgb(255, 255, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h1 {
|
||||||
|
@apply mt-2 text-5xl;
|
||||||
|
}
|
||||||
|
.markdown h2 {
|
||||||
|
@apply mt-2 text-4xl;
|
||||||
|
}
|
||||||
|
.markdown h3 {
|
||||||
|
@apply mt-2 text-3xl;
|
||||||
|
}
|
||||||
|
.markdown h4 {
|
||||||
|
@apply mt-2 text-2xl;
|
||||||
|
}
|
||||||
|
.markdown h5 {
|
||||||
|
@apply mt-2 text-xl;
|
||||||
|
}
|
||||||
|
.markdown h6 {
|
||||||
|
@apply mt-2 text-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown a {
|
||||||
|
@apply text-sky-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown p {
|
||||||
|
@apply my-2;
|
||||||
|
}
|
20
src/app.tsx
Normal file
20
src/app.tsx
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { Router } from "@solidjs/router";
|
||||||
|
import { FileRoutes } from "@solidjs/start/router";
|
||||||
|
import { Suspense } from "solid-js";
|
||||||
|
import Nav from "~/components/Nav";
|
||||||
|
import "./app.css";
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
return (
|
||||||
|
<Router
|
||||||
|
root={props => (
|
||||||
|
<>
|
||||||
|
<Nav />
|
||||||
|
<Suspense>{props.children}</Suspense>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<FileRoutes />
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
}
|
18
src/components/Chip.tsx
Normal file
18
src/components/Chip.tsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { ParentComponent, Show } from 'solid-js';
|
||||||
|
import { Icon } from '@iconify-icon/solid';
|
||||||
|
|
||||||
|
const Chip: ParentComponent<{ icon?: string; title?: string }> = (props) => {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
class="p-1 m-1 border rounded-lg inline-flex items-center"
|
||||||
|
title={props.title}
|
||||||
|
>
|
||||||
|
<Show when={props.icon}>
|
||||||
|
<Icon icon={props.icon!} class="pr-1" />
|
||||||
|
</Show>
|
||||||
|
{props.children}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Chip;
|
10
src/components/Markdown.tsx
Normal file
10
src/components/Markdown.tsx
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { clientOnly } from '@solidjs/start';
|
||||||
|
import { ParentComponent } from 'solid-js';
|
||||||
|
|
||||||
|
const ClientOnlyComp = clientOnly(() => import('./MarkdownInner'));
|
||||||
|
|
||||||
|
const Markdown: ParentComponent = (props) => {
|
||||||
|
return <ClientOnlyComp>{props.children}</ClientOnlyComp>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Markdown;
|
11
src/components/MarkdownInner.tsx
Normal file
11
src/components/MarkdownInner.tsx
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { clientOnly } from '@solidjs/start';
|
||||||
|
import { ParentComponent } from 'solid-js';
|
||||||
|
import { SolidMarkdown } from 'solid-markdown';
|
||||||
|
|
||||||
|
const MarkdownInner: ParentComponent = (props) => {
|
||||||
|
return (
|
||||||
|
<SolidMarkdown class="text-left markdown">{props.children}</SolidMarkdown>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MarkdownInner;
|
21
src/components/Nav.tsx
Normal file
21
src/components/Nav.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { useLocation } from '@solidjs/router';
|
||||||
|
|
||||||
|
export default function Nav() {
|
||||||
|
const location = useLocation();
|
||||||
|
const active = (path: string) =>
|
||||||
|
path == location.pathname
|
||||||
|
? 'border-sky-600'
|
||||||
|
: 'border-transparent hover:border-sky-600';
|
||||||
|
return (
|
||||||
|
<nav class="bg-sky-800">
|
||||||
|
<ul class="container flex items-center p-3 text-gray-200">
|
||||||
|
<li class={`border-b-2 ${active('/')} mx-1.5 sm:mx-6`}>
|
||||||
|
<a href="/">Home</a>
|
||||||
|
</li>
|
||||||
|
<li class={`border-b-2 ${active('/servers')} mx-1.5 sm:mx-6`}>
|
||||||
|
<a href="/servers">Servers</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
82
src/components/ui/accordion.tsx
Normal file
82
src/components/ui/accordion.tsx
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
import { JSX, splitProps, ValidComponent } from "solid-js"
|
||||||
|
|
||||||
|
import * as AccordionPrimitive from "@kobalte/core/accordion"
|
||||||
|
import { PolymorphicProps } from "@kobalte/core/polymorphic"
|
||||||
|
|
||||||
|
import { cn } from "~/lib/utils"
|
||||||
|
|
||||||
|
const Accordion = AccordionPrimitive.Root
|
||||||
|
|
||||||
|
type AccordionItemProps<T extends ValidComponent = "div"> =
|
||||||
|
AccordionPrimitive.AccordionItemProps<T> & {
|
||||||
|
class?: string | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const AccordionItem = <T extends ValidComponent = "div">(
|
||||||
|
props: PolymorphicProps<T, AccordionItemProps<T>>
|
||||||
|
) => {
|
||||||
|
const [local, others] = splitProps(props as AccordionItemProps, ["class"])
|
||||||
|
return <AccordionPrimitive.Item class={cn("border-b", local.class)} {...others} />
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccordionTriggerProps<T extends ValidComponent = "button"> =
|
||||||
|
AccordionPrimitive.AccordionTriggerProps<T> & {
|
||||||
|
class?: string | undefined
|
||||||
|
children?: JSX.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
const AccordionTrigger = <T extends ValidComponent = "button">(
|
||||||
|
props: PolymorphicProps<T, AccordionTriggerProps<T>>
|
||||||
|
) => {
|
||||||
|
const [local, others] = splitProps(props as AccordionTriggerProps, ["class", "children"])
|
||||||
|
return (
|
||||||
|
<AccordionPrimitive.Header class="flex">
|
||||||
|
<AccordionPrimitive.Trigger
|
||||||
|
class={cn(
|
||||||
|
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-expanded]>svg]:rotate-180",
|
||||||
|
local.class
|
||||||
|
)}
|
||||||
|
{...others}
|
||||||
|
>
|
||||||
|
{local.children}
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="size-4 shrink-0 transition-transform duration-200"
|
||||||
|
>
|
||||||
|
<path d="M6 9l6 6l6 -6" />
|
||||||
|
</svg>
|
||||||
|
</AccordionPrimitive.Trigger>
|
||||||
|
</AccordionPrimitive.Header>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccordionContentProps<T extends ValidComponent = "div"> =
|
||||||
|
AccordionPrimitive.AccordionContentProps<T> & {
|
||||||
|
class?: string | undefined
|
||||||
|
children?: JSX.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
const AccordionContent = <T extends ValidComponent = "div">(
|
||||||
|
props: PolymorphicProps<T, AccordionContentProps<T>>
|
||||||
|
) => {
|
||||||
|
const [local, others] = splitProps(props as AccordionContentProps, ["class", "children"])
|
||||||
|
return (
|
||||||
|
<AccordionPrimitive.Content
|
||||||
|
class={cn(
|
||||||
|
"animate-accordion-up overflow-hidden text-sm transition-all data-[expanded]:animate-accordion-down",
|
||||||
|
local.class
|
||||||
|
)}
|
||||||
|
{...others}
|
||||||
|
>
|
||||||
|
<div class="pb-4 pt-0">{local.children}</div>
|
||||||
|
</AccordionPrimitive.Content>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
52
src/components/ui/button.tsx
Normal file
52
src/components/ui/button.tsx
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import { JSX, splitProps, ValidComponent } from "solid-js"
|
||||||
|
|
||||||
|
import * as ButtonPrimitive from "@kobalte/core/button"
|
||||||
|
import { PolymorphicProps } from "@kobalte/core/polymorphic"
|
||||||
|
import type { VariantProps } from "class-variance-authority"
|
||||||
|
import { cva } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "~/lib/utils"
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||||
|
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||||
|
outline: "border border-input hover:bg-accent hover:text-accent-foreground",
|
||||||
|
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||||
|
link: "text-primary underline-offset-4 hover:underline"
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: "h-10 px-4 py-2",
|
||||||
|
sm: "h-9 rounded-md px-3",
|
||||||
|
lg: "h-11 rounded-md px-8",
|
||||||
|
icon: "size-10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type ButtonProps<T extends ValidComponent = "button"> = ButtonPrimitive.ButtonRootProps<T> &
|
||||||
|
VariantProps<typeof buttonVariants> & { class?: string | undefined; children?: JSX.Element }
|
||||||
|
|
||||||
|
const Button = <T extends ValidComponent = "button">(
|
||||||
|
props: PolymorphicProps<T, ButtonProps<T>>
|
||||||
|
) => {
|
||||||
|
const [local, others] = splitProps(props as ButtonProps, ["variant", "size", "class"])
|
||||||
|
return (
|
||||||
|
<ButtonPrimitive.Root
|
||||||
|
class={cn(buttonVariants({ variant: local.variant, size: local.size }), local.class)}
|
||||||
|
{...others}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { ButtonProps }
|
||||||
|
export { Button, buttonVariants }
|
141
src/components/ui/dialog.tsx
Normal file
141
src/components/ui/dialog.tsx
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
import type { Component, ComponentProps, JSX, ValidComponent } from "solid-js"
|
||||||
|
import { splitProps } from "solid-js"
|
||||||
|
|
||||||
|
import * as DialogPrimitive from "@kobalte/core/dialog"
|
||||||
|
import { PolymorphicProps } from "@kobalte/core/polymorphic"
|
||||||
|
|
||||||
|
import { cn } from "~/lib/utils"
|
||||||
|
|
||||||
|
const Dialog = DialogPrimitive.Root
|
||||||
|
const DialogTrigger = DialogPrimitive.Trigger
|
||||||
|
|
||||||
|
const DialogPortal: Component<DialogPrimitive.DialogPortalProps> = (props) => {
|
||||||
|
const [, rest] = splitProps(props, ["children"])
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Portal {...rest}>
|
||||||
|
<div class="fixed inset-0 z-50 flex items-start justify-center sm:items-center">
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
</DialogPrimitive.Portal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DialogOverlayProps<T extends ValidComponent = "div"> =
|
||||||
|
DialogPrimitive.DialogOverlayProps<T> & { class?: string | undefined }
|
||||||
|
|
||||||
|
const DialogOverlay = <T extends ValidComponent = "div">(
|
||||||
|
props: PolymorphicProps<T, DialogOverlayProps<T>>
|
||||||
|
) => {
|
||||||
|
const [, rest] = splitProps(props as DialogOverlayProps, ["class"])
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Overlay
|
||||||
|
class={cn(
|
||||||
|
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[expanded]:animate-in data-[closed]:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0",
|
||||||
|
props.class
|
||||||
|
)}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DialogContentProps<T extends ValidComponent = "div"> =
|
||||||
|
DialogPrimitive.DialogContentProps<T> & {
|
||||||
|
class?: string | undefined
|
||||||
|
children?: JSX.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
const DialogContent = <T extends ValidComponent = "div">(
|
||||||
|
props: PolymorphicProps<T, DialogContentProps<T>>
|
||||||
|
) => {
|
||||||
|
const [, rest] = splitProps(props as DialogContentProps, ["class", "children"])
|
||||||
|
return (
|
||||||
|
<DialogPortal>
|
||||||
|
<DialogOverlay />
|
||||||
|
<DialogPrimitive.Content
|
||||||
|
class={cn(
|
||||||
|
"fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border bg-background p-6 shadow-lg duration-200 data-[expanded]:animate-in data-[closed]:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0 data-[closed]:zoom-out-95 data-[expanded]:zoom-in-95 data-[closed]:slide-out-to-left-1/2 data-[closed]:slide-out-to-top-[48%] data-[expanded]:slide-in-from-left-1/2 data-[expanded]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||||
|
props.class
|
||||||
|
)}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
<DialogPrimitive.CloseButton class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[expanded]:bg-accent data-[expanded]:text-muted-foreground">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="size-4"
|
||||||
|
>
|
||||||
|
<path d="M18 6l-12 12" />
|
||||||
|
<path d="M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
<span class="sr-only">Close</span>
|
||||||
|
</DialogPrimitive.CloseButton>
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</DialogPortal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const DialogHeader: Component<ComponentProps<"div">> = (props) => {
|
||||||
|
const [, rest] = splitProps(props, ["class"])
|
||||||
|
return (
|
||||||
|
<div class={cn("flex flex-col space-y-1.5 text-center sm:text-left", props.class)} {...rest} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const DialogFooter: Component<ComponentProps<"div">> = (props) => {
|
||||||
|
const [, rest] = splitProps(props, ["class"])
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", props.class)}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DialogTitleProps<T extends ValidComponent = "h2"> = DialogPrimitive.DialogTitleProps<T> & {
|
||||||
|
class?: string | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const DialogTitle = <T extends ValidComponent = "h2">(
|
||||||
|
props: PolymorphicProps<T, DialogTitleProps<T>>
|
||||||
|
) => {
|
||||||
|
const [, rest] = splitProps(props as DialogTitleProps, ["class"])
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Title
|
||||||
|
class={cn("text-lg font-semibold leading-none tracking-tight", props.class)}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DialogDescriptionProps<T extends ValidComponent = "p"> =
|
||||||
|
DialogPrimitive.DialogDescriptionProps<T> & {
|
||||||
|
class?: string | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const DialogDescription = <T extends ValidComponent = "p">(
|
||||||
|
props: PolymorphicProps<T, DialogDescriptionProps<T>>
|
||||||
|
) => {
|
||||||
|
const [, rest] = splitProps(props as DialogDescriptionProps, ["class"])
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Description
|
||||||
|
class={cn("text-sm text-muted-foreground", props.class)}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Dialog,
|
||||||
|
DialogTrigger,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogFooter,
|
||||||
|
DialogTitle,
|
||||||
|
DialogDescription
|
||||||
|
}
|
4
src/entry-client.tsx
Normal file
4
src/entry-client.tsx
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
// @refresh reload
|
||||||
|
import { mount, StartClient } from "@solidjs/start/client";
|
||||||
|
|
||||||
|
mount(() => <StartClient />, document.getElementById("app")!);
|
21
src/entry-server.tsx
Normal file
21
src/entry-server.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// @refresh reload
|
||||||
|
import { createHandler, StartServer } from "@solidjs/start/server";
|
||||||
|
|
||||||
|
export default createHandler(() => (
|
||||||
|
<StartServer
|
||||||
|
document={({ assets, children, scripts }) => (
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
{assets}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app">{children}</div>
|
||||||
|
{scripts}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
));
|
1
src/global.d.ts
vendored
Normal file
1
src/global.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/// <reference types="@solidjs/start/env" />
|
7
src/lib/utils.ts
Normal file
7
src/lib/utils.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import type { ClassValue } from "clsx"
|
||||||
|
import { clsx } from "clsx"
|
||||||
|
import { twMerge } from "tailwind-merge"
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs))
|
||||||
|
}
|
25
src/routes/[...404].tsx
Normal file
25
src/routes/[...404].tsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { A } from "@solidjs/router";
|
||||||
|
|
||||||
|
export default function NotFound() {
|
||||||
|
return (
|
||||||
|
<main class="text-center mx-auto text-gray-700 p-4">
|
||||||
|
<h1 class="max-6-xs text-6xl text-sky-700 font-thin uppercase my-16">Not Found</h1>
|
||||||
|
<p class="mt-8">
|
||||||
|
Visit{" "}
|
||||||
|
<a href="https://solidjs.com" target="_blank" class="text-sky-600 hover:underline">
|
||||||
|
solidjs.com
|
||||||
|
</a>{" "}
|
||||||
|
to learn how to build Solid apps.
|
||||||
|
</p>
|
||||||
|
<p class="my-4">
|
||||||
|
<A href="/" class="text-sky-600 hover:underline">
|
||||||
|
Home
|
||||||
|
</A>
|
||||||
|
{" - "}
|
||||||
|
<A href="/about" class="text-sky-600 hover:underline">
|
||||||
|
About Page
|
||||||
|
</A>
|
||||||
|
</p>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
14
src/routes/index.tsx
Normal file
14
src/routes/index.tsx
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { Button } from '~/components/ui/button';
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<main class="text-center mx-auto text-gray-700 p-4">
|
||||||
|
<h1 class="max-6-xs text-6xl text-sky-700 font-thin uppercase my-16">
|
||||||
|
Mbin
|
||||||
|
</h1>
|
||||||
|
<Button href="/servers" as="a">
|
||||||
|
Join a Server
|
||||||
|
</Button>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
319
src/routes/servers.tsx
Normal file
319
src/routes/servers.tsx
Normal file
|
@ -0,0 +1,319 @@
|
||||||
|
import { For, Show, createSignal } from 'solid-js';
|
||||||
|
import { createAsync, cache } from '@solidjs/router';
|
||||||
|
import Markdown from '~/components/Markdown';
|
||||||
|
import Chip from '~/components/Chip';
|
||||||
|
import { Button } from '~/components/ui/button';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from '~/components/ui/dialog';
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from '~/components/ui/accordion';
|
||||||
|
|
||||||
|
type Server = {
|
||||||
|
domain: string;
|
||||||
|
version: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
openRegistrations: boolean;
|
||||||
|
federationEnabled: boolean;
|
||||||
|
language: string;
|
||||||
|
contactEmail: string;
|
||||||
|
totalUsers: number;
|
||||||
|
activeHalfyearUsers: number;
|
||||||
|
activeMonthUsers: number;
|
||||||
|
localPosts: number;
|
||||||
|
localComments: number;
|
||||||
|
pages: {
|
||||||
|
about?: string;
|
||||||
|
contact?: string;
|
||||||
|
faq?: string;
|
||||||
|
privacyPolicy?: string;
|
||||||
|
terms?: string;
|
||||||
|
};
|
||||||
|
defederated: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const pageNames: Required<Server['pages']> = {
|
||||||
|
about: 'About',
|
||||||
|
contact: 'Contact',
|
||||||
|
faq: 'Frequently Asked Questions',
|
||||||
|
privacyPolicy: 'Privacy Policy',
|
||||||
|
terms: 'Terms of Service',
|
||||||
|
};
|
||||||
|
|
||||||
|
const getServers = cache(async () => {
|
||||||
|
'use server';
|
||||||
|
|
||||||
|
const fediverseObserver = await (
|
||||||
|
await fetch('https://api.fediverse.observer/', {
|
||||||
|
body: '{"query":"{nodes(softwarename:\\"mbin\\" status: \\"UP\\"){domain}}"}',
|
||||||
|
method: 'POST',
|
||||||
|
})
|
||||||
|
).json();
|
||||||
|
|
||||||
|
const servers: string[] = fediverseObserver.data.nodes.map((v) => v.domain);
|
||||||
|
|
||||||
|
return (
|
||||||
|
await Promise.allSettled(
|
||||||
|
servers.map(async (serverHost) => {
|
||||||
|
console.log('START:', serverHost);
|
||||||
|
|
||||||
|
const jsonNodeInfo = await (
|
||||||
|
await fetch(`https://${serverHost}/nodeinfo/2.1.json`)
|
||||||
|
).json();
|
||||||
|
|
||||||
|
if (jsonNodeInfo.software.name != 'mbin') {
|
||||||
|
throw new Error(
|
||||||
|
`${serverHost} software does not match mbin (skipped)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsonApiInfo = await (
|
||||||
|
await fetch(`https://${serverHost}/api/info`)
|
||||||
|
).json();
|
||||||
|
if (jsonApiInfo.websiteDomain != serverHost) {
|
||||||
|
throw new Error(`${serverHost} api not setup correctly (skipped)`);
|
||||||
|
}
|
||||||
|
const jsonApiInstance = await (
|
||||||
|
await fetch(`https://${serverHost}/api/instance`)
|
||||||
|
).json();
|
||||||
|
const jsonApiDefederated = await (
|
||||||
|
await fetch(`https://${serverHost}/api/defederated`)
|
||||||
|
).json();
|
||||||
|
|
||||||
|
console.log('FINISH:', serverHost);
|
||||||
|
|
||||||
|
const server: Server = {
|
||||||
|
domain: serverHost,
|
||||||
|
version: jsonNodeInfo.software.version,
|
||||||
|
name: jsonNodeInfo.metadata.nodeName,
|
||||||
|
description: jsonNodeInfo.metadata.nodeDescription,
|
||||||
|
openRegistrations: jsonNodeInfo.openRegistrations,
|
||||||
|
federationEnabled: jsonApiInfo.websiteFederationEnabled,
|
||||||
|
language: jsonApiInfo.websiteDefaultLang ?? 'en',
|
||||||
|
contactEmail: jsonApiInfo.websiteContactEmail,
|
||||||
|
totalUsers: jsonNodeInfo.usage.users.total,
|
||||||
|
activeHalfyearUsers: jsonNodeInfo.usage.users.activeHalfyear,
|
||||||
|
activeMonthUsers: jsonNodeInfo.usage.users.activeMonth,
|
||||||
|
localPosts: jsonNodeInfo.usage.localPosts,
|
||||||
|
localComments: jsonNodeInfo.usage.localComments,
|
||||||
|
pages: jsonApiInstance,
|
||||||
|
defederated: jsonApiDefederated.instances ?? [],
|
||||||
|
};
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.filter((v) => v.status == 'fulfilled')
|
||||||
|
.map((v) => v.value as Server);
|
||||||
|
}, 'users');
|
||||||
|
|
||||||
|
export const route = {
|
||||||
|
load: () => getServers(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const languageNames = new Intl.DisplayNames(['en'], {
|
||||||
|
type: 'language',
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function ServersPage() {
|
||||||
|
const servers = createAsync(() => getServers());
|
||||||
|
|
||||||
|
const [filterRegistration, setFilterRegistration] = createSignal(true);
|
||||||
|
const [langFilter, setLangFilter] = createSignal<string>('');
|
||||||
|
|
||||||
|
const resultServers = () => {
|
||||||
|
if (!servers()) return undefined;
|
||||||
|
|
||||||
|
return servers()!.filter(
|
||||||
|
(server) =>
|
||||||
|
(!filterRegistration() || server.openRegistrations) &&
|
||||||
|
(!langFilter() || server.language == langFilter()),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main class="text-center mx-auto text-gray-700 p-4 max-w-screen-xl">
|
||||||
|
<h1 class="max-6-xs text-6xl text-sky-600 font-extralight uppercase my-16">
|
||||||
|
Mbin Servers
|
||||||
|
</h1>
|
||||||
|
Open Registration Only:{' '}
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={filterRegistration()}
|
||||||
|
onChange={(e) => setFilterRegistration(e.target.checked)}
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
Language:
|
||||||
|
<select onChange={(e) => setLangFilter(e.target.value)}>
|
||||||
|
<option value="">All Languages</option>
|
||||||
|
<For each={[...new Set(servers()?.map((v) => v.language))]}>
|
||||||
|
{(lang) => <option value={lang}>{languageNames.of(lang)}</option>}
|
||||||
|
</For>
|
||||||
|
</select>
|
||||||
|
<div
|
||||||
|
class="grid"
|
||||||
|
style={{
|
||||||
|
'grid-template-columns': 'repeat(auto-fit, minmax(350px, 1fr))',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<For each={resultServers()}>
|
||||||
|
{(server) => {
|
||||||
|
const StatChips = () => (
|
||||||
|
<>
|
||||||
|
<Chip>Version {server.version}</Chip>
|
||||||
|
<Chip title="Language" icon="material-symbols:language">
|
||||||
|
{languageNames.of(server.language)}
|
||||||
|
</Chip>
|
||||||
|
<Chip title="Total users" icon="material-symbols:person">
|
||||||
|
{server.totalUsers}
|
||||||
|
</Chip>
|
||||||
|
<Chip title="Active users" icon="material-symbols:person-check">
|
||||||
|
{server.activeMonthUsers}
|
||||||
|
<span class="opacity-75">/month</span>
|
||||||
|
</Chip>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="border rounded-lg p-4 m-4 text-white block relative">
|
||||||
|
<a
|
||||||
|
class="text-3xl text-sky-600 block text-center"
|
||||||
|
href={`https://${server.domain}`}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={`https://${server.domain}/favicon.ico`}
|
||||||
|
class="inline-block size-16"
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
{server.domain}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<Show when={server.name != '' && server.name != server.domain}>
|
||||||
|
<div class="text-2xl text-sky-600 font-light">
|
||||||
|
{server.name}
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<p>{server.description}</p>
|
||||||
|
|
||||||
|
<StatChips />
|
||||||
|
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger
|
||||||
|
as={Button<'button'>}
|
||||||
|
variant="outline"
|
||||||
|
class="my-1 w-full"
|
||||||
|
>
|
||||||
|
More info
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent class="sm:max-w-screen-xl max-h-[95%] overflow-scroll">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{server.domain}</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
{server.description}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<StatChips />
|
||||||
|
<Chip
|
||||||
|
title="Active users"
|
||||||
|
icon="material-symbols:person-check"
|
||||||
|
>
|
||||||
|
{server.activeHalfyearUsers}
|
||||||
|
<span class="opacity-75">/halfyear</span>
|
||||||
|
</Chip>
|
||||||
|
<Chip title="Local posts" icon="material-symbols:news">
|
||||||
|
{server.localPosts}
|
||||||
|
</Chip>
|
||||||
|
<Chip
|
||||||
|
title="Local comments"
|
||||||
|
icon="material-symbols:comment"
|
||||||
|
>
|
||||||
|
{server.localComments}
|
||||||
|
</Chip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Accordion
|
||||||
|
multiple={false}
|
||||||
|
collapsible
|
||||||
|
defaultValue={['about']}
|
||||||
|
>
|
||||||
|
<For each={Object.entries(server.pages)}>
|
||||||
|
{([page, pageContent]) => (
|
||||||
|
<Show when={pageContent || page === 'contact'}>
|
||||||
|
<AccordionItem value={page}>
|
||||||
|
<AccordionTrigger>
|
||||||
|
{pageNames[page as keyof Server['pages']]}
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
<Show when={page == 'contact'}>
|
||||||
|
<Button
|
||||||
|
href={`mailto:${server.contactEmail}`}
|
||||||
|
as="a"
|
||||||
|
>
|
||||||
|
Email admin: {server.contactEmail}
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
|
<Markdown>{pageContent}</Markdown>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Show>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
<Show when={server.defederated.length}>
|
||||||
|
<AccordionItem value="defederated">
|
||||||
|
<AccordionTrigger>
|
||||||
|
Defederated Servers ({server.defederated.length})
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
<For each={server.defederated}>
|
||||||
|
{(server) => <Chip>{server}</Chip>}
|
||||||
|
</For>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Show>
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
<Show when={server.openRegistrations}>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
href={`https://${server.domain}/register`}
|
||||||
|
as="a"
|
||||||
|
>
|
||||||
|
Join
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</Show>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
<Show when={server.openRegistrations}>
|
||||||
|
<Button
|
||||||
|
href={`https://${server.domain}/register`}
|
||||||
|
as="a"
|
||||||
|
variant="default"
|
||||||
|
class="my-1 w-full"
|
||||||
|
>
|
||||||
|
Join
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
97
tailwind.config.cjs
Normal file
97
tailwind.config.cjs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
/**@type {import("tailwindcss").Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: ['./src/**/*.{ts,tsx}'],
|
||||||
|
theme: {
|
||||||
|
container: {
|
||||||
|
center: true,
|
||||||
|
padding: '2rem',
|
||||||
|
screens: {
|
||||||
|
'2xl': '1400px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
border: 'hsl(var(--border))',
|
||||||
|
input: 'hsl(var(--input))',
|
||||||
|
ring: 'hsl(var(--ring))',
|
||||||
|
background: 'hsl(var(--background))',
|
||||||
|
foreground: 'hsl(var(--foreground))',
|
||||||
|
primary: {
|
||||||
|
DEFAULT: 'hsl(var(--primary))',
|
||||||
|
foreground: 'hsl(var(--primary-foreground))',
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
DEFAULT: 'hsl(var(--secondary))',
|
||||||
|
foreground: 'hsl(var(--secondary-foreground))',
|
||||||
|
},
|
||||||
|
destructive: {
|
||||||
|
DEFAULT: 'hsl(var(--destructive))',
|
||||||
|
foreground: 'hsl(var(--destructive-foreground))',
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
DEFAULT: 'hsl(var(--info))',
|
||||||
|
foreground: 'hsl(var(--info-foreground))',
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
DEFAULT: 'hsl(var(--success))',
|
||||||
|
foreground: 'hsl(var(--success-foreground))',
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
DEFAULT: 'hsl(var(--warning))',
|
||||||
|
foreground: 'hsl(var(--warning-foreground))',
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
DEFAULT: 'hsl(var(--error))',
|
||||||
|
foreground: 'hsl(var(--error-foreground))',
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
DEFAULT: 'hsl(var(--muted))',
|
||||||
|
foreground: 'hsl(var(--muted-foreground))',
|
||||||
|
},
|
||||||
|
accent: {
|
||||||
|
DEFAULT: 'hsl(var(--accent))',
|
||||||
|
foreground: 'hsl(var(--accent-foreground))',
|
||||||
|
},
|
||||||
|
popover: {
|
||||||
|
DEFAULT: 'hsl(var(--popover))',
|
||||||
|
foreground: 'hsl(var(--popover-foreground))',
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
DEFAULT: 'hsl(var(--card))',
|
||||||
|
foreground: 'hsl(var(--card-foreground))',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
xl: 'calc(var(--radius) + 4px)',
|
||||||
|
lg: 'var(--radius)',
|
||||||
|
md: 'calc(var(--radius) - 2px)',
|
||||||
|
sm: 'calc(var(--radius) - 4px)',
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
'accordion-down': {
|
||||||
|
from: { height: 0 },
|
||||||
|
to: { height: 'var(--kb-accordion-content-height)' },
|
||||||
|
},
|
||||||
|
'accordion-up': {
|
||||||
|
from: { height: 'var(--kb-accordion-content-height)' },
|
||||||
|
to: { height: 0 },
|
||||||
|
},
|
||||||
|
'content-show': {
|
||||||
|
from: { opacity: 0, transform: 'scale(0.96)' },
|
||||||
|
to: { opacity: 1, transform: 'scale(1)' },
|
||||||
|
},
|
||||||
|
'content-hide': {
|
||||||
|
from: { opacity: 1, transform: 'scale(1)' },
|
||||||
|
to: { opacity: 0, transform: 'scale(0.96)' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
'accordion-down': 'accordion-down 0.2s ease-out',
|
||||||
|
'accordion-up': 'accordion-up 0.2s ease-out',
|
||||||
|
'content-show': 'content-show 0.2s ease-out',
|
||||||
|
'content-hide': 'content-hide 0.2s ease-out',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [require('tailwindcss-animate')],
|
||||||
|
};
|
23
tsconfig.json
Normal file
23
tsconfig.json
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"jsxImportSource": "solid-js",
|
||||||
|
"allowJs": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"strict": true,
|
||||||
|
"types": [
|
||||||
|
"vinxi/types/client"
|
||||||
|
],
|
||||||
|
"isolatedModules": true,
|
||||||
|
"paths": {
|
||||||
|
"~/*": [
|
||||||
|
"./src/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
ui.config.json
Normal file
11
ui.config.json
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"tsx": true,
|
||||||
|
"componentDir": "./src/components/ui",
|
||||||
|
"tailwind": {
|
||||||
|
"config": "tailwind.config.cjs",
|
||||||
|
"css": "src/app.css"
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"path": "~/*"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue