refactor progress
This commit is contained in:
64
packages/ui/src/utils/AvatarMediaUtils.tsx
Normal file
64
packages/ui/src/utils/AvatarMediaUtils.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @jsxRuntime automatic */
|
||||
/** @jsxImportSource hono/jsx */
|
||||
|
||||
import type {MediaProxyImageSize} from '@fluxer/constants/src/MediaProxyImageSizes';
|
||||
|
||||
const DEFAULT_AVATAR_PRIMARY_COLORS = [0x4641d9, 0xf0b100, 0x00bba7, 0x2b7fff, 0xad46ff, 0x6a7282];
|
||||
export const DEFAULT_AVATAR_COUNT = BigInt(DEFAULT_AVATAR_PRIMARY_COLORS.length);
|
||||
|
||||
export const normalizeEndpoint = (endpoint: string): string => endpoint.replace(/\/$/, '');
|
||||
|
||||
export const parseAvatarHash = (value: string) => {
|
||||
const animated = value.startsWith('a_');
|
||||
const hash = animated ? value.slice(2) : value;
|
||||
return {animated, hash};
|
||||
};
|
||||
|
||||
export const buildMediaUrl = ({
|
||||
endpoint,
|
||||
path,
|
||||
id,
|
||||
hash,
|
||||
size,
|
||||
animated,
|
||||
}: {
|
||||
endpoint: string;
|
||||
path: string;
|
||||
id: string;
|
||||
hash: string;
|
||||
size: MediaProxyImageSize;
|
||||
animated?: boolean;
|
||||
}) => {
|
||||
const normalizedEndpoint = normalizeEndpoint(endpoint);
|
||||
const params = new URLSearchParams();
|
||||
params.set('size', size.toString());
|
||||
if (animated) {
|
||||
params.set('animated', 'true');
|
||||
}
|
||||
const query = params.toString();
|
||||
return `${normalizedEndpoint}/${path}/${id}/${hash}.webp${query ? `?${query}` : ''}`;
|
||||
};
|
||||
|
||||
export const getDefaultAvatarIndex = (id: string): number => Number(BigInt(id) % DEFAULT_AVATAR_COUNT);
|
||||
|
||||
export const getDefaultAvatarPrimaryColor = (id: string): number =>
|
||||
DEFAULT_AVATAR_PRIMARY_COLORS[getDefaultAvatarIndex(id)];
|
||||
33
packages/ui/src/utils/ClassNames.tsx
Normal file
33
packages/ui/src/utils/ClassNames.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @jsxRuntime automatic */
|
||||
/** @jsxImportSource hono/jsx */
|
||||
|
||||
export type ClassNameInput = string | boolean | undefined | null;
|
||||
|
||||
export function cn(...inputs: Array<ClassNameInput>): string {
|
||||
const classes: Array<string> = [];
|
||||
for (const input of inputs) {
|
||||
if (typeof input === 'string' && input.length > 0) {
|
||||
classes.push(input);
|
||||
}
|
||||
}
|
||||
return classes.join(' ');
|
||||
}
|
||||
94
packages/ui/src/utils/ColorVariants.tsx
Normal file
94
packages/ui/src/utils/ColorVariants.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @jsxRuntime automatic */
|
||||
/** @jsxImportSource hono/jsx */
|
||||
|
||||
export type ColorTone = 'neutral' | 'info' | 'success' | 'warning' | 'danger' | 'primary' | 'purple' | 'orange';
|
||||
export type ColorIntensity = 'subtle' | 'normal' | 'strong';
|
||||
|
||||
export interface ColorVariant {
|
||||
bg: string;
|
||||
text: string;
|
||||
border?: string;
|
||||
}
|
||||
|
||||
export const colorVariants: Record<ColorTone, Record<ColorIntensity, ColorVariant>> = {
|
||||
neutral: {
|
||||
subtle: {bg: 'bg-neutral-50', text: 'text-neutral-700', border: 'border-neutral-200'},
|
||||
normal: {bg: 'bg-neutral-100', text: 'text-neutral-700', border: 'border-neutral-200'},
|
||||
strong: {bg: 'bg-neutral-900', text: 'text-white'},
|
||||
},
|
||||
info: {
|
||||
subtle: {bg: 'bg-blue-50', text: 'text-blue-800', border: 'border-blue-200'},
|
||||
normal: {bg: 'bg-blue-100', text: 'text-blue-700', border: 'border-blue-200'},
|
||||
strong: {bg: 'bg-blue-600', text: 'text-white'},
|
||||
},
|
||||
success: {
|
||||
subtle: {bg: 'bg-green-50', text: 'text-green-800', border: 'border-green-200'},
|
||||
normal: {bg: 'bg-green-100', text: 'text-green-700', border: 'border-green-200'},
|
||||
strong: {bg: 'bg-green-600', text: 'text-white'},
|
||||
},
|
||||
warning: {
|
||||
subtle: {bg: 'bg-yellow-50', text: 'text-yellow-800', border: 'border-yellow-200'},
|
||||
normal: {bg: 'bg-yellow-100', text: 'text-yellow-700', border: 'border-yellow-200'},
|
||||
strong: {bg: 'bg-yellow-600', text: 'text-white'},
|
||||
},
|
||||
danger: {
|
||||
subtle: {bg: 'bg-red-50', text: 'text-red-800', border: 'border-red-200'},
|
||||
normal: {bg: 'bg-red-100', text: 'text-red-700', border: 'border-red-200'},
|
||||
strong: {bg: 'bg-red-600', text: 'text-white'},
|
||||
},
|
||||
primary: {
|
||||
subtle: {bg: 'bg-neutral-100', text: 'text-neutral-700'},
|
||||
normal: {bg: 'bg-neutral-900', text: 'text-white'},
|
||||
strong: {bg: 'bg-neutral-900', text: 'text-white'},
|
||||
},
|
||||
purple: {
|
||||
subtle: {bg: 'bg-purple-50', text: 'text-purple-800', border: 'border-purple-200'},
|
||||
normal: {bg: 'bg-purple-100', text: 'text-purple-700', border: 'border-purple-200'},
|
||||
strong: {bg: 'bg-purple-600', text: 'text-white'},
|
||||
},
|
||||
orange: {
|
||||
subtle: {bg: 'bg-orange-50', text: 'text-orange-800', border: 'border-orange-200'},
|
||||
normal: {bg: 'bg-orange-100', text: 'text-orange-700', border: 'border-orange-200'},
|
||||
strong: {bg: 'bg-orange-600', text: 'text-white'},
|
||||
},
|
||||
};
|
||||
|
||||
export function getColorClasses(tone: ColorTone, intensity: ColorIntensity = 'normal'): string {
|
||||
const variant = colorVariants[tone][intensity];
|
||||
const classes = [variant.bg, variant.text];
|
||||
if (variant.border) {
|
||||
classes.push(variant.border);
|
||||
}
|
||||
return classes.join(' ');
|
||||
}
|
||||
|
||||
export type AlertTone = 'error' | 'warning' | 'success' | 'info';
|
||||
|
||||
export function getAlertClasses(tone: AlertTone): string {
|
||||
const toneMapping: Record<AlertTone, ColorTone> = {
|
||||
error: 'danger',
|
||||
warning: 'warning',
|
||||
success: 'success',
|
||||
info: 'info',
|
||||
};
|
||||
return getColorClasses(toneMapping[tone], 'subtle');
|
||||
}
|
||||
42
packages/ui/src/utils/FormatNumber.tsx
Normal file
42
packages/ui/src/utils/FormatNumber.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @jsxRuntime automatic */
|
||||
/** @jsxImportSource hono/jsx */
|
||||
|
||||
export function formatNumber(value: number): string {
|
||||
const digits = Math.max(0, Math.trunc(value)).toString();
|
||||
return formatNumberDigits(digits);
|
||||
}
|
||||
|
||||
function formatNumberDigits(digits: string): string {
|
||||
const len = digits.length;
|
||||
if (len <= 3) return digits;
|
||||
const headLength = len % 3 === 0 ? 3 : len % 3;
|
||||
const head = digits.slice(0, headLength);
|
||||
const tail = digits.slice(headLength);
|
||||
return `${head}${chunkDigits(tail)}`;
|
||||
}
|
||||
|
||||
function chunkDigits(digits: string): string {
|
||||
if (digits.length <= 3) return digits;
|
||||
const head = digits.slice(0, 3);
|
||||
const tail = digits.slice(3);
|
||||
return `${head},${chunkDigits(tail)}`;
|
||||
}
|
||||
35
packages/ui/src/utils/FormatSize.tsx
Normal file
35
packages/ui/src/utils/FormatSize.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @jsxRuntime automatic */
|
||||
/** @jsxImportSource hono/jsx */
|
||||
|
||||
export function formatFileSize(bytes: number, decimals = 2): string {
|
||||
if (bytes === 0) {
|
||||
return '0 Bytes';
|
||||
}
|
||||
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`;
|
||||
}
|
||||
214
packages/ui/src/utils/FormatUser.tsx
Normal file
214
packages/ui/src/utils/FormatUser.tsx
Normal file
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @jsxRuntime automatic */
|
||||
/** @jsxImportSource hono/jsx */
|
||||
|
||||
import {
|
||||
MEDIA_PROXY_AVATAR_SIZE_DEFAULT,
|
||||
MEDIA_PROXY_GUILD_BANNER_SIZE_DEFAULT,
|
||||
MEDIA_PROXY_GUILD_EMBED_SPLASH_SIZE_DEFAULT,
|
||||
MEDIA_PROXY_GUILD_SPLASH_SIZE_DEFAULT,
|
||||
MEDIA_PROXY_ICON_SIZE_DEFAULT,
|
||||
MEDIA_PROXY_PROFILE_BANNER_SIZE_MODAL,
|
||||
} from '@fluxer/constants/src/MediaProxyAssetSizes';
|
||||
import {extractTimestampFromSnowflakeAsDate} from '@fluxer/snowflake/src/SnowflakeUtils';
|
||||
import {
|
||||
buildMediaUrl,
|
||||
getDefaultAvatarIndex,
|
||||
normalizeEndpoint,
|
||||
parseAvatarHash,
|
||||
} from '@fluxer/ui/src/utils/AvatarMediaUtils';
|
||||
|
||||
export function formatDiscriminator(discriminator: number | string): string {
|
||||
const discStr = String(discriminator).padStart(4, '0');
|
||||
return discStr;
|
||||
}
|
||||
|
||||
export function formatUserTag(username: string, discriminator: string | number): string {
|
||||
const discStr = typeof discriminator === 'number' ? formatDiscriminator(discriminator) : discriminator;
|
||||
return `${username}#${discStr}`;
|
||||
}
|
||||
|
||||
export function getInitials(name: string): string {
|
||||
if (!name || name.trim() === '') {
|
||||
return '?';
|
||||
}
|
||||
const words = name.trim().split(/\s+/);
|
||||
const firstWord = words[0];
|
||||
const lastWord = words[words.length - 1];
|
||||
|
||||
if (!firstWord) {
|
||||
return '?';
|
||||
}
|
||||
|
||||
if (words.length === 1 || !lastWord) {
|
||||
return firstWord.charAt(0).toUpperCase();
|
||||
}
|
||||
|
||||
return (firstWord.charAt(0) + lastWord.charAt(0)).toUpperCase();
|
||||
}
|
||||
|
||||
export function extractTimestampFromSnowflake(snowflake: string, epoch = '1420070400000'): string {
|
||||
try {
|
||||
const date = extractTimestampFromSnowflakeAsDate(snowflake, epoch);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
const year = date.getUTCFullYear().toString();
|
||||
const month = (date.getUTCMonth() + 1).toString().padStart(2, '0');
|
||||
const day = date.getUTCDate().toString().padStart(2, '0');
|
||||
const hour = date.getUTCHours().toString().padStart(2, '0');
|
||||
const minute = date.getUTCMinutes().toString().padStart(2, '0');
|
||||
|
||||
const monthNames: Record<string, string> = {
|
||||
'01': 'Jan',
|
||||
'02': 'Feb',
|
||||
'03': 'Mar',
|
||||
'04': 'Apr',
|
||||
'05': 'May',
|
||||
'06': 'Jun',
|
||||
'07': 'Jul',
|
||||
'08': 'Aug',
|
||||
'09': 'Sep',
|
||||
'10': 'Oct',
|
||||
'11': 'Nov',
|
||||
'12': 'Dec',
|
||||
};
|
||||
|
||||
const monthName = monthNames[month] ?? month;
|
||||
return `${monthName} ${day}, ${year} at ${hour}:${minute}`;
|
||||
} catch {
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
export function getUserAvatarUrl(
|
||||
mediaEndpoint: string,
|
||||
staticCdnEndpoint: string,
|
||||
userId: string,
|
||||
avatar: string | null,
|
||||
forceStatic: boolean,
|
||||
_assetVersion: string,
|
||||
): string {
|
||||
if (avatar) {
|
||||
const {hash, animated} = parseAvatarHash(avatar);
|
||||
const shouldAnimate = animated && !forceStatic;
|
||||
return buildMediaUrl({
|
||||
endpoint: mediaEndpoint,
|
||||
path: 'avatars',
|
||||
id: userId,
|
||||
hash,
|
||||
size: MEDIA_PROXY_AVATAR_SIZE_DEFAULT,
|
||||
animated: shouldAnimate,
|
||||
});
|
||||
}
|
||||
const defaultIndex = getDefaultAvatarIndex(userId);
|
||||
return `${normalizeEndpoint(staticCdnEndpoint)}/avatars/${defaultIndex}.png`;
|
||||
}
|
||||
|
||||
export function getGuildIconUrl(
|
||||
mediaEndpoint: string,
|
||||
guildId: string,
|
||||
icon: string | null,
|
||||
forceStatic: boolean,
|
||||
): string | null {
|
||||
if (!icon) {
|
||||
return null;
|
||||
}
|
||||
const {hash, animated} = parseAvatarHash(icon);
|
||||
const shouldAnimate = animated && !forceStatic;
|
||||
return buildMediaUrl({
|
||||
endpoint: mediaEndpoint,
|
||||
path: 'icons',
|
||||
id: guildId,
|
||||
hash,
|
||||
size: MEDIA_PROXY_ICON_SIZE_DEFAULT,
|
||||
animated: shouldAnimate,
|
||||
});
|
||||
}
|
||||
|
||||
export function getUserBannerUrl(
|
||||
mediaEndpoint: string,
|
||||
userId: string,
|
||||
banner: string | null,
|
||||
forceStatic: boolean,
|
||||
): string | null {
|
||||
if (!banner) {
|
||||
return null;
|
||||
}
|
||||
const {hash, animated} = parseAvatarHash(banner);
|
||||
const shouldAnimate = animated && !forceStatic;
|
||||
return buildMediaUrl({
|
||||
endpoint: mediaEndpoint,
|
||||
path: 'banners',
|
||||
id: userId,
|
||||
hash,
|
||||
size: MEDIA_PROXY_PROFILE_BANNER_SIZE_MODAL,
|
||||
animated: shouldAnimate,
|
||||
});
|
||||
}
|
||||
|
||||
export function getGuildBannerUrl(
|
||||
mediaEndpoint: string,
|
||||
guildId: string,
|
||||
banner: string | null,
|
||||
forceStatic: boolean,
|
||||
): string | null {
|
||||
if (!banner) {
|
||||
return null;
|
||||
}
|
||||
const {hash, animated} = parseAvatarHash(banner);
|
||||
const shouldAnimate = animated && !forceStatic;
|
||||
return buildMediaUrl({
|
||||
endpoint: mediaEndpoint,
|
||||
path: 'banners',
|
||||
id: guildId,
|
||||
hash,
|
||||
size: MEDIA_PROXY_GUILD_BANNER_SIZE_DEFAULT,
|
||||
animated: shouldAnimate,
|
||||
});
|
||||
}
|
||||
|
||||
export function getGuildSplashUrl(mediaEndpoint: string, guildId: string, splash: string | null): string | null {
|
||||
if (!splash) {
|
||||
return null;
|
||||
}
|
||||
return buildMediaUrl({
|
||||
endpoint: mediaEndpoint,
|
||||
path: 'splashes',
|
||||
id: guildId,
|
||||
hash: splash,
|
||||
size: MEDIA_PROXY_GUILD_SPLASH_SIZE_DEFAULT,
|
||||
});
|
||||
}
|
||||
|
||||
export function getGuildEmbedSplashUrl(mediaEndpoint: string, guildId: string, splash: string | null): string | null {
|
||||
if (!splash) {
|
||||
return null;
|
||||
}
|
||||
return buildMediaUrl({
|
||||
endpoint: mediaEndpoint,
|
||||
path: 'embed-splashes',
|
||||
id: guildId,
|
||||
hash: splash,
|
||||
size: MEDIA_PROXY_GUILD_EMBED_SPLASH_SIZE_DEFAULT,
|
||||
});
|
||||
}
|
||||
53
packages/ui/src/utils/VariantClasses.tsx
Normal file
53
packages/ui/src/utils/VariantClasses.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @jsxRuntime automatic */
|
||||
/** @jsxImportSource hono/jsx */
|
||||
|
||||
export function createVariantClasses<T extends string>(
|
||||
variants: Record<T, string>,
|
||||
base: string = '',
|
||||
): (variant: T) => string {
|
||||
return (variant: T) => {
|
||||
const classes = variants[variant];
|
||||
return base ? `${base} ${classes}` : classes;
|
||||
};
|
||||
}
|
||||
|
||||
export function createCompoundVariantClasses<T extends string, S extends string>(
|
||||
variants: Record<T, string>,
|
||||
sizes?: Record<S, string>,
|
||||
base: string = '',
|
||||
): {
|
||||
getVariant: (variant: T) => string;
|
||||
getSize: (size: S) => string;
|
||||
getBase: () => string;
|
||||
} {
|
||||
return {
|
||||
getVariant: (variant: T) => {
|
||||
const classes = variants[variant];
|
||||
return base ? `${base} ${classes}` : classes;
|
||||
},
|
||||
getSize: (size: S) => {
|
||||
if (!sizes) return '';
|
||||
return sizes[size];
|
||||
},
|
||||
getBase: () => base,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user