feat: Usable todolist app (not the prettiest yet but...)
This commit is contained in:
parent
9be964ef8f
commit
9d4019f3e4
16
rust_solid_cassandra/frontend/package-lock.json
generated
16
rust_solid_cassandra/frontend/package-lock.json
generated
@ -10,7 +10,8 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solidjs/router": "^0.5.1",
|
||||
"solid-js": "^1.5.1"
|
||||
"solid-js": "^1.5.1",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.4.13",
|
||||
@ -2136,6 +2137,14 @@
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
||||
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.4.tgz",
|
||||
@ -3605,6 +3614,11 @@
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"dev": true
|
||||
},
|
||||
"uuid": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
||||
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
|
||||
},
|
||||
"vite": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.4.tgz",
|
||||
|
@ -19,6 +19,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@solidjs/router": "^0.5.1",
|
||||
"solid-js": "^1.5.1"
|
||||
"solid-js": "^1.5.1",
|
||||
"uuid": "^9.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,47 @@
|
||||
import { Route, Routes } from '@solidjs/router';
|
||||
import { Component, lazy } from 'solid-js';
|
||||
import { Component, createEffect, createSignal, lazy } from 'solid-js';
|
||||
import { loggedInUser } from './pages/Login';
|
||||
import { createStore, Store, SetStoreFunction } from 'solid-js/store';
|
||||
import Navbar from './ui/Navbar';
|
||||
import { Todo } from './pages/Home';
|
||||
// Only load the components if we are navigating to them
|
||||
const Home = lazy(() => import('./pages/Home'));
|
||||
const Login = lazy(() => import('./pages/Login'));
|
||||
const Test = lazy(() => import('./pages/Test'));
|
||||
|
||||
export type User = {
|
||||
id: string;
|
||||
login: string;
|
||||
};
|
||||
|
||||
// Helper funciton to get a global state
|
||||
// https://stackoverflow.com/a/72339551
|
||||
export const createGlobalStore = <T extends object>(
|
||||
init: T
|
||||
): [Store<T>, SetStoreFunction<T>] => {
|
||||
const [state, setState] = createStore(init);
|
||||
if (localStorage.globalStore) {
|
||||
try {
|
||||
setState(JSON.parse(localStorage.globalStore));
|
||||
} catch (err) {
|
||||
setState(() => init);
|
||||
}
|
||||
}
|
||||
createEffect(() => {
|
||||
localStorage.globalStore = JSON.stringify(state);
|
||||
});
|
||||
return [state, setState];
|
||||
};
|
||||
|
||||
const [store, setStore] = createGlobalStore({
|
||||
user: { id: '', login: '' } as User,
|
||||
todos: [] as Todo[],
|
||||
});
|
||||
|
||||
const App: Component = () => {
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
<Routes>
|
||||
<Route path={'test'} component={Test} />
|
||||
<Route path={['login', 'register']} component={Login} />
|
||||
@ -17,4 +51,5 @@ const App: Component = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export { store, setStore };
|
||||
export default App;
|
||||
|
@ -1,9 +1,220 @@
|
||||
import { Component } from "solid-js";
|
||||
import {
|
||||
Component,
|
||||
createEffect,
|
||||
createResource,
|
||||
createSignal,
|
||||
For,
|
||||
onMount,
|
||||
Show,
|
||||
} from 'solid-js';
|
||||
import RestClient from '../api/RestClient';
|
||||
import { store, setStore } from '../App';
|
||||
import Button from '../ui/Button';
|
||||
import Table, { TableData, TableRow } from '../ui/Table';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export type Todo = {
|
||||
id: string;
|
||||
user_id: string;
|
||||
title: string;
|
||||
priority: Priority;
|
||||
status: Status;
|
||||
description: string;
|
||||
};
|
||||
|
||||
type TodoModalProps = {
|
||||
todo: Todo;
|
||||
};
|
||||
|
||||
export enum Status {
|
||||
Todo = 'Todo',
|
||||
Doing = 'Doing',
|
||||
Done = 'Done',
|
||||
}
|
||||
|
||||
export enum Priority {
|
||||
High = 'High',
|
||||
Normal = 'Normal',
|
||||
Low = 'Low',
|
||||
}
|
||||
|
||||
const createNewTodo = (): Todo => ({
|
||||
id: uuidv4(),
|
||||
user_id: store.user.id,
|
||||
title: 'New Title',
|
||||
description: 'Some description.',
|
||||
status: Status.Todo,
|
||||
priority: Priority.Normal,
|
||||
});
|
||||
|
||||
const TodoModal: Component<TodoModalProps> = (props) => {
|
||||
const [show, setShow] = createSignal(false);
|
||||
const [todo, setTodo] = createSignal(props.todo);
|
||||
const inputClass =
|
||||
'form-control block w-full px-3 py-1.5 text-base font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none';
|
||||
const selectClass =
|
||||
'form-select block w-full px-3 py-1.5 text-base font-normal text-gray-700 bg-white bg-clip-padding bg-no-repeat border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none';
|
||||
const labelClass = 'form-label inline-block mb-2 text-gray-700';
|
||||
|
||||
const Home: Component = () => {
|
||||
return (
|
||||
<>
|
||||
<h1 class="text-4xl">Home</h1>
|
||||
<Button onClick={() => setShow(true)}>🖉</Button>
|
||||
<Show when={show()} fallback={<></>}>
|
||||
<div class='fixed top-0 left-0 w-screen h-screen flex items-center justify-center bg-sh-bgP2 bg-opacity-50 transform transition-transform duration-300'>
|
||||
<div class='p-6 rounded-lg bg-white w-1/2'>
|
||||
<form>
|
||||
<div class='form-group mb-6'>
|
||||
<label for='title' class={labelClass}>
|
||||
Title
|
||||
</label>
|
||||
<input
|
||||
type='text'
|
||||
class={inputClass}
|
||||
id='title'
|
||||
onInput={(e) =>
|
||||
setTodo((t) => ({ ...t, title: e.currentTarget.value } as Todo))
|
||||
}
|
||||
value={todo()?.title}
|
||||
/>
|
||||
</div>
|
||||
<div class='form-group mb-6'>
|
||||
<label for='description' class={labelClass}>
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
class={inputClass}
|
||||
id='description'
|
||||
onInput={(e) =>
|
||||
setTodo((t) => ({ ...t, description: e.currentTarget.value } as Todo))
|
||||
}
|
||||
value={todo()?.description}
|
||||
/>
|
||||
</div>
|
||||
<div class='form-group mb-6'>
|
||||
<label for='status' class={labelClass}>
|
||||
Status
|
||||
</label>
|
||||
<select
|
||||
onChange={(e) =>
|
||||
setTodo((t) => ({ ...t, status: e.currentTarget.value } as Todo))
|
||||
}
|
||||
class={selectClass}
|
||||
>
|
||||
{Object.keys(Status).map((k) => {
|
||||
if (todo().status === Status[k as Status]) {
|
||||
return (
|
||||
<option selected value={Status[k as Status]}>
|
||||
{k}
|
||||
</option>
|
||||
);
|
||||
} else {
|
||||
return <option value={Status[k as Status]}>{k}</option>;
|
||||
}
|
||||
})}
|
||||
</select>
|
||||
<div class='form-group mb-6'>
|
||||
<label for='priority' class={labelClass}>
|
||||
Priority
|
||||
</label>
|
||||
<select
|
||||
onChange={(e) =>
|
||||
setTodo((t) => ({ ...t, priority: e.currentTarget.value } as Todo))
|
||||
}
|
||||
class={selectClass}
|
||||
>
|
||||
{Object.keys(Priority).map((k) => {
|
||||
if (todo().priority === Priority[k as Priority]) {
|
||||
return (
|
||||
<option selected value={Priority[k as Priority]}>
|
||||
{k}
|
||||
</option>
|
||||
);
|
||||
} else {
|
||||
return <option value={Priority[k as Priority]}>{k}</option>;
|
||||
}
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<Button
|
||||
backgroundColor='green'
|
||||
onClick={() =>
|
||||
RestClient.PUT('/todo', JSON.stringify(todo())).then(() => {
|
||||
fetchTodos();
|
||||
setShow(false);
|
||||
})
|
||||
}
|
||||
>
|
||||
Save
|
||||
</Button>{' '}
|
||||
<Button backgroundColor='gray' onClick={() => setShow(false)}>
|
||||
Close
|
||||
</Button>{' '}
|
||||
<Button
|
||||
backgroundColor='red'
|
||||
onClick={() => {
|
||||
setTodo({ ...props.todo } as Todo);
|
||||
setShow(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const fetchTodos = async () => {
|
||||
const todos = (await RestClient.GET('/todo')) as Todo[];
|
||||
setStore({
|
||||
...store,
|
||||
todos,
|
||||
});
|
||||
};
|
||||
|
||||
const Home: Component = () => {
|
||||
onMount(fetchTodos);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Show when={store.todos.length > 0} fallback={<></>}>
|
||||
<Table
|
||||
data={store.todos.map((todo) => ({
|
||||
Title: todo.title,
|
||||
Description: todo.description,
|
||||
Status: todo.status,
|
||||
Priority: todo.priority,
|
||||
Remove: (
|
||||
<Button
|
||||
backgroundColor='red'
|
||||
onClick={() => {
|
||||
RestClient.DELETE(`/todo/${todo.id}`).then(
|
||||
() => (window.location.href = '/')
|
||||
);
|
||||
}}
|
||||
>
|
||||
🗑
|
||||
</Button>
|
||||
),
|
||||
Edit: <TodoModal todo={todo}></TodoModal>,
|
||||
}))}
|
||||
></Table>
|
||||
</Show>
|
||||
<div class='mt-10 ml-10'>
|
||||
<Button
|
||||
backgroundColor='green'
|
||||
onClick={() =>
|
||||
RestClient.PUT('/todo', JSON.stringify(createNewTodo())).then(() => {
|
||||
fetchTodos();
|
||||
})
|
||||
}
|
||||
>
|
||||
New
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,7 +1,123 @@
|
||||
import { Component } from 'solid-js';
|
||||
import { Component, createEffect, createSignal } from 'solid-js';
|
||||
import RestClient from '../api/RestClient';
|
||||
import { setStore, store, User } from '../App';
|
||||
import Button from '../ui/Button';
|
||||
|
||||
type LoginRequest = {
|
||||
login: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
const [loggedInUser, setLoggedInUser] = createSignal();
|
||||
export { loggedInUser };
|
||||
|
||||
const Login: Component = () => {
|
||||
return <h1 class='text-4xl'>Login</h1>;
|
||||
const [loginRequest, setLoginRequest] = createSignal<LoginRequest>({
|
||||
login: '',
|
||||
password: '',
|
||||
});
|
||||
const [login, setLogin] = createSignal('');
|
||||
const [password, setPassword] = createSignal('');
|
||||
|
||||
// Populate the current user outside the JSX (we need createEffect for this!)
|
||||
createEffect(async () => {
|
||||
if (loginRequest().login.trim() === '' || loginRequest().password.trim() === '') {
|
||||
return;
|
||||
}
|
||||
const user = (await RestClient.POST(
|
||||
'/login',
|
||||
JSON.stringify(loginRequest())
|
||||
)) as User;
|
||||
if (user.id === undefined) {
|
||||
console.log(user);
|
||||
return;
|
||||
}
|
||||
setStore({
|
||||
...store,
|
||||
user,
|
||||
});
|
||||
window.location.href = '/';
|
||||
});
|
||||
|
||||
return (
|
||||
<div class='grid h-screen place-items-center'>
|
||||
<div class='block p-6 rounded-lg shadow-lg bg-white max-w-sm'>
|
||||
<form>
|
||||
<div class='form-group mb-6'>
|
||||
<label for='username' class='form-label inline-block mb-2 text-gray-700'>
|
||||
Login
|
||||
</label>
|
||||
<input
|
||||
type='text'
|
||||
class='form-control
|
||||
block
|
||||
w-full
|
||||
px-3
|
||||
py-1.5
|
||||
text-base
|
||||
font-normal
|
||||
text-gray-700
|
||||
bg-white bg-clip-padding
|
||||
border border-solid border-gray-300
|
||||
rounded
|
||||
transition
|
||||
ease-in-out
|
||||
m-0
|
||||
focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none'
|
||||
id='username'
|
||||
placeholder='Username'
|
||||
onInput={(e) => setLogin(e.currentTarget.value)}
|
||||
value={login()}
|
||||
/>
|
||||
</div>
|
||||
<div class='form-group mb-6'>
|
||||
<label for='Password' class='form-label inline-block mb-2 text-gray-700'>
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
type='password'
|
||||
class='form-control block
|
||||
w-full
|
||||
px-3
|
||||
py-1.5
|
||||
text-base
|
||||
font-normal
|
||||
text-gray-700
|
||||
bg-white bg-clip-padding
|
||||
border border-solid border-gray-300
|
||||
rounded
|
||||
transition
|
||||
ease-in-out
|
||||
m-0
|
||||
focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none'
|
||||
id='Password'
|
||||
placeholder='Password'
|
||||
onInput={(e) => setPassword(e.currentTarget.value)}
|
||||
value={password()}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
fullWidth
|
||||
onClick={() => {
|
||||
console.log(login(), password());
|
||||
setLoginRequest({ login: login(), password: password() });
|
||||
}}
|
||||
>
|
||||
Sign in
|
||||
</Button>
|
||||
<p class='text-gray-800 mt-6 text-center'>
|
||||
Want to track your todo{"'"}s?{' '}
|
||||
<a
|
||||
href='#!'
|
||||
class='text-blue-600 hover:text-blue-700 focus:text-blue-700 transition duration-200 ease-in-out'
|
||||
>
|
||||
Register
|
||||
</a>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Component } from 'solid-js';
|
||||
import RestClient from '../api/RestClient';
|
||||
import Button from '../ui/Button';
|
||||
|
||||
const Test: Component = () => {
|
||||
const TEST_USER = {
|
||||
@ -9,27 +10,18 @@ const Test: Component = () => {
|
||||
salt: 'MEIN_SALZ',
|
||||
};
|
||||
|
||||
const btnPrimary =
|
||||
'inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded-full shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out';
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1 class='text-4xl'>TEST</h1>
|
||||
<button class={btnPrimary} onClick={() => RestClient.GET('/')}>
|
||||
GET_HOME
|
||||
</button>
|
||||
<button
|
||||
class={btnPrimary}
|
||||
<Button onClick={() => RestClient.GET('/')}>HOME</Button>
|
||||
<Button
|
||||
backgroundColor='gray'
|
||||
onClick={() => RestClient.POST('/login', JSON.stringify(TEST_USER))}
|
||||
>
|
||||
POST_LOGIN
|
||||
</button>
|
||||
<button class={btnPrimary} onClick={() => RestClient.GET('/user')}>
|
||||
GET_USER
|
||||
</button>
|
||||
<button class={btnPrimary} onClick={() => RestClient.GET('/todo')}>
|
||||
GET_TODO
|
||||
</button>
|
||||
</Button>
|
||||
<Button onClick={() => RestClient.GET('/user')}>GET_USER</Button>
|
||||
<Button onClick={() => RestClient.GET('/todo')}>GET_TODO</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
53
rust_solid_cassandra/frontend/src/ui/Button.tsx
Normal file
53
rust_solid_cassandra/frontend/src/ui/Button.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import { Component, JSX } from 'solid-js';
|
||||
|
||||
type ButtonProps = {
|
||||
backgroundColor?: string;
|
||||
color?: string;
|
||||
fullWidth?: boolean;
|
||||
onClick?: Function;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const backgroundColors: { [key: string]: string } = {
|
||||
blue: 'bg-sh-blue hover:bg-sh-blueM1 focus:bg-sh-blueM1 active:bg-sh-blueM1',
|
||||
red: 'bg-red-600 hover:bg-red-700 focus:bg-red-700 active:bg-red-800',
|
||||
green: 'bg-green-600 hover:bg-green-700 focus:bg-green-700 active:bg-green-800',
|
||||
yellow: 'bg-sh-yellow hover:bg-sh-yellowM1 focus:bg-sh-yellowM1 active:bg-sh-yellowM1',
|
||||
magenta:
|
||||
'bg-sh-magenta hover:bg-sh-magentaM1 focus:bg-sh-magentaM1 active:bg-sh-magentaM1',
|
||||
gray: 'bg-gray-700 hover:bg-gray-800 focus:bg-gray-800 active:bg-gray-800',
|
||||
cyan: 'bg-cyan-700 hover:bg-cyan-900 focus:bg-cyan-900 active:bg-cyan-800',
|
||||
};
|
||||
|
||||
const colors: { [key: string]: string } = {
|
||||
white: 'text-white',
|
||||
black: 'text-black',
|
||||
};
|
||||
|
||||
const Button: Component<ButtonProps> = (props) => {
|
||||
const backgroundColor = backgroundColors[props.backgroundColor || 'blue'];
|
||||
const color = colors[props.color || 'white'];
|
||||
const fullWidth = props.fullWidth ? 'w-full' : '';
|
||||
const fun =
|
||||
props.onClick !== undefined
|
||||
? (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
(props.onClick as () => void)();
|
||||
}
|
||||
: (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
console.log(
|
||||
'Default behavior of Button component is invoked!',
|
||||
'You might want to change the onClick function?'
|
||||
);
|
||||
};
|
||||
const btnStyle = `${color} ${backgroundColor} ${fullWidth} inline-block px-6 py-2.5 font-medium text-xs leading-tight uppercase rounded-full shadow-md hover:shadow-lg focus:shadow-lg focus:outline-none focus:ring-0 active:shadow-lg transition duration-150 ease-in-out`;
|
||||
|
||||
return (
|
||||
<button class={btnStyle} onClick={fun}>
|
||||
{props.children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default Button;
|
51
rust_solid_cassandra/frontend/src/ui/Navbar.tsx
Normal file
51
rust_solid_cassandra/frontend/src/ui/Navbar.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import { A } from '@solidjs/router';
|
||||
import { Component, Show } from 'solid-js';
|
||||
import RestClient from '../api/RestClient';
|
||||
import { setStore, store, User } from '../App';
|
||||
import Button from './Button';
|
||||
|
||||
const Navbar: Component = () => {
|
||||
return (
|
||||
<div class='flex justify-between items-center bg-sh-bg p-3'>
|
||||
<div class='flex items-center'>
|
||||
<a
|
||||
href='/'
|
||||
class='pl-2 text-xl font-bold no-underline text-sh-yellow hover:text-sh-yellowM1'
|
||||
>
|
||||
Just todo it!
|
||||
</a>
|
||||
</div>
|
||||
<h1 class='flex -ml-20 text-xl font-bold no-underline text-sh-yellow'>
|
||||
Hey {store.user.login}!
|
||||
</h1>
|
||||
<Show
|
||||
when={store.user.login !== ''}
|
||||
fallback={
|
||||
<>
|
||||
<Button
|
||||
onClick={() => (window.location.href = '/login')}
|
||||
backgroundColor='yellow'
|
||||
color='black'
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
onClick={() => {
|
||||
RestClient.DELETE('/logout');
|
||||
setStore({ todos: [], user: { id: '', login: '' } });
|
||||
window.location.href = '/login';
|
||||
}}
|
||||
backgroundColor='magenta'
|
||||
color='black'
|
||||
>
|
||||
Logout
|
||||
</Button>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navbar;
|
65
rust_solid_cassandra/frontend/src/ui/Table.tsx
Normal file
65
rust_solid_cassandra/frontend/src/ui/Table.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import { Component, For, JSX } from 'solid-js';
|
||||
import { Priority, Status, Todo } from '../pages/Home';
|
||||
|
||||
type TableProps = {
|
||||
data: Todo[];
|
||||
};
|
||||
|
||||
type TableRowProps = {
|
||||
children: JSX.Element;
|
||||
};
|
||||
|
||||
type TableDataProps = {
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
type TableHeadProps = {
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
export const TableHead: Component<TableHeadProps> = (props) => {
|
||||
return (
|
||||
<th scope='col' class='text-sm font-medium px-6 py-4'>
|
||||
{props.children}
|
||||
</th>
|
||||
);
|
||||
};
|
||||
|
||||
export const TableData: Component<TableDataProps> = (props) => {
|
||||
return <td class='text-sm text-gray-900 font-light px-6 py-4 '>{props.children}</td>;
|
||||
};
|
||||
|
||||
export const TableRow: Component<TableRowProps> = (props) => {
|
||||
const rowClass = 'border-b';
|
||||
return <tr class={rowClass}>{props.children}</tr>;
|
||||
};
|
||||
|
||||
const Table: Component<TableProps> = (props) => {
|
||||
if (props.data.length < 1) {
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
<div class='overflow-hidden mt-10 mx-10'>
|
||||
<table class='min-w-full text-center'>
|
||||
<thead class='border-b text-sh-bgM1 bg-sh-yellow'>
|
||||
{Object.keys(props.data[0]).map((key) => (
|
||||
<TableHead>{key}</TableHead>
|
||||
))}
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={props.data}>
|
||||
{(child) => (
|
||||
<TableRow>
|
||||
{Object.values(child).map((val) => (
|
||||
<TableData>{val}</TableData>
|
||||
))}
|
||||
</TableRow>
|
||||
)}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Table;
|
@ -1,8 +1,48 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ["./src/**/*.{js,jsx,ts,tsx}"],
|
||||
content: ['./src/**/*.{js,jsx,ts,tsx}'],
|
||||
theme: {
|
||||
extend: {},
|
||||
extend: {
|
||||
colors: {
|
||||
'sh-bgM2': '#000306',
|
||||
'sh-bgM1': '#020E18',
|
||||
'sh-bg': '#0D1F2D',
|
||||
'sh-bgP1': '#1E3141',
|
||||
'sh-bgP2': '#3C5161',
|
||||
'sh-fgM2': '#546A7B',
|
||||
'sh-fgM1': '#9EA3B0',
|
||||
'sh-fg': '#C3C9E9',
|
||||
'sh-fgP1': '#BDD3DD',
|
||||
'sh-white': '#FEFEFE',
|
||||
'sh-black': '#000306',
|
||||
'sh-yellowM1': '#DA7F05',
|
||||
'sh-yellow': '#FDAA3A',
|
||||
'sh-yellowP1': '#FFC16E',
|
||||
'sh-orangeM1': '#C45A00',
|
||||
'sh-orange': '#FF7F11',
|
||||
'sh-orangeP1': '#FFA251',
|
||||
'sh-redM2': '#960004',
|
||||
'sh-redM1': '#CD1419',
|
||||
'sh-red': '#ED474A',
|
||||
'sh-redP1': '#FD7E81',
|
||||
'sh-greenM1': '#739F2F',
|
||||
'sh-green': '#A5CC69',
|
||||
'sh-greenP1': '#D5EEAE',
|
||||
'sh-greenP2': '#A2DFED',
|
||||
'sh-blueM2': '#0683A0',
|
||||
'sh-blueM1': '#36A1BB',
|
||||
'sh-blue': '#64BFD6',
|
||||
'sh-blueP1': '#A2DFED',
|
||||
'sh-magentaM2': '#690635',
|
||||
'sh-magentaM1': '#902B5B',
|
||||
'sh-magenta': '#B95F8A',
|
||||
'sh-magentaP1': '#DBA1BC',
|
||||
'sh-purpleM2': '#630DAE',
|
||||
'sh-purpleM1': '#863FC4',
|
||||
'sh-purple': '#A86CDC',
|
||||
'sh-purpleP1': '#CEA7F0',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user