feat: extract shop from mp/shop — initial libreshop/shop
Some checks failed
Build and publish / build (push) Failing after 19s

Source moved verbatim from mp/shop/ on 2026-04-29; mp was the first
concrete adapter consuming the libreshop toolkit. Builds and publishes
git.librete.ch/libreshop/shop on every main / v* push via the standard
.gitea/workflows/build.yml shared across libreshop components.
This commit is contained in:
Michael Czechowski
2026-04-29 17:48:56 +02:00
commit 44107c0734
134 changed files with 19521 additions and 0 deletions

8
utils/numberFormatter.ts Normal file
View File

@@ -0,0 +1,8 @@
export const numberFormatter = (num: number, symbol?: string): string => {
const formatter = new Intl.NumberFormat("de-DE", {
style: "decimal",
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
return formatter.format(num) + (symbol ? ` ${symbol}` : "");
};

View File

@@ -0,0 +1,29 @@
type TailwindColorPrefix = "text" | "bg" | "shadow" | "from" | "to" | "via" | "group-hover:shadow" | "group-active:shadow";
type TailwindColorShade = 50 | 100 | 150 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
export const randomTailwindColor = (rand: number, prefix: TailwindColorPrefix = "bg", shade: TailwindColorShade = 100, suffix = "") => {
const tailwindColors = [
"red",
"orange",
"amber",
"yellow",
"lime",
"green",
"emerald",
"teal",
"cyan",
"sky",
"blue",
"indigo",
"violet",
"purple",
"fuchsia",
"pink",
"rose"
];
const randomColorIndex = rand % tailwindColors.length;
return `${prefix}-${tailwindColors[randomColorIndex]}-${shade}${suffix}`;
};

8
utils/scrollProgress.ts Normal file
View File

@@ -0,0 +1,8 @@
export const scrollProgress = () => {
if (!import.meta.client) return 0;
const { documentElement, body } = document;
const windowScroll = body.scrollTop || documentElement.scrollTop;
const height = documentElement.scrollHeight - documentElement.clientHeight;
return height > 0 ? Math.round((windowScroll / height) * 100) : 0;
};

9
utils/slugify.ts Normal file
View File

@@ -0,0 +1,9 @@
export function slugify(text: string): string {
return text
.toLowerCase()
.replace(/\s+/g, "-")
.replace(/ü/g, "ue")
.replace(/ä/g, "ae")
.replace(/ö/g, "oe")
.replace(/ß/g, "ss");
}

13
utils/trackEvent.ts Normal file
View File

@@ -0,0 +1,13 @@
declare global {
interface Window {
umami?: {
track: (event: string, data?: Record<string, unknown>) => void;
};
}
}
export const trackEvent = (event: string, data?: Record<string, unknown>) => {
if (import.meta.client && window.umami) {
window.umami.track(event, data);
}
};