@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.
Files changed (86) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +183 -0
  3. package/lib/button/Button.astro +58 -0
  4. package/lib/button/buttonCva.ts +196 -0
  5. package/lib/button/buttonIconCva.ts +52 -0
  6. package/lib/button/classesButtonClickAnimation.ts +8 -0
  7. package/lib/button/classesButtonClickAnimationPush.ts +6 -0
  8. package/lib/button/classesButtonClickAnimationSquish.ts +6 -0
  9. package/lib/button/classesButtonDisabled.ts +1 -0
  10. package/lib/card/CardWrapper.astro +15 -0
  11. package/lib/card/classesCardWrapper.ts +16 -0
  12. package/lib/details/Details.astro +57 -0
  13. package/lib/dev/TailwindIndicator.astro +28 -0
  14. package/lib/form/Fieldset.astro +21 -0
  15. package/lib/form/classesFieldset.ts +1 -0
  16. package/lib/generate_ai_rules/generate_agent_rules.bash +9 -0
  17. package/lib/generate_ai_rules/generate_agent_rules_1_lib.bash +33 -0
  18. package/lib/generate_ai_rules/generate_agent_rules_2_copy.bash +14 -0
  19. package/lib/generate_ai_rules/generate_agent_rules_3_combine.bash +31 -0
  20. package/lib/generate_demo_list/DemoList.astro +31 -0
  21. package/lib/generate_demo_list/DemoListType.ts +1 -0
  22. package/lib/generate_demo_list/generateDemoList.ts +55 -0
  23. package/lib/generate_image_list/generateImageList.ts +101 -0
  24. package/lib/grid/classesGridCols.ts +86 -0
  25. package/lib/icon/Icon1.astro +21 -0
  26. package/lib/img/ImageType.ts +6 -0
  27. package/lib/img/Img.astro +27 -0
  28. package/lib/img/TypedImg.astro +26 -0
  29. package/lib/img/classInvertDiagram.ts +1 -0
  30. package/lib/img/classesImgZoomInOnHover.ts +1 -0
  31. package/lib/layouts/MarkdownWrapper.astro +17 -0
  32. package/lib/layouts/MinimalLayout.astro +67 -0
  33. package/lib/layouts/parts/ThemeToggle.astro +137 -0
  34. package/lib/layouts/parts/astroElementId.ts +7 -0
  35. package/lib/layouts/parts/markdown.css +93 -0
  36. package/lib/link/LinkButton.astro +48 -0
  37. package/lib/link/LinkText.astro +23 -0
  38. package/lib/link/classesTextLink.ts +13 -0
  39. package/lib/list/BlackBulletPoint.astro +16 -0
  40. package/lib/list/BlackBulletPoints.astro +23 -0
  41. package/lib/list/CheckPoint.astro +20 -0
  42. package/lib/list/CheckPoints.astro +23 -0
  43. package/lib/list/NumberedList.astro +14 -0
  44. package/lib/list/Ps.astro +12 -0
  45. package/lib/list/TextOrLink.astro +19 -0
  46. package/lib/modal/Modal.astro +54 -0
  47. package/lib/modal/Modal.module.css +19 -0
  48. package/lib/modal/ModalButton.astro +41 -0
  49. package/lib/modal/modal.ts +137 -0
  50. package/lib/page/PageCentered.astro +14 -0
  51. package/lib/page/PageCenteredCard.astro +17 -0
  52. package/lib/page/classesBg.ts +27 -0
  53. package/lib/page/classesPageCentered.ts +6 -0
  54. package/lib/popover/Popover1.astro +45 -0
  55. package/lib/popover/setupPopoverListeners.ts +22 -0
  56. package/lib/select/Select.astro +45 -0
  57. package/lib/table/DesktopTableClassses.ts +6 -0
  58. package/lib/table/MobileTableClassses.ts +7 -0
  59. package/lib/table/Table.astro +41 -0
  60. package/lib/table/TableColumnDef.ts +10 -0
  61. package/lib/table/TableD.astro +55 -0
  62. package/lib/table/TableM.astro +22 -0
  63. package/lib/table/TableMEntry.astro +27 -0
  64. package/lib/table/sharedTableRowClasses.ts +1 -0
  65. package/lib/table/tableVisibilityClasses.ts +28 -0
  66. package/lib/text/classesTextGray.ts +1 -0
  67. package/lib/text/classesTextHeader.ts +1 -0
  68. package/lib/utils/bun/BunCmd.ts +7 -0
  69. package/lib/utils/bun/cryAndTryAgainLater.ts +6 -0
  70. package/lib/utils/bun/logBunCmd.ts +1 -0
  71. package/lib/utils/bun/runCmdAsync.ts +44 -0
  72. package/lib/utils/bun/runCmdLocally.ts +13 -0
  73. package/lib/utils/obj/objectKeys.ts +21 -0
  74. package/lib/utils/ran/generateId12.ts +7 -0
  75. package/lib/utils/ran/generateId3.ts +7 -0
  76. package/lib/utils/ran/generateId4.ts +7 -0
  77. package/lib/utils/ran/generateId5.ts +7 -0
  78. package/lib/utils/ran/generateId6.ts +7 -0
  79. package/lib/utils/ran/generateId7.ts +7 -0
  80. package/lib/utils/ran/generateReadableId.ts +35 -0
  81. package/lib/utils/ran/urlAlphabet32.ts +8 -0
  82. package/lib/utils/ui/classArr.ts +3 -0
  83. package/lib/utils/ui/classMerge.ts +10 -0
  84. package/lib/utils/ui/isDevEnv.ts +7 -0
  85. package/lib/utils/ui/tailwindBreakpoint.ts +83 -0
  86. 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,6 @@
1
+ import { classArr } from "~/utils/ui/classArr"
2
+
3
+ export const classesPageCentered = classArr(
4
+ "flex flex-col justify-center items-center my-auto", // align center
5
+ "min-h-[80svh] sm:min-h-[90svh]", // full screen
6
+ )
@@ -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,6 @@
1
+ export type DesktopTableClassses = {
2
+ class?: string
3
+ row?: string
4
+ header?: string
5
+ data?: string
6
+ }
@@ -0,0 +1,7 @@
1
+ export type MobileTableClassses = {
2
+ class?: string
3
+ entry?: string
4
+ first?: string
5
+ header?: string
6
+ other?: string
7
+ }
@@ -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,10 @@
1
+ /** @jsxImportSource astro */
2
+
3
+ export interface TableColumnDef {
4
+ id: string
5
+ name: string
6
+ title?: string
7
+ cell: (data: string) => string | number
8
+ classHeader?: string
9
+ classData?: string
10
+ }
@@ -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,7 @@
1
+ export type BunCmd = {
2
+ cmd: string[]
3
+ success: boolean
4
+ exitCode: number
5
+ lines: string[]
6
+ ms: number
7
+ }
@@ -0,0 +1,6 @@
1
+ import process from "node:process"
2
+
3
+ export function cryAndTryAgainLater<T>(t: T): T {
4
+ process.exit(1)
5
+ return t
6
+ }
@@ -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>
@@ -0,0 +1,7 @@
1
+ import { generateReadableId } from "~/utils/ran/generateReadableId"
2
+
3
+ const gen = generateReadableId(12)
4
+
5
+ export function generateId12(): string {
6
+ return gen()
7
+ }
@@ -0,0 +1,7 @@
1
+ import { generateReadableId } from "~/utils/ran/generateReadableId"
2
+
3
+ const gen = generateReadableId(3)
4
+
5
+ export function generateId3(): string {
6
+ return gen()
7
+ }
@@ -0,0 +1,7 @@
1
+ import { generateReadableId } from "~/utils/ran/generateReadableId"
2
+
3
+ const gen = generateReadableId(4)
4
+
5
+ export function generateId4(): string {
6
+ return gen()
7
+ }
@@ -0,0 +1,7 @@
1
+ import { generateReadableId } from "./generateReadableId"
2
+
3
+ const gen = generateReadableId(5)
4
+
5
+ export function generateId5(): string {
6
+ return gen()
7
+ }