generated from avior/template-web-astro
feat: Add multi selector
Signed-off-by: Avior <github@avior.me>
This commit is contained in:
parent
89d2debb1e
commit
d8ad16608e
@ -52,7 +52,7 @@ if (baseProps.type === 'textarea') {
|
||||
@apply px-4 w-full bg-gray-100 rounded-lg border-gray-200 min-h-0 border flex items-center gap-2 py-2 text-black
|
||||
}
|
||||
.input textarea, .input input {
|
||||
@apply bg-transparent outline-none invalid:border-red-300 placeholder:text-gray-400 placeholder:font-light focus-visible:outline-none
|
||||
@apply bg-transparent outline-none invalid:border-red-300 placeholder:text-gray-400 placeholder:font-light focus-visible:outline-none m-0 p-0 border-0
|
||||
}
|
||||
.textarea {
|
||||
@apply overflow-y-hidden
|
||||
|
@ -7,14 +7,16 @@ export interface Props extends Omit<astroHTML.JSX.InputHTMLAttributes, 'type'> {
|
||||
block?: boolean
|
||||
suffix?: string
|
||||
prefix?: string
|
||||
multiple?: boolean
|
||||
options: Array<string | number | {value?: string, title: string | number, description?: string | number | null}>
|
||||
}
|
||||
|
||||
const baseProps = objectOmit(Astro.props, 'label', 'block', 'suffix', 'prefix', 'options')
|
||||
const values = Array.isArray(Astro.props.value) ? Astro.props.value : (Astro.props.value?.toString()?.split(',') ?? [])
|
||||
---
|
||||
|
||||
<!-- input wrapper -->
|
||||
<label data-component="select" data-options={Astro.props.options} class:list={['parent', {'w-full': Astro.props.block}]}>
|
||||
<label data-component="select" data-options={JSON.stringify(Astro.props.options)} class:list={['parent', {'w-full': Astro.props.block}]}>
|
||||
{Astro.props.label && (
|
||||
<div class="label">{Astro.props.label}</div>
|
||||
)}
|
||||
@ -23,19 +25,23 @@ const baseProps = objectOmit(Astro.props, 'label', 'block', 'suffix', 'prefix',
|
||||
{Astro.props.prefix && (
|
||||
<p class="prefix">{Astro.props.prefix}</p>
|
||||
)}
|
||||
<input type="hidden" {...baseProps} />
|
||||
<input readonly {...objectOmit(baseProps, 'name') as any} />
|
||||
<ul class="list hidden">
|
||||
{Astro.props.options.map((it) => {
|
||||
if (typeof it !== 'object') {
|
||||
it = {title: it}
|
||||
}
|
||||
const itemValue = it.value ?? it.title
|
||||
const checked = values.includes(itemValue)
|
||||
return (
|
||||
<li data-value={it.value ?? it.title}>
|
||||
<p>{it.title}</p>
|
||||
{it.description && (
|
||||
<p class="desc">{it.description}</p>
|
||||
)}
|
||||
<li>
|
||||
<label class="flex gap-2">
|
||||
<input width={14} height={14} hidden={!Astro.props.multiple} type={Astro.props.multiple ? 'checkbox' : 'radio'} name={baseProps.name} value={itemValue} checked={checked} />
|
||||
<p>{it.title}</p>
|
||||
{it.description && (
|
||||
<p class="desc">{it.description}</p>
|
||||
)}
|
||||
</label>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
@ -51,10 +57,6 @@ const baseProps = objectOmit(Astro.props, 'label', 'block', 'suffix', 'prefix',
|
||||
@apply flex flex-col cursor-text gap-2
|
||||
}
|
||||
|
||||
.parent input, .parent textarea {
|
||||
@apply w-full
|
||||
}
|
||||
|
||||
.suffix, .prefix {
|
||||
@apply select-none font-light text-gray-400
|
||||
}
|
||||
@ -62,8 +64,8 @@ const baseProps = objectOmit(Astro.props, 'label', 'block', 'suffix', 'prefix',
|
||||
@apply px-4 w-full bg-gray-100 rounded-lg border-gray-200 min-h-0 border flex items-center gap-2 py-2
|
||||
}
|
||||
|
||||
.input textarea, .input input {
|
||||
@apply bg-transparent outline-none invalid:border-red-300 placeholder:text-gray-400 placeholder:font-light focus-visible:outline-none text-black
|
||||
.input > input {
|
||||
@apply w-full bg-transparent outline-none invalid:border-red-300 placeholder:text-gray-400 placeholder:font-light focus-visible:outline-none text-black
|
||||
}
|
||||
|
||||
.textarea {
|
||||
@ -73,7 +75,7 @@ const baseProps = objectOmit(Astro.props, 'label', 'block', 'suffix', 'prefix',
|
||||
.list {
|
||||
@apply absolute top-full mt-2 z-10 bg-gray-50 rounded-lg border border-gray-300 overflow-hidden
|
||||
}
|
||||
input:focus + ul, ul:hover {
|
||||
.input > input:focus + ul, ul:hover {
|
||||
@apply block
|
||||
}
|
||||
ul :global(li) {
|
||||
@ -93,28 +95,29 @@ const baseProps = objectOmit(Astro.props, 'label', 'block', 'suffix', 'prefix',
|
||||
|
||||
|
||||
Component.addComponent<HTMLElement>('select', (it) => {
|
||||
const input = it.querySelector<HTMLInputElement>('input[type="hidden"]')
|
||||
const displayInput = it.querySelector<HTMLInputElement>('input[readonly]')
|
||||
const inputs = Array.from(it.querySelectorAll<HTMLInputElement>('li input'))
|
||||
const list = it.querySelector('ul')
|
||||
if (!input || !list || !displayInput) {
|
||||
if (!list || !displayInput) {
|
||||
return
|
||||
}
|
||||
function setSelectValue(value: string, displayValue: string) {
|
||||
input!.value = value
|
||||
input!.setAttribute('value', value)
|
||||
displayInput!.value = displayValue
|
||||
input!.dispatchEvent(new Event('change'))
|
||||
function updateSelectValue() {
|
||||
const checkedValues = inputs.filter((it) => it.checked).map((it) => it.value)
|
||||
displayInput.value = checkedValues.toString()
|
||||
}
|
||||
const value = input.getAttribute('value') ?? displayInput.getAttribute('value')
|
||||
if (value) {
|
||||
const displayValue = list.querySelector(`li[data-value="${value}"]`)
|
||||
if (displayValue) {
|
||||
setSelectValue(value!, displayValue.querySelector('p')?.innerText ?? '')
|
||||
const values = displayInput.getAttribute('value')?.split(',') ?? []
|
||||
console.log('values', values)
|
||||
if (values.length > 0) {
|
||||
const checkbox = list.querySelector(`input[value="${value}"]`)
|
||||
if (checkbox) {
|
||||
checkbox.checked = true
|
||||
}
|
||||
}
|
||||
list.querySelectorAll('li').forEach((listItem) => {
|
||||
listItem.addEventListener('click', () => {
|
||||
setSelectValue(listItem.dataset.value!, listItem.querySelector('p')?.innerText ?? '')
|
||||
list.querySelectorAll('li input').forEach((listItem: HTMLInputElement) => {
|
||||
console.log('')
|
||||
listItem.addEventListener('change', () => {
|
||||
console.log(listItem, 'changed to', listItem.checked)
|
||||
updateSelectValue()
|
||||
})
|
||||
})
|
||||
})
|
@ -1,13 +1,6 @@
|
||||
import Priority from 'models/Priority'
|
||||
import Schema, { type Impl } from 'models/Schema'
|
||||
|
||||
export enum Priority {
|
||||
None,
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
Urgent
|
||||
}
|
||||
|
||||
const schema = new Schema({
|
||||
/**
|
||||
* the project ID
|
||||
@ -24,7 +17,7 @@ const schema = new Schema({
|
||||
description: { type: String, nullable: true },
|
||||
|
||||
state: String, //state id
|
||||
priority: {type: Number, defaultValue: Priority.None},
|
||||
priority: {type: Number, defaultValue: Priority.NONE},
|
||||
begin: { type: Date, nullable: true },
|
||||
due: { type: Date, nullable: true },
|
||||
|
||||
|
23
src/models/Priority/index.ts
Normal file
23
src/models/Priority/index.ts
Normal file
@ -0,0 +1,23 @@
|
||||
enum Priority {
|
||||
NONE,
|
||||
LOW,
|
||||
MEDIUM,
|
||||
HIGH,
|
||||
URGENT,
|
||||
}
|
||||
|
||||
export function getPriorityText(priority: Priority) {
|
||||
switch (priority) {
|
||||
case Priority.NONE: return 'None'
|
||||
case Priority.LOW: return 'Low'
|
||||
case Priority.MEDIUM: return 'Medium'
|
||||
case Priority.HIGH: return 'High'
|
||||
case Priority.URGENT: return 'Urgent'
|
||||
}
|
||||
}
|
||||
|
||||
export function getPriorities(): Array<Priority> {
|
||||
return Object.values(Priority).filter((it) => typeof it === 'number') as Array<Priority>
|
||||
}
|
||||
|
||||
export default Priority
|
@ -1,6 +1,7 @@
|
||||
import type { APIRoute } from 'astro'
|
||||
import ResponseBuilder from 'libs/ResponseBuilder'
|
||||
import DaoFactory from 'models/DaoFactory'
|
||||
import Priority, { getPriorityText } from 'models/Priority'
|
||||
import { Sort } from 'models/Query'
|
||||
|
||||
export const POST: APIRoute = async (ctx) => {
|
||||
@ -26,9 +27,18 @@ export const POST: APIRoute = async (ctx) => {
|
||||
|
||||
export const GET: APIRoute = async (ctx) => {
|
||||
const projectId = ctx.params.id!
|
||||
const sort = ctx.params.sort ?? 'localid'
|
||||
const dao = DaoFactory.get('issue')
|
||||
const stateDao = DaoFactory.get('state')
|
||||
const states = (await stateDao.findAll({ project: projectId })).data
|
||||
const issues = (await dao.findAll({project: projectId, $sort: { [sort]: Sort.ASC}})).data
|
||||
|
||||
const res = issues.map((it) => ({
|
||||
...it,
|
||||
state: states.find((st) => st.id === it.state),
|
||||
priority: getPriorityText(it.priority ?? Priority.NONE)
|
||||
}))
|
||||
return new ResponseBuilder()
|
||||
.body((await dao.findAll({project: projectId, $sort: { localid: Sort.ASC}})).data)
|
||||
.body(res)
|
||||
.build()
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
---
|
||||
import Button from 'components/global/Button.astro'
|
||||
import Input from 'components/global/Input.astro'
|
||||
import Select from 'components/global/Select/index.astro'
|
||||
import MainLayout from 'layouts/MainLayout.astro'
|
||||
import DaoFactory from 'models/DaoFactory'
|
||||
import Schema from 'models/Schema'
|
||||
@ -47,6 +48,8 @@ if (Astro.request.method === 'POST') {
|
||||
|
||||
return Astro.redirect(route('/', {message: 'Error creating your project'}))
|
||||
}
|
||||
|
||||
const projects = await dao.findAll()
|
||||
---
|
||||
|
||||
<MainLayout title="Dzeio - Website Template">
|
||||
@ -57,16 +60,20 @@ if (Astro.request.method === 'POST') {
|
||||
<Input label="Nom du projet" name="name" />
|
||||
<Button>Créer</Button>
|
||||
</form>
|
||||
<Input
|
||||
name="name"
|
||||
data-input="/api/v1/projects"
|
||||
data-output="#projectItem ul inner"
|
||||
data-trigger="keydown load after:100"
|
||||
data-multiple
|
||||
/>
|
||||
<form action="">
|
||||
<Select
|
||||
multiple
|
||||
options={projects.data.map((it) => ({value: it.id, title: it.name}))}
|
||||
name="name"
|
||||
data-input="/api/v1/projects"
|
||||
data-output="#projectItem ul#results inner"
|
||||
data-trigger="keydown load after:100"
|
||||
data-multiple
|
||||
/>
|
||||
</form>
|
||||
|
||||
<p>Results</p>
|
||||
<ul>
|
||||
<ul id="results">
|
||||
|
||||
</ul>
|
||||
</main>
|
||||
@ -75,27 +82,6 @@ if (Astro.request.method === 'POST') {
|
||||
<a data-attribute="name href:/projects/{id}"></a>
|
||||
</li>
|
||||
</template>
|
||||
<!-- Define a template which contains how it is displayed -->
|
||||
<template id="pouet">
|
||||
<div>
|
||||
<!-- Define the attribute in the object that will be assigned -->
|
||||
<p data-attribute="caca"></p>
|
||||
<!-- You can change the display type by selecting `html` | `innerHTML` | `outerHTML` | `text` | `innerText` | `outerText` -->
|
||||
<p data-attribute="caca" data-type="html"></p>
|
||||
<!-- You can even add another Requester inside the template! -->
|
||||
<p
|
||||
data-attribute="caca value:caca"
|
||||
|
||||
data-action="/api/v1/checkout"
|
||||
data-template="#pouet"
|
||||
data-trigger="mouseover"
|
||||
data-target="outerHTML"
|
||||
></p>
|
||||
</div>
|
||||
</template>
|
||||
<template id="list">
|
||||
<li data-attribute="this"></li>
|
||||
</template>
|
||||
</MainLayout>
|
||||
|
||||
<script>
|
||||
|
@ -5,10 +5,11 @@ import DaoFactory from 'models/DaoFactory'
|
||||
import route from 'route'
|
||||
import Input from 'components/global/Input.astro'
|
||||
import Table from 'components/global/Table/Table.astro'
|
||||
import Select from 'components/global/Select.astro'
|
||||
import Select from 'components/global/Select/index.astro'
|
||||
import { X } from 'lucide-astro'
|
||||
import { Sort } from 'models/Query'
|
||||
import type { StateObj } from 'models/State'
|
||||
import Priority, { getPriorities, getPriorityText } from 'models/Priority'
|
||||
|
||||
const defaultStates: Array<StateObj> = [{
|
||||
id: '',
|
||||
@ -45,7 +46,7 @@ console.log(project, states)
|
||||
|
||||
<MainLayout>
|
||||
<main class="container flex gap-24 justify-center items-center md:mt-6">
|
||||
<div>
|
||||
<!-- <div class="w-1/3">
|
||||
<Input
|
||||
name="displayID"
|
||||
value={project.displayid}
|
||||
@ -53,14 +54,18 @@ console.log(project, states)
|
||||
data-input={`post:${route('/api/v1/projects/[id]', {id: project.id})}`}
|
||||
data-output="run:reload"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
</div> -->
|
||||
<div class="w-1/2">
|
||||
<h1 class="text-6xl text-center font-bold">{project.name}</h1>
|
||||
<Select data-trigger="change" data-output="hyp:tbody[data-input]" name="sort" options={[{value: 'localid', title: 'ID'}, {value: 'state', title: 'State'}]} />
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>id</th>
|
||||
<th>name</th>
|
||||
<th>state</th>
|
||||
<th>Priority</th>
|
||||
<th>Labels</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody
|
||||
@ -79,6 +84,15 @@ console.log(project, states)
|
||||
>
|
||||
<td data-attribute={`${project.displayid}-{localid}`}></td>
|
||||
<td data-attribute="name"></td>
|
||||
<td data-attribute="state.name"></td>
|
||||
<td data-attribute="priority"></td>
|
||||
<td>
|
||||
<ul class="flex gap-2">
|
||||
<li data-loop="labels" data-attribute="this">
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<form data-input={`post:${route('/api/v1/projects/[id]/issues', {id: project.id})}`} data-output="hyp:tbody[data-input]">
|
||||
@ -86,34 +100,43 @@ console.log(project, states)
|
||||
<Button>Ajouter</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div>
|
||||
<div class="w-1/2">
|
||||
<div id="issue-details"></div>
|
||||
<template id="issueTemplate">
|
||||
<form
|
||||
data-trigger="keyup pointerup after:250"
|
||||
data-input={`post:${route('/api/v1/projects/[id]/issues/[issueId]', {id: project.id, issueId: '{id}'}, true)}`}
|
||||
data-output="hyp:tbody[data-input]"
|
||||
class="flex flex-col gap-5"
|
||||
>
|
||||
<h2 data-attribute="name"></h2>
|
||||
<Select label="progress" name="state" data-attribute="value:state" options={states.map((state) => ({ value: state.id, title: state.name}))} />
|
||||
<ul>
|
||||
<li data-loop="labels">
|
||||
<span data-attribute="this"></span>
|
||||
<X
|
||||
data-params="label:{this}"
|
||||
data-input={`delete:${route('/api/v1/projects/[id]/issues/[issueId]/labels', {id: project.id, issueId: '{id}'}, true)}`}
|
||||
data-output="hyp:[data-id='{id}']"
|
||||
/>
|
||||
</li>
|
||||
<li><Select
|
||||
<Input data-attribute="value:name" name="name" />
|
||||
<Input type="textarea" name="description" data-attribute="description"></Input>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex gap-4 items-center"><p>State</p><Select name="state" data-attribute="value:state" options={states.map((state) => ({ value: state.id, title: state.name}))} /></div>
|
||||
<div class="flex gap-4 items-center"><p>Priority</p><Select name="priority" data-attribute="value:priority" options={getPriorities().map((priority: Priority) => ({ value: priority, title: getPriorityText(priority)}))} /></div>
|
||||
<div class="flex gap-4 items-center"><p>Labels</p><Select
|
||||
data-trigger="change"
|
||||
name="label"
|
||||
multiple
|
||||
options={['a', 'b', 'c']}
|
||||
data-output="hyp:[data-id='{id}']"
|
||||
data-attribute="value:labels"
|
||||
data-input={`post:${route('/api/v1/projects/[id]/issues/[issueId]/labels', {id: project.id, issueId: '{id}'}, true)}`}
|
||||
/></li>
|
||||
</ul>
|
||||
<Input type="textarea" name="description" data-attribute="description"></Input>
|
||||
<button type="submit">Submit</button>
|
||||
/></div>
|
||||
<ul class="flex gap-2 flex-row">
|
||||
<li data-loop="labels" class="group flex gap-2 bg-slate-700 px-2 py-px rounded-full items-center">
|
||||
<span data-attribute="this"></span>
|
||||
<X
|
||||
class="opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer"
|
||||
data-params="label:{this}"
|
||||
data-input={`delete:${route('/api/v1/projects/[id]/issues/[issueId]/labels', {id: project.id, issueId: '{id}'}, true)}`}
|
||||
data-output="hyp:[data-id='{id}']"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</template>
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user