@adaptive-sm/astro-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +183 -0
- package/lib/button/Button.astro +58 -0
- package/lib/button/buttonCva.ts +196 -0
- package/lib/button/buttonIconCva.ts +52 -0
- package/lib/button/classesButtonClickAnimation.ts +8 -0
- package/lib/button/classesButtonClickAnimationPush.ts +6 -0
- package/lib/button/classesButtonClickAnimationSquish.ts +6 -0
- package/lib/button/classesButtonDisabled.ts +1 -0
- package/lib/card/CardWrapper.astro +15 -0
- package/lib/card/classesCardWrapper.ts +16 -0
- package/lib/details/Details.astro +57 -0
- package/lib/dev/TailwindIndicator.astro +28 -0
- package/lib/form/Fieldset.astro +21 -0
- package/lib/form/classesFieldset.ts +1 -0
- package/lib/generate_ai_rules/generate_agent_rules.bash +9 -0
- package/lib/generate_ai_rules/generate_agent_rules_1_lib.bash +33 -0
- package/lib/generate_ai_rules/generate_agent_rules_2_copy.bash +14 -0
- package/lib/generate_ai_rules/generate_agent_rules_3_combine.bash +31 -0
- package/lib/generate_demo_list/DemoList.astro +31 -0
- package/lib/generate_demo_list/DemoListType.ts +1 -0
- package/lib/generate_demo_list/generateDemoList.ts +55 -0
- package/lib/generate_image_list/generateImageList.ts +101 -0
- package/lib/grid/classesGridCols.ts +86 -0
- package/lib/icon/Icon1.astro +21 -0
- package/lib/img/ImageType.ts +6 -0
- package/lib/img/Img.astro +27 -0
- package/lib/img/TypedImg.astro +26 -0
- package/lib/img/classInvertDiagram.ts +1 -0
- package/lib/img/classesImgZoomInOnHover.ts +1 -0
- package/lib/layouts/MarkdownWrapper.astro +17 -0
- package/lib/layouts/MinimalLayout.astro +67 -0
- package/lib/layouts/parts/ThemeToggle.astro +137 -0
- package/lib/layouts/parts/astroElementId.ts +7 -0
- package/lib/layouts/parts/markdown.css +93 -0
- package/lib/link/LinkButton.astro +48 -0
- package/lib/link/LinkText.astro +23 -0
- package/lib/link/classesTextLink.ts +13 -0
- package/lib/list/BlackBulletPoint.astro +16 -0
- package/lib/list/BlackBulletPoints.astro +23 -0
- package/lib/list/CheckPoint.astro +20 -0
- package/lib/list/CheckPoints.astro +23 -0
- package/lib/list/NumberedList.astro +14 -0
- package/lib/list/Ps.astro +12 -0
- package/lib/list/TextOrLink.astro +19 -0
- package/lib/modal/Modal.astro +54 -0
- package/lib/modal/Modal.module.css +19 -0
- package/lib/modal/ModalButton.astro +41 -0
- package/lib/modal/modal.ts +137 -0
- package/lib/page/PageCentered.astro +14 -0
- package/lib/page/PageCenteredCard.astro +17 -0
- package/lib/page/classesBg.ts +27 -0
- package/lib/page/classesPageCentered.ts +6 -0
- package/lib/popover/Popover1.astro +45 -0
- package/lib/popover/setupPopoverListeners.ts +22 -0
- package/lib/select/Select.astro +45 -0
- package/lib/table/DesktopTableClassses.ts +6 -0
- package/lib/table/MobileTableClassses.ts +7 -0
- package/lib/table/Table.astro +41 -0
- package/lib/table/TableColumnDef.ts +10 -0
- package/lib/table/TableD.astro +55 -0
- package/lib/table/TableM.astro +22 -0
- package/lib/table/TableMEntry.astro +27 -0
- package/lib/table/sharedTableRowClasses.ts +1 -0
- package/lib/table/tableVisibilityClasses.ts +28 -0
- package/lib/text/classesTextGray.ts +1 -0
- package/lib/text/classesTextHeader.ts +1 -0
- package/lib/utils/bun/BunCmd.ts +7 -0
- package/lib/utils/bun/cryAndTryAgainLater.ts +6 -0
- package/lib/utils/bun/logBunCmd.ts +1 -0
- package/lib/utils/bun/runCmdAsync.ts +44 -0
- package/lib/utils/bun/runCmdLocally.ts +13 -0
- package/lib/utils/obj/objectKeys.ts +21 -0
- package/lib/utils/ran/generateId12.ts +7 -0
- package/lib/utils/ran/generateId3.ts +7 -0
- package/lib/utils/ran/generateId4.ts +7 -0
- package/lib/utils/ran/generateId5.ts +7 -0
- package/lib/utils/ran/generateId6.ts +7 -0
- package/lib/utils/ran/generateId7.ts +7 -0
- package/lib/utils/ran/generateReadableId.ts +35 -0
- package/lib/utils/ran/urlAlphabet32.ts +8 -0
- package/lib/utils/ui/classArr.ts +3 -0
- package/lib/utils/ui/classMerge.ts +10 -0
- package/lib/utils/ui/isDevEnv.ts +7 -0
- package/lib/utils/ui/tailwindBreakpoint.ts +83 -0
- package/package.json +56 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
type FocusableElement =
|
|
2
|
+
| HTMLAnchorElement
|
|
3
|
+
| HTMLButtonElement
|
|
4
|
+
| HTMLInputElement
|
|
5
|
+
| HTMLTextAreaElement
|
|
6
|
+
| HTMLSelectElement
|
|
7
|
+
| HTMLDetailsElement
|
|
8
|
+
|
|
9
|
+
export function modal() {
|
|
10
|
+
// variables
|
|
11
|
+
let modals = document.querySelectorAll<HTMLDialogElement>(".modal")
|
|
12
|
+
|
|
13
|
+
// abort controllers for global event listeners
|
|
14
|
+
let trapFocusController: AbortController | undefined
|
|
15
|
+
let keydownController: AbortController | undefined
|
|
16
|
+
|
|
17
|
+
const getKeyboardFocusableElements = (element: HTMLElement) => {
|
|
18
|
+
return [
|
|
19
|
+
...element.querySelectorAll<FocusableElement>(
|
|
20
|
+
'a, button, input, textarea, select, details,[tabindex]:not([tabindex="-1"])',
|
|
21
|
+
),
|
|
22
|
+
].filter((el) => !el.hasAttribute("disabled"))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const trapFocus = (event: KeyboardEvent, modal: HTMLDialogElement) => {
|
|
26
|
+
const focusables = getKeyboardFocusableElements(modal)
|
|
27
|
+
|
|
28
|
+
// These will not be undefined as a modal always has at least one <button>
|
|
29
|
+
const firstFocusable = focusables[0]!
|
|
30
|
+
const lastFocusable = focusables[focusables.length - 1]!
|
|
31
|
+
|
|
32
|
+
if (document.activeElement === lastFocusable && event.key === "Tab" && !event.shiftKey) {
|
|
33
|
+
event.preventDefault()
|
|
34
|
+
firstFocusable.focus()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (document.activeElement === firstFocusable && event.key === "Tab" && event.shiftKey) {
|
|
38
|
+
event.preventDefault()
|
|
39
|
+
lastFocusable.focus()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const openModal = (modal: HTMLDialogElement) => {
|
|
44
|
+
const modalTitle = modal.querySelector("h2")
|
|
45
|
+
const modalContent = modal.querySelector<HTMLDivElement>(".modal-content")
|
|
46
|
+
|
|
47
|
+
modal.showModal()
|
|
48
|
+
modalTitle!.focus()
|
|
49
|
+
|
|
50
|
+
setAllowBackgroundScrolling(false)
|
|
51
|
+
|
|
52
|
+
trapFocusController = new AbortController()
|
|
53
|
+
keydownController = new AbortController()
|
|
54
|
+
|
|
55
|
+
document.addEventListener("keydown", (e) => trapFocus(e, modal), { signal: trapFocusController.signal })
|
|
56
|
+
|
|
57
|
+
modal.addEventListener(
|
|
58
|
+
"keydown",
|
|
59
|
+
(event) => {
|
|
60
|
+
if (event.key === "Escape") {
|
|
61
|
+
closeSpecificModal(modal)
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{ signal: keydownController.signal },
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
modal.addEventListener(
|
|
68
|
+
"click",
|
|
69
|
+
() => {
|
|
70
|
+
closeSpecificModal(modal)
|
|
71
|
+
},
|
|
72
|
+
{ signal: keydownController.signal },
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
modalContent!.addEventListener(
|
|
76
|
+
"click",
|
|
77
|
+
(event) => {
|
|
78
|
+
event.stopPropagation()
|
|
79
|
+
},
|
|
80
|
+
{ signal: keydownController.signal },
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const closeSpecificModal = (modal: HTMLDialogElement) => {
|
|
85
|
+
const modalId = modal.getAttribute("aria-labelledby")
|
|
86
|
+
const modalTrigger = document.querySelector(`#${modalId}`) as HTMLButtonElement
|
|
87
|
+
modalTrigger.focus({ preventScroll: true })
|
|
88
|
+
modal.close()
|
|
89
|
+
|
|
90
|
+
setAllowBackgroundScrolling(true)
|
|
91
|
+
|
|
92
|
+
trapFocusController?.abort()
|
|
93
|
+
keydownController?.abort()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// execution
|
|
97
|
+
addEventListeners(openModal, closeSpecificModal, modals)
|
|
98
|
+
|
|
99
|
+
// Listen for view transitions
|
|
100
|
+
document.addEventListener("astro:after-swap", () => {
|
|
101
|
+
// reset variables
|
|
102
|
+
modals = document.querySelectorAll<HTMLDialogElement>(".modal")
|
|
103
|
+
addEventListeners(openModal, closeSpecificModal, modals)
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function addEventListeners(
|
|
108
|
+
openModal: (modal: HTMLDialogElement) => void,
|
|
109
|
+
closeModal: (modal: HTMLDialogElement) => void,
|
|
110
|
+
modals = document.querySelectorAll<HTMLDialogElement>(".modal"),
|
|
111
|
+
) {
|
|
112
|
+
// execution
|
|
113
|
+
for (const modal of modals) {
|
|
114
|
+
const modalId = modal.getAttribute("aria-labelledby")
|
|
115
|
+
const modalTrigger = document.querySelector(`#${modalId}`)
|
|
116
|
+
if (!modalTrigger) {
|
|
117
|
+
throw new Error(`Trigger element not found. \n
|
|
118
|
+
Did you forget to add a trigger element with id: "${modalId}"?`)
|
|
119
|
+
}
|
|
120
|
+
modalTrigger.addEventListener("click", () => openModal(modal))
|
|
121
|
+
|
|
122
|
+
const closeId = `${modalId}-close`
|
|
123
|
+
const modalCloseButton = modal.querySelector(`#${closeId}`)
|
|
124
|
+
if (!modalCloseButton) {
|
|
125
|
+
throw new Error(`Close button not found. Expected id: "${closeId}"?`)
|
|
126
|
+
}
|
|
127
|
+
modalCloseButton.addEventListener("click", () => closeModal(modal))
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function setAllowBackgroundScrolling(allowed: boolean): void {
|
|
132
|
+
if (allowed) {
|
|
133
|
+
document.body.classList.remove("overflow-hidden")
|
|
134
|
+
} else {
|
|
135
|
+
document.body.classList.add("overflow-hidden")
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { classMerge } from "~/utils/ui/classMerge"
|
|
3
|
+
import { classesPageCentered } from "./classesPageCentered"
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
id?:string
|
|
7
|
+
class?: string
|
|
8
|
+
}
|
|
9
|
+
const p = Astro.props
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<div id={p.id} class={classMerge(classesPageCentered, p.class)}>
|
|
13
|
+
<slot />
|
|
14
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { classMerge } from "~/utils/ui/classMerge"
|
|
3
|
+
import PageCentered from "./PageCentered.astro"
|
|
4
|
+
import { classesCardWrapperPage } from "~/card/classesCardWrapper"
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
class?: string
|
|
8
|
+
classInner?: string
|
|
9
|
+
}
|
|
10
|
+
const props = Astro.props
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<PageCentered class={props.class}>
|
|
14
|
+
<div class={classMerge(classesCardWrapperPage, props.classInner)}>
|
|
15
|
+
<slot />
|
|
16
|
+
</div>
|
|
17
|
+
</PageCentered>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type ClassesBg = keyof typeof classesBg
|
|
2
|
+
|
|
3
|
+
export const classesBg = {
|
|
4
|
+
white: "bg-white dark:bg-black",
|
|
5
|
+
orange: "bg-orange-100 dark:bg-yellow-900",
|
|
6
|
+
red: "bg-red-100 dark:bg-red-900",
|
|
7
|
+
amber: "bg-amber-100 dark:bg-amber-900",
|
|
8
|
+
yellow: "bg-yellow-100 dark:bg-yellow-900",
|
|
9
|
+
lime: "bg-lime-100 dark:bg-lime-900",
|
|
10
|
+
green: "bg-green-100 dark:bg-green-900",
|
|
11
|
+
emerald: "bg-emerald-100 dark:bg-emerald-900",
|
|
12
|
+
teal: "bg-teal-100 dark:bg-teal-900",
|
|
13
|
+
cyan: "bg-cyan-100 dark:bg-cyan-900",
|
|
14
|
+
sky: "bg-sky-100 dark:bg-sky-900",
|
|
15
|
+
blue: "bg-blue-100 dark:bg-blue-900",
|
|
16
|
+
indigo: "bg-indigo-100 dark:bg-indigo-900",
|
|
17
|
+
violet: "bg-violet-100 dark:bg-violet-900",
|
|
18
|
+
purple: "bg-purple-100 dark:bg-purple-900",
|
|
19
|
+
fuchsia: "bg-fuchsia-100 dark:bg-fuchsia-900",
|
|
20
|
+
pink: "bg-pink-100 dark:bg-pink-900",
|
|
21
|
+
rose: "bg-rose-100 dark:bg-rose-900",
|
|
22
|
+
slate: "bg-slate-100 dark:bg-slate-900",
|
|
23
|
+
gray: "bg-gray-100 dark:bg-gray-900",
|
|
24
|
+
zinc: "bg-zinc-100 dark:bg-zinc-900",
|
|
25
|
+
neutral: "bg-neutral-100 dark:bg-neutral-900",
|
|
26
|
+
stone: "bg-stone-100 dark:bg-stone-900",
|
|
27
|
+
} as const
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Icon1 from "~/icon/Icon1.astro"
|
|
3
|
+
import { buttonVariant, type ButtonVariant, buttonCva2 } from "~/button/buttonCva"
|
|
4
|
+
import { classArr } from "~/utils/ui/classArr"
|
|
5
|
+
import { setupPopoverListeners } from "./setupPopoverListeners"
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
id: string
|
|
9
|
+
icon: string
|
|
10
|
+
title: string
|
|
11
|
+
variant?: ButtonVariant
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { id, icon, title, variant = buttonVariant.link } = Astro.props
|
|
15
|
+
const summaryClasses = buttonCva2(variant, null, "flex flex-wrap gap-2 text-xl font-semibold list-none")
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
<details
|
|
19
|
+
id={id}
|
|
20
|
+
class={classArr(
|
|
21
|
+
"relative",
|
|
22
|
+
"popover-details", // used by popover
|
|
23
|
+
)}
|
|
24
|
+
>
|
|
25
|
+
<summary class={summaryClasses}>
|
|
26
|
+
<Icon1 path={icon} />
|
|
27
|
+
{title}
|
|
28
|
+
</summary>
|
|
29
|
+
<div
|
|
30
|
+
class={classArr(
|
|
31
|
+
"absolute", // layout
|
|
32
|
+
"bg-white dark:bg-gray-800", // background
|
|
33
|
+
"border border-gray-300 dark:border-gray-600 rounded shadow-lg", // borders/shadows
|
|
34
|
+
"p-2 mt-1", // spacing
|
|
35
|
+
"z-10", // z-index
|
|
36
|
+
)}
|
|
37
|
+
>
|
|
38
|
+
<slot />
|
|
39
|
+
</div>
|
|
40
|
+
</details>
|
|
41
|
+
|
|
42
|
+
<script>
|
|
43
|
+
import { setupPopoverListeners } from "./setupPopoverListeners"
|
|
44
|
+
setupPopoverListeners()
|
|
45
|
+
</script>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const classPopoverSelector = ".popover-details"
|
|
2
|
+
|
|
3
|
+
export function setupPopoverListeners() {
|
|
4
|
+
document.addEventListener("keydown", (e: KeyboardEvent) => {
|
|
5
|
+
if (e.key === "Escape") {
|
|
6
|
+
const detailsElements = document.querySelectorAll(classPopoverSelector) as NodeListOf<HTMLDetailsElement>
|
|
7
|
+
detailsElements.forEach((details) => {
|
|
8
|
+
if (details.open) {
|
|
9
|
+
details.open = false
|
|
10
|
+
}
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
})
|
|
14
|
+
document.addEventListener("click", (e: MouseEvent) => {
|
|
15
|
+
const detailsElements = document.querySelectorAll(classPopoverSelector) as NodeListOf<HTMLDetailsElement>
|
|
16
|
+
detailsElements.forEach((details) => {
|
|
17
|
+
if (details.open && !details.contains(e.target as Node)) {
|
|
18
|
+
details.open = false
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { classArr } from "~/utils/ui/classArr"
|
|
3
|
+
|
|
4
|
+
type ValueDisplayFn = (value: string) => string
|
|
5
|
+
|
|
6
|
+
type NativeSingleSelectProps = {
|
|
7
|
+
initialValue: string
|
|
8
|
+
options: string[]
|
|
9
|
+
optionClass?: string
|
|
10
|
+
valueDisplay?: ValueDisplayFn
|
|
11
|
+
id?: string
|
|
12
|
+
class?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const props = Astro.props as NativeSingleSelectProps
|
|
16
|
+
|
|
17
|
+
const getDisplayValue = (itemValue: string, valueDisplay?: ValueDisplayFn) => {
|
|
18
|
+
if (!valueDisplay) return itemValue
|
|
19
|
+
const hasValue = valueDisplay(itemValue)
|
|
20
|
+
if (!hasValue) return itemValue
|
|
21
|
+
return hasValue
|
|
22
|
+
}
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
<select
|
|
26
|
+
id={props.id}
|
|
27
|
+
class={classArr(
|
|
28
|
+
"block w-full p-2.5",
|
|
29
|
+
"text-gray-900 dark:text-white text-sm",
|
|
30
|
+
"dark:placeholder-gray-400",
|
|
31
|
+
"bg-gray-50 dark:bg-gray-700",
|
|
32
|
+
"rounded-lg border border-gray-300 dark:border-gray-600",
|
|
33
|
+
"focus:ring-blue-500 focus:border-blue-500 dark:focus:border-blue-500 dark:focus:ring-blue-500",
|
|
34
|
+
props.class,
|
|
35
|
+
)}
|
|
36
|
+
value={props.initialValue}
|
|
37
|
+
>
|
|
38
|
+
{
|
|
39
|
+
props.options.map((option: string) => (
|
|
40
|
+
<option value={option} class={props.optionClass}>
|
|
41
|
+
{getDisplayValue(option, props.valueDisplay)}
|
|
42
|
+
</option>
|
|
43
|
+
))
|
|
44
|
+
}
|
|
45
|
+
</select>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { DesktopTableClassses } from "./DesktopTableClassses"
|
|
3
|
+
import type { MobileTableClassses } from "./MobileTableClassses"
|
|
4
|
+
import { tableVisibilityClasses } from "./tableVisibilityClasses"
|
|
5
|
+
import { classMerge } from "~/utils/ui/classMerge"
|
|
6
|
+
import { type TailwindBreakpoint, tailwindBreakpoint } from "~/utils/ui/tailwindBreakpoint"
|
|
7
|
+
import type { TableColumnDef } from "./TableColumnDef"
|
|
8
|
+
import TableD from "./TableD.astro"
|
|
9
|
+
import TableM from "./TableM.astro"
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
rows: string[]
|
|
13
|
+
columns: TableColumnDef[]
|
|
14
|
+
mobileClasses?: MobileTableClassses
|
|
15
|
+
desktopClasses?: DesktopTableClassses
|
|
16
|
+
breakpoint?: TailwindBreakpoint
|
|
17
|
+
class?: string
|
|
18
|
+
noEntriesText?: string
|
|
19
|
+
}
|
|
20
|
+
const props = Astro.props
|
|
21
|
+
const rows = props.rows
|
|
22
|
+
const columns = props.columns
|
|
23
|
+
const desktopClasses = props.desktopClasses
|
|
24
|
+
const mobileClasses = props.mobileClasses
|
|
25
|
+
const breakpoint = props.breakpoint ?? tailwindBreakpoint.sm
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
<TableD
|
|
29
|
+
rows={rows}
|
|
30
|
+
columns={columns}
|
|
31
|
+
class={classMerge(props.class, tableVisibilityClasses.desktop(breakpoint))}
|
|
32
|
+
desktopClasses={desktopClasses}
|
|
33
|
+
noEntriesText={props.noEntriesText}
|
|
34
|
+
/>
|
|
35
|
+
<TableM
|
|
36
|
+
rows={rows}
|
|
37
|
+
columns={columns}
|
|
38
|
+
class={classMerge(props.class, tableVisibilityClasses.mobile(breakpoint))}
|
|
39
|
+
mobileClasses={mobileClasses}
|
|
40
|
+
noEntriesText={props.noEntriesText}
|
|
41
|
+
/>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { type DesktopTableClassses } from "./DesktopTableClassses"
|
|
3
|
+
import { sharedTableRowClasses } from "./sharedTableRowClasses"
|
|
4
|
+
// import { t4table } from "~/ui_tables/table3/t4table"
|
|
5
|
+
import { classMerge } from "~/utils/ui/classMerge"
|
|
6
|
+
import { type TableColumnDef } from "./TableColumnDef"
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
rows: string[]
|
|
10
|
+
noEntriesText?: string
|
|
11
|
+
columns: TableColumnDef[]
|
|
12
|
+
desktopClasses?: DesktopTableClassses
|
|
13
|
+
class?: string
|
|
14
|
+
}
|
|
15
|
+
const props = Astro.props
|
|
16
|
+
const rows = props.rows
|
|
17
|
+
const columns = props.columns
|
|
18
|
+
const c = props.desktopClasses
|
|
19
|
+
const noEntries = rows.length <= 0
|
|
20
|
+
const hasEntries = rows.length > 0
|
|
21
|
+
const noEntriesText = props.noEntriesText ?? "No Entries"
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
{
|
|
25
|
+
noEntries && (
|
|
26
|
+
<>
|
|
27
|
+
<span class={"text-lg text-center p-2 pt-6"}>{noEntriesText}</span>
|
|
28
|
+
</>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
{
|
|
33
|
+
hasEntries && (
|
|
34
|
+
<table class={classMerge("overflow-x-auto", props.class, c?.class)}>
|
|
35
|
+
<thead>
|
|
36
|
+
<tr>
|
|
37
|
+
{columns.map((h: TableColumnDef, i: number) => (
|
|
38
|
+
<th scope="col" class={classMerge("text-left", "px-4 py-2", c?.header, h.classHeader)} title={h.title}>
|
|
39
|
+
{h.name}
|
|
40
|
+
</th>
|
|
41
|
+
))}
|
|
42
|
+
</tr>
|
|
43
|
+
</thead>
|
|
44
|
+
<tbody>
|
|
45
|
+
{rows.map((d: any, y: number) => (
|
|
46
|
+
<tr class={classMerge(sharedTableRowClasses, c?.row)}>
|
|
47
|
+
{columns.map((h: any) => {
|
|
48
|
+
return <td class={classMerge(c?.data, h.classData)}>{h.cell(d)}</td>
|
|
49
|
+
})}
|
|
50
|
+
</tr>
|
|
51
|
+
))}
|
|
52
|
+
</tbody>
|
|
53
|
+
</table>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { classMerge } from "~/utils/ui/classMerge"
|
|
3
|
+
import { type TableColumnDef } from "./TableColumnDef"
|
|
4
|
+
import TableMEntry from "./TableMEntry.astro"
|
|
5
|
+
import type { MobileTableClassses } from "~/table/MobileTableClassses"
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
rows: string[]
|
|
9
|
+
noEntriesText?: string
|
|
10
|
+
columns: TableColumnDef[]
|
|
11
|
+
mobileClasses?: MobileTableClassses
|
|
12
|
+
class?: string
|
|
13
|
+
}
|
|
14
|
+
const props = Astro.props
|
|
15
|
+
const rows = props.rows
|
|
16
|
+
const columns = props.columns
|
|
17
|
+
const c = props.mobileClasses
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
<div class={classMerge("flex w-full flex-col flex-wrap gap-8", props.class, c?.class)}>
|
|
21
|
+
{rows.map((row: string, y: number) => <TableMEntry row={row} columns={columns} mobileClasses={c} />)}
|
|
22
|
+
</div>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { classMerge } from "~/utils/ui/classMerge"
|
|
3
|
+
import { type TableColumnDef } from "./TableColumnDef"
|
|
4
|
+
import type { MobileTableClassses } from "~/table/MobileTableClassses"
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
row: string
|
|
8
|
+
columns: TableColumnDef[]
|
|
9
|
+
mobileClasses?: MobileTableClassses
|
|
10
|
+
}
|
|
11
|
+
const p = Astro.props
|
|
12
|
+
const c = p.mobileClasses
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<div class={classMerge("flex flex-col flex-wrap gap-2", c?.entry)}>
|
|
16
|
+
{
|
|
17
|
+
p.columns.map((column: TableColumnDef, i: number) => {
|
|
18
|
+
if (i == 0) return <span class={classMerge("font-semibold", c?.first)}>{column.cell(p.row)}</span>
|
|
19
|
+
return (
|
|
20
|
+
<span class={classMerge("flex flex-wrap justify-between", c?.entry)}>
|
|
21
|
+
<span class={classMerge("text-muted-foreground", c?.header)}>{column.name + ":"}</span>
|
|
22
|
+
<span class={classMerge("text-right", c?.other)}>{column.cell(p.row)}</span>
|
|
23
|
+
</span>
|
|
24
|
+
)
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const sharedTableRowClasses = "border-t hover:bg-gray-100 dark:hover:bg-gray-800"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { classMerge } from "~/utils/ui/classMerge"
|
|
2
|
+
import type { TailwindBreakpoint } from "~/utils/ui/tailwindBreakpoint"
|
|
3
|
+
import { tailwindBreakpoint } from "~/utils/ui/tailwindBreakpoint"
|
|
4
|
+
|
|
5
|
+
function tableDesktopclass(b: TailwindBreakpoint) {
|
|
6
|
+
const tb = tailwindBreakpoint
|
|
7
|
+
return classMerge(
|
|
8
|
+
"hidden",
|
|
9
|
+
b === tb.sm && "sm:table",
|
|
10
|
+
b === tb.md && "md:table",
|
|
11
|
+
b === tb.lg && "lg:table",
|
|
12
|
+
b === tb.xl && "xl:table",
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
function tableMobileclass(b: TailwindBreakpoint) {
|
|
16
|
+
const tb = tailwindBreakpoint
|
|
17
|
+
return classMerge(
|
|
18
|
+
"w-full",
|
|
19
|
+
b === tb.sm && "sm:hidden",
|
|
20
|
+
b === tb.md && "md:hidden",
|
|
21
|
+
b === tb.lg && "lg:hidden",
|
|
22
|
+
b === tb.xl && "xl:hidden",
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
export const tableVisibilityClasses = {
|
|
26
|
+
desktop: tableDesktopclass,
|
|
27
|
+
mobile: tableMobileclass,
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const classesTextGray = "text-gray-600 dark:text-gray-400"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const classesTextHeader = "text-xl font-semibold"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const logBunCmd = true
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import console from "node:console"
|
|
2
|
+
import type { BunCmd } from "./BunCmd"
|
|
3
|
+
import { logBunCmd } from "./logBunCmd"
|
|
4
|
+
|
|
5
|
+
export async function runCmdAsync(cmd: string[]): Promise<BunCmd> {
|
|
6
|
+
const startedAt = performance.now()
|
|
7
|
+
if (logBunCmd) {
|
|
8
|
+
console.log({ cmd })
|
|
9
|
+
}
|
|
10
|
+
const process = Bun.spawn(cmd, {
|
|
11
|
+
stdout: "pipe",
|
|
12
|
+
stderr: "pipe",
|
|
13
|
+
})
|
|
14
|
+
const exitCode = await process.exited
|
|
15
|
+
const output = await Bun.readableStreamToText(process.stdout)
|
|
16
|
+
const error = await Bun.readableStreamToText(process.stderr)
|
|
17
|
+
const outputLines = output.split("\n").filter((s) => s.length > 0)
|
|
18
|
+
const errorLines = error.split("\n").filter((s) => s.length > 0)
|
|
19
|
+
const lines = [...outputLines, ...errorLines]
|
|
20
|
+
const endedAt = performance.now()
|
|
21
|
+
const ms = Math.round(endedAt - startedAt)
|
|
22
|
+
const r: BunCmd = {
|
|
23
|
+
cmd,
|
|
24
|
+
success: exitCode === 0,
|
|
25
|
+
exitCode,
|
|
26
|
+
lines,
|
|
27
|
+
ms,
|
|
28
|
+
}
|
|
29
|
+
if (logBunCmd) {
|
|
30
|
+
if (lines.length < 8) {
|
|
31
|
+
console.log(r)
|
|
32
|
+
} else {
|
|
33
|
+
const l: Omit<BunCmd, "lines"> = {
|
|
34
|
+
cmd,
|
|
35
|
+
success: exitCode === 0,
|
|
36
|
+
exitCode,
|
|
37
|
+
ms,
|
|
38
|
+
}
|
|
39
|
+
console.log(l)
|
|
40
|
+
console.log(JSON.stringify(lines, null, 2))
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return r
|
|
44
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BunCmd } from "./BunCmd"
|
|
2
|
+
import { cryAndTryAgainLater } from "./cryAndTryAgainLater"
|
|
3
|
+
import { runCmdAsync } from "./runCmdAsync"
|
|
4
|
+
|
|
5
|
+
export async function runCmdLocally(cmd: string | string[]): Promise<BunCmd> {
|
|
6
|
+
return runCmdAsync(["sh", "-c", Array.isArray(cmd) ? cmd.join(" ") : cmd])
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function runCmdLocallyAndExitOnError(cmd: string | string[]): Promise<BunCmd> {
|
|
10
|
+
const got = await runCmdLocally(cmd)
|
|
11
|
+
if (!got.success) return cryAndTryAgainLater(got)
|
|
12
|
+
return got
|
|
13
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Like Object.keys, but unsound in exchange for more convenience.
|
|
3
|
+
*
|
|
4
|
+
* Casts the result of Object.keys to the known keys of an object type,
|
|
5
|
+
* even though JavaScript objects may contain additional keys.
|
|
6
|
+
*
|
|
7
|
+
* Only use this function when you know/control the provenance of the object
|
|
8
|
+
* you're iterating, and can verify it contains exactly the keys declared
|
|
9
|
+
* to the type system.
|
|
10
|
+
*
|
|
11
|
+
* Example:
|
|
12
|
+
* ```
|
|
13
|
+
* const o = {x: "ok", y: 10}
|
|
14
|
+
* o["z"] = "UNTRACKED_KEY"
|
|
15
|
+
* const safeKeys = Object.keys(o)
|
|
16
|
+
* const unsafeKeys = objectKeys(o)
|
|
17
|
+
* ```
|
|
18
|
+
* => const safeKeys: string[]
|
|
19
|
+
* => const unsafeKeys: ("x" | "y")[] // Missing "z"
|
|
20
|
+
*/
|
|
21
|
+
export const objectKeys = Object.keys as <T>(obj: T) => Array<keyof T>
|