update and add components (#691)

* update: switch

* add: notice

* update: link

* add: badge

* update heading

* update: basetext

* update: card

* fix: heading

* components-dev: fix

* lint

* update: margins
This commit is contained in:
prism 2026-01-27 15:21:12 +01:00 • committed by GitHub
parent 715b4bef5a
commit b7f6e23fff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 490 additions and 189 deletions

54
src/components/Badge.css Normal file
View file

@ -0,0 +1,54 @@
.vc-badge-base {
display: inline-flex;
align-items: center;
align-self: center;
gap: var(--space-4, 4px);
padding: 0 var(--space-8, 8px);
border-radius: var(--radius-round, 9999px);
box-sizing: border-box;
font-family: var(--font-display);
font-size: 12px;
font-weight: 700;
line-height: 1.3333;
letter-spacing: 0.02em;
text-align: center;
text-transform: uppercase;
}
.vc-badge-icon {
width: 12px;
height: 12px;
flex-shrink: 0;
}
/* Discord variants */
.vc-badge-default {
background-color: var(--badge-background-brand);
color: var(--badge-text-brand);
}
.vc-badge-expressive {
background-color: var(--badge-expressive-background-default);
color: var(--badge-expressive-text-default);
}
/* Equicord variants */
.vc-badge-danger {
background-color: var(--status-danger);
color: var(--white);
}
.vc-badge-positive {
background-color: var(--status-positive);
color: var(--white);
}
.vc-badge-warning {
background-color: var(--status-warning);
color: var(--black);
}
.vc-badge-premium {
background-color: var(--premium-tier-2-purple-for-gradients);
color: var(--white);
}

40
src/components/Badge.tsx Normal file
View file

@ -0,0 +1,40 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2026 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./Badge.css";
import { classNameFactory } from "@utils/css";
import { classes } from "@utils/misc";
import type { ComponentPropsWithoutRef, ComponentType, CSSProperties, ReactNode, SVGProps } from "react";
const cl = classNameFactory("vc-badge-");
export const BadgeVariants = ["default", "expressive", "danger", "positive", "warning", "premium"] as const;
export type BadgeVariant = typeof BadgeVariants[number];
export type BadgeProps = Omit<ComponentPropsWithoutRef<"div">, "children"> & {
text: ReactNode;
variant?: BadgeVariant;
icon?: ComponentType<SVGProps<SVGSVGElement> & { size?: string; }>;
color?: string;
backgroundColor?: string;
};
export function Badge({ text, variant = "default", icon: Icon, className, color, backgroundColor, style, ...restProps }: BadgeProps) {
const customStyle: CSSProperties | undefined = (color || backgroundColor) ? {
...style,
...(color && { color }),
...(backgroundColor && { backgroundColor })
} : style;
return (
<div className={classes(cl("base", variant), className)} style={customStyle} {...restProps}>
{Icon && <Icon size="xxs" className={cl("icon")} />}
{text}
</div>
);
}

View file

@ -1,8 +1,26 @@
.vc-text-base {
font-family: var(--font-primary);
color: var(--text-default);
line-height: normal;
/* Discord puts an insane default margin on p tags, so reset that here */
margin: 0;
}
}
.vc-text-selectable {
user-select: text;
}
.vc-text-line-clamp-1 {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.vc-text-line-clamp {
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
}
.vc-text-tabular-numbers {
font-variant-numeric: tabular-nums;
}

View file

@ -14,13 +14,13 @@ import type { ComponentPropsWithoutRef, ReactNode } from "react";
const textCls = classNameFactory("vc-text-");
export const TextSizes = {
xxs: "0.625rem",
xs: "0.75rem",
sm: "0.875rem",
md: "1rem",
lg: "1.25rem",
xl: "1.5rem",
xxl: "2rem"
xxs: { fontSize: "10px", lineHeight: "1.2" },
xs: { fontSize: "12px", lineHeight: "1.33333" },
sm: { fontSize: "14px", lineHeight: "1.28571" },
md: { fontSize: "16px", lineHeight: "1.25" },
lg: { fontSize: "20px", lineHeight: "1.2" },
xl: { fontSize: "24px", lineHeight: "1.25" },
xxl: { fontSize: "32px", lineHeight: "1.25" },
} as const;
export const TextWeights = {
@ -42,6 +42,15 @@ export const TextColors = {
"text-brand": "var(--text-brand)",
"text-strong": "var(--text-strong)",
"text-subtle": "var(--text-subtle)",
"text-invert": "var(--text-invert)",
"text-feedback-critical": "var(--text-feedback-critical)",
"text-feedback-info": "var(--text-feedback-info)",
"text-feedback-positive": "var(--text-feedback-positive)",
"text-feedback-warning": "var(--text-feedback-warning)",
"text-status-dnd": "var(--text-status-dnd)",
"text-status-idle": "var(--text-status-idle)",
"text-status-offline": "var(--text-status-offline)",
"text-status-online": "var(--text-status-online)",
"control-text-critical": "var(--control-text-critical-secondary-default)",
"control-text-primary": "var(--control-text-primary-default)",
} as const;
@ -49,8 +58,8 @@ export const TextColors = {
export function generateTextCss() {
let css = "";
for (const [size, value] of Object.entries(TextSizes)) {
css += `.${textCls(size)}{font-size:${value};}`;
for (const [size, { fontSize, lineHeight }] of Object.entries(TextSizes)) {
css += `.${textCls(size)}{font-size:${fontSize};line-height:${lineHeight};}`;
}
for (const [weight, value] of Object.entries(TextWeights)) {
@ -70,6 +79,9 @@ export type BaseTextProps<Tag extends TextTag = "div"> = ComponentPropsWithoutRe
weight?: TextWeight;
color?: TextColor;
tag?: Tag;
selectable?: boolean;
lineClamp?: number;
tabularNumbers?: boolean;
};
export function BaseText<T extends TextTag = "div">(props: BaseTextProps<T>): ReactNode {
@ -78,6 +90,9 @@ export function BaseText<T extends TextTag = "div">(props: BaseTextProps<T>): Re
weight = "normal",
color,
tag: Tag = "div",
selectable = false,
lineClamp,
tabularNumbers = false,
children,
className,
style,
@ -86,10 +101,18 @@ export function BaseText<T extends TextTag = "div">(props: BaseTextProps<T>): Re
return (
<Tag
className={classes(textCls("base", size, weight), className)}
className={classes(
textCls("base", size, weight),
selectable && textCls("selectable"),
lineClamp === 1 && textCls("line-clamp-1"),
lineClamp != null && lineClamp > 1 && textCls("line-clamp"),
tabularNumbers && textCls("tabular-numbers"),
className
)}
style={{
...style,
...(color && { color: TextColors[color] })
...(color && { color: TextColors[color] }),
...(lineClamp && lineClamp > 1 && { WebkitLineClamp: lineClamp })
}}
{...restProps}
>

View file

@ -1,20 +1,59 @@
.vc-card-base {
background: var(--card-background-default);
border-radius: var(--radius-sm, 8px)
border-style: solid;
border-width: 1px;
border-radius: var(--radius-sm);
position: relative;
}
.vc-card-normal {
border: 1px solid var(--border-subtle);
}
.vc-card-warning {
border: 1px solid var(--icon-status-idle);
.vc-card-primary {
background-color: var(--card-background-default);
border-color: var(--border-subtle);
color: var(--text-default);
}
.vc-card-danger {
border: 1px solid var(--icon-status-dnd);
background-color: var(--background-feedback-critical);
border-color: var(--background-feedback-critical);
color: var(--text-feedback-critical);
}
.vc-card-warning {
background-color: var(--background-feedback-warning);
border-color: var(--background-feedback-warning);
color: var(--text-feedback-warning);
}
.vc-card-success {
background-color: var(--background-feedback-positive);
border-color: var(--background-feedback-positive);
color: var(--text-feedback-positive);
}
.vc-card-brand {
background-color: var(--brand-500);
border-color: var(--brand-500);
}
.vc-card-outline {
background-color: transparent;
}
.vc-card-base a {
color: var(--text-link);
}
.vc-card-danger a,
.vc-card-warning a,
.vc-card-success a,
.vc-card-brand a {
color: var(--white);
font-weight: var(--font-weight-bold);
}
.vc-card-base a:hover {
text-decoration: underline;
}
.vc-card-defaultPadding {
padding: 1em;
}
}

View file

@ -13,18 +13,19 @@ import { ComponentPropsWithRef } from "react";
const cl = classNameFactory("vc-card-");
export interface CardProps extends ComponentPropsWithRef<"div"> {
variant?: "normal" | "warning" | "danger";
variant?: "primary" | "warning" | "danger" | "success" | "brand";
outline?: boolean;
/** Add a default padding of 1em to the card. This is implied if no className prop is passed */
defaultPadding?: boolean;
}
export function Card({ variant = "normal", defaultPadding, children, className, ...restProps }: CardProps) {
export function Card({ variant = "primary", outline = false, defaultPadding, children, className, ...restProps }: CardProps) {
const addDefaultPadding = defaultPadding != null
? defaultPadding
: !className;
return (
<div className={classes(cl("base", variant, addDefaultPadding && "defaultPadding"), className)} {...restProps}>
<div className={classes(cl("base", variant, outline && "outline", addDefaultPadding && "defaultPadding"), className)} {...restProps}>
{children}
</div>
);

View file

@ -1,51 +1,40 @@
.vc-h1,
.vc-h2 {
color: var(--text-strong);
font-weight: 600;
}
.vc-h2,
.vc-h3,
.vc-h4,
.vc-h5 {
color: var(--text-strong);
font-family: var(--font-display);
}
.vc-h1 {
font-size: 20px;
line-height: 24px
font-weight: 600;
line-height: 1.2;
}
.vc-h2 {
font-size: 16px;
line-height: 20px
font-weight: 600;
line-height: 1.25;
}
.vc-h3 {
font-weight: 500;
line-height: 24px
}
.vc-h3,
.vc-h4 {
font-size: 16px
}
.vc-h4 {
font-weight: 600;
letter-spacing: .3px
}
.vc-h4,
.vc-h5 {
line-height: 20px
}
.vc-h5 {
color: var(--text-strong);
font-size: 16px;
font-weight: 500;
margin-bottom: 8px;
text-transform: unset
line-height: 1.25;
}
.vc-h4 {
font-size: 16px;
font-weight: 600;
line-height: 1.25;
}
.vc-h5 {
font-size: 16px;
font-weight: 500;
line-height: 1.25;
}
.vc-h1-defaultMargin,
@ -62,4 +51,4 @@
.vc-h3-defaultMargin,
.vc-h5-defaultMargin {
margin-bottom: 8px
}
}

View file

@ -29,7 +29,7 @@ export function Heading<T extends HeadingTag>(props: HeadingProps<T>) {
} = props;
return (
<Tag className={classes(`vc-${Tag}`, !className && `vc-${Tag}-defaultMargin`, className)} {...restProps}>
<Tag className={classes(`vc-${Tag}`, `vc-${Tag}-defaultMargin`, className)} {...restProps}>
{children}
</Tag>
);

View file

@ -1,10 +1,9 @@
.vc-link {
color: var(--text-link);
text-decoration: none;
cursor: pointer;
text-decoration: var(--link-decoration);
}
.vc-link:hover {
.vc-link-underline-on-hover:hover {
text-decoration: underline;
}
@ -12,3 +11,22 @@
pointer-events: none;
opacity: 0.5;
}
/* Forced colors mode (high contrast accessibility) */
.enable-forced-colors .vc-link,
.enable-forced-colors .vc-link-underline-on-hover {
background-color: canvas;
text-decoration: underline;
color: linktext !important;
}
.enable-forced-colors .vc-link svg,
.enable-forced-colors .vc-link-underline-on-hover svg {
color: currentcolor;
}
.enable-forced-colors .vc-link:not([href]),
.enable-forced-colors .vc-link-underline-on-hover:not([href]) {
background-color: buttonface;
color: buttontext !important;
}

View file

@ -11,15 +11,34 @@ import type { AnchorHTMLAttributes, PropsWithChildren } from "react";
export interface LinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
disabled?: boolean;
useDefaultUnderlineStyles?: boolean;
}
export function Link({ disabled, className, children, ...restProps }: PropsWithChildren<LinkProps>) {
export function Link({
disabled,
useDefaultUnderlineStyles = true,
href,
rel,
target,
className,
children,
...restProps
}: PropsWithChildren<LinkProps>) {
const isInternal = href && /^(?:discord:\/)?\/[a-zA-Z0-9_-]/.test(href);
const needsSafeAttrs = href && !isInternal;
return (
<a
role="link"
target="_blank"
rel="noreferrer noopener"
className={classes("vc-link", disabled && "vc-link-disabled", className)}
href={href}
target={target ?? (needsSafeAttrs ? "_blank" : undefined)}
rel={rel ?? (needsSafeAttrs ? "noreferrer noopener" : undefined)}
className={classes(
"vc-link",
useDefaultUnderlineStyles && "vc-link-underline-on-hover",
disabled && "vc-link-disabled",
className
)}
aria-disabled={disabled}
{...restProps}
>

114
src/components/Notice.css Normal file
View file

@ -0,0 +1,114 @@
.vc-notice-container {
border-radius: var(--radius-sm, 8px);
box-sizing: border-box;
display: grid;
font-weight: 500;
grid-template-rows: 1fr;
padding: 8px;
width: 100%;
transition-duration: var(--expand-structural-duration, 0.2s);
transition-property: grid-template-rows, padding-bottom, padding-top, border-width;
transition-timing-function: var(--expand-easing-function, ease);
}
.vc-notice-hidden {
border-width: 0;
grid-template-rows: 0fr;
padding-bottom: 0;
padding-top: 0;
transition-delay: var(--collapse-fade-duration, 0.1s);
transition-duration: var(--collapse-structural-duration, 0.2s);
transition-timing-function: var(--collapse-easing-function, ease);
}
.vc-notice-inner {
display: flex;
min-height: 0;
opacity: 1;
overflow: hidden;
transition: opacity var(--expand-fade-duration, 0.1s) var(--expand-easing-function, ease) var(--expand-structural-duration, 0.2s);
}
.vc-notice-hidden .vc-notice-inner {
opacity: 0;
transition-delay: 0s;
transition-duration: var(--collapse-fade-duration, 0.1s);
transition-timing-function: var(--collapse-easing-function, ease);
}
.vc-notice-icon-wrapper {
display: flex;
}
.vc-notice-icon {
flex-shrink: 0;
width: 20px;
height: 20px;
}
.vc-notice-text {
align-self: center;
flex: 1 1 0%;
margin-left: var(--space-8, 8px);
font-family: var(--font-primary);
font-size: 14px;
font-weight: 500;
line-height: 1.2857;
color: var(--text-default);
}
.vc-notice-text p {
margin: 0;
}
.vc-notice-text p~p {
margin-top: 8px;
}
.vc-notice-action {
margin: auto 0 auto 16px;
}
/* Info variant */
.vc-notice-info {
background: var(--background-feedback-info);
border: 1px solid var(--icon-feedback-info);
color: var(--text-feedback-info);
}
.vc-notice-info .vc-notice-icon {
color: var(--icon-feedback-info);
}
/* Warning variant */
.vc-notice-warning {
background: var(--background-feedback-warning);
border: 1px solid var(--icon-feedback-warning);
color: var(--text-feedback-warning);
}
.vc-notice-warning .vc-notice-icon {
color: var(--icon-feedback-warning);
}
/* Danger variant */
.vc-notice-danger {
background: var(--background-feedback-critical);
border: 1px solid var(--icon-feedback-critical);
color: var(--text-feedback-critical);
}
.vc-notice-danger .vc-notice-icon {
color: var(--icon-feedback-critical);
}
/* Positive variant */
.vc-notice-positive {
background: var(--background-feedback-positive);
border: 1px solid var(--icon-feedback-positive);
color: var(--text-feedback-positive);
}
.vc-notice-positive .vc-notice-icon {
color: var(--icon-feedback-positive);
}

View file

@ -4,69 +4,88 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { filters, mapMangledModuleLazy } from "@webpack";
import type { ComponentType, CSSProperties, ReactNode } from "react";
import "./Notice.css";
export type NoticeType = "warn" | "info" | "danger" | "positive" | "preview";
import { classNameFactory } from "@utils/css";
import { classes } from "@utils/misc";
import type { ComponentPropsWithoutRef, ComponentType, ReactNode, SVGProps } from "react";
export const NoticeTypes: Record<"WARNING" | "INFO" | "ERROR" | "POSITIVE" | "PREVIEW", NoticeType> = {
WARNING: "warn",
INFO: "info",
ERROR: "danger",
POSITIVE: "positive",
PREVIEW: "preview"
};
const cl = classNameFactory("vc-notice-");
const { HelpMessage } = mapMangledModuleLazy('POSITIVE="positive"', {
HelpMessage: filters.byCode("messageType:")
});
export const NoticeVariants = ["info", "warning", "danger", "positive"] as const;
export interface NoticeProps {
export type NoticeVariant = typeof NoticeVariants[number];
export type NoticeProps = Omit<ComponentPropsWithoutRef<"div">, "children"> & {
children: ReactNode;
className?: string;
style?: CSSProperties;
icon?: ComponentType<{ className?: string; color?: string; }>;
variant?: NoticeVariant;
icon?: ComponentType<SVGProps<SVGSVGElement>>;
action?: ReactNode;
textColor?: string;
textVariant?: string;
hidden?: boolean;
}
function Warning({ children, className, style, icon, action, textColor, textVariant, hidden }: NoticeProps) {
return (
<HelpMessage messageType={NoticeTypes.WARNING} className={className} style={style} icon={icon} action={action} textColor={textColor} textVariant={textVariant} hidden={hidden}>
{children}
</HelpMessage>
);
}
function Info({ children, className, style, icon, action, textColor, textVariant, hidden }: NoticeProps) {
return (
<HelpMessage messageType={NoticeTypes.INFO} className={className} style={style} icon={icon} action={action} textColor={textColor} textVariant={textVariant} hidden={hidden}>
{children}
</HelpMessage>
);
}
function Error({ children, className, style, icon, action, textColor, textVariant, hidden }: NoticeProps) {
return (
<HelpMessage messageType={NoticeTypes.ERROR} className={className} style={style} icon={icon} action={action} textColor={textColor} textVariant={textVariant} hidden={hidden}>
{children}
</HelpMessage>
);
}
function Positive({ children, className, style, icon, action, textColor, textVariant, hidden }: NoticeProps) {
return (
<HelpMessage messageType={NoticeTypes.POSITIVE} className={className} style={style} icon={icon} action={action} textColor={textColor} textVariant={textVariant} hidden={hidden}>
{children}
</HelpMessage>
);
}
export const Notice = {
Warning,
Info,
Error,
Positive
};
function InfoIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 24 24" {...props}>
<path fill="currentColor" fillRule="evenodd" d="M23 12a11 11 0 1 1-22 0 11 11 0 0 1 22 0Zm-9.5-4.75a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Zm-.77 3.96a1 1 0 1 0-1.96-.42l-1.04 4.86a2.77 2.77 0 0 0 4.31 2.83l.24-.17a1 1 0 1 0-1.16-1.62l-.24.17a.77.77 0 0 1-1.2-.79l1.05-4.86Z" clipRule="evenodd" />
</svg>
);
}
function WarningIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 24 24" {...props}>
<path fill="currentColor" fillRule="evenodd" d="M10 3.1a2.37 2.37 0 0 1 4 0l8.71 14.75c.84 1.41-.26 3.15-2 3.15H3.29c-1.74 0-2.84-1.74-2-3.15L9.99 3.1Zm3.25 14.65a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0ZM13.06 14l.37-5.94a1 1 0 0 0-1-1.06h-.87a1 1 0 0 0-1 1.06l.38 5.94a1.06 1.06 0 0 0 2.12 0Z" clipRule="evenodd" />
</svg>
);
}
function DangerIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 24 24" {...props}>
<path fill="currentColor" fillRule="evenodd" d="M12 23a11 11 0 1 0 0-22 11 11 0 0 0 0 22Zm4.7-15.7a1 1 0 0 0-1.4 0L12 10.58l-3.3-3.3a1 1 0 0 0-1.4 1.42L10.58 12l-3.3 3.3a1 1 0 1 0 1.42 1.4L12 13.42l3.3 3.3a1 1 0 0 0 1.4-1.42L13.42 12l3.3-3.3a1 1 0 0 0 0-1.4Z" clipRule="evenodd" />
</svg>
);
}
function PositiveIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 24 24" {...props}>
<path fill="currentColor" fillRule="evenodd" d="M12 23a11 11 0 1 0 0-22 11 11 0 0 0 0 22Zm5.7-13.3a1 1 0 0 0-1.4-1.4L10 14.58l-2.3-2.3a1 1 0 0 0-1.4 1.42l3 3a1 1 0 0 0 1.4 0l7-7Z" clipRule="evenodd" />
</svg>
);
}
const DefaultIcons: Record<NoticeVariant, ComponentType<SVGProps<SVGSVGElement>>> = {
info: InfoIcon,
warning: WarningIcon,
danger: DangerIcon,
positive: PositiveIcon
};
export function Notice({ children, variant = "info", icon, action, hidden = false, className, ...restProps }: NoticeProps) {
const Icon = icon ?? DefaultIcons[variant];
return (
<div className={classes(cl("container", variant), hidden && cl("hidden"), className)} {...restProps}>
<div className={cl("inner")}>
<div className={cl("icon-wrapper")}>
<Icon className={cl("icon")} />
</div>
<div className={cl("text")}>
{children}
</div>
{action && (
<div className={cl("action")}>
{action}
</div>
)}
</div>
</div>
);
}
Notice.Info = (props: Omit<NoticeProps, "variant">) => <Notice variant="info" {...props} />;
Notice.Warning = (props: Omit<NoticeProps, "variant">) => <Notice variant="warning" {...props} />;
Notice.Danger = (props: Omit<NoticeProps, "variant">) => <Notice variant="danger" {...props} />;
Notice.Positive = (props: Omit<NoticeProps, "variant">) => <Notice variant="positive" {...props} />;

View file

@ -16,6 +16,11 @@
transition: background-color 200ms ease, border-color 200ms ease;
}
.vc-switch-checked .vc-switch-indicator {
background: var(--switch-background-selected-default);
border-color: var(--switch-border-selected-default);
}
.vc-switch-disabled {
cursor: not-allowed;
opacity: 0.5;
@ -29,17 +34,12 @@
box-shadow: 0 0 0 2px var(--border-focus, #00a8fc);
}
.vc-switch-indicator:hover {
.vc-switch-container:hover .vc-switch-indicator {
background: var(--switch-background-hover);
border-color: var(--switch-border-hover);
}
.vc-switch-checked .vc-switch-indicator {
background: var(--switch-background-selected-default);
border-color: var(--switch-border-selected-default);
}
.vc-switch-checked .vc-switch-indicator:hover {
.vc-switch-checked:hover .vc-switch-indicator {
background: var(--switch-background-selected-hover);
border-color: var(--switch-border-selected-hover);
}

View file

@ -29,6 +29,7 @@ export interface SwitchProps {
onChange: (checked: boolean) => void;
disabled?: boolean;
id?: string;
// checkmark/x icon in thumb
hasIcon?: boolean;
innerRef?: Ref<HTMLInputElement>;
"aria-describedby"?: string;

View file

@ -21,12 +21,12 @@ import { classNameFactory } from "@utils/css";
const marginCls = classNameFactory("vc-margin-");
const Directions = ["top", "bottom", "left", "right"] as const;
const Sizes = [8, 16, 20] as const;
const Sizes = [4, 8, 16, 20, 24, 40, 60] as const;
export type MarginDirection = (typeof Directions)[number];
export type MarginSize = (typeof Sizes)[number];
export const Margins: Record<`${MarginDirection}${MarginSize}`, string> = {} as any;
export const Margins = {} as Record<`${MarginDirection}${MarginSize}` | "reset" | "centerHorz", string>;
export function generateMarginCss() {
let css = "";
@ -39,5 +39,11 @@ export function generateMarginCss() {
}
}
Margins.reset = marginCls("reset");
css += `.${Margins.reset}{margin:0;}`;
Margins.centerHorz = marginCls("center-horz");
css += `.${Margins.centerHorz}{margin-left:auto;margin-right:auto;}`;
return css;
}

View file

@ -18,8 +18,8 @@
import "./AddonCard.css";
import { Badge } from "@components/Badge";
import { BaseText } from "@components/BaseText";
import { AddonBadge } from "@components/settings/PluginBadge";
import { Switch } from "@components/Switch";
import { classNameFactory } from "@utils/css";
import { Tooltip, useRef } from "@webpack/common";
@ -72,7 +72,7 @@ export function AddonCard({ disabled, isNew, sourceBadge, tooltip, name, infoBut
{name}
</div>
</div>
{isNew && <AddonBadge text="NEW" color="#ED4245" />}
{isNew && <Badge text="NEW" variant="danger" />}
</BaseText>
{!!author && (

View file

@ -18,9 +18,9 @@
import "./AddonCard.css";
import { Badge } from "@components/Badge";
import { BaseText } from "@components/BaseText";
import { Switch } from "@components/settings";
import { AddonBadge } from "@components/settings/PluginBadge";
import { classNameFactory } from "@utils/css";
import { useRef } from "@webpack/common";
import type { MouseEventHandler, ReactNode } from "react";
@ -101,7 +101,7 @@ export function OnlineThemeCard({
)}
</div>
</div>
{isNew && <AddonBadge text="NEW" color="#ED4245" />}
{isNew && <Badge text="NEW" variant="danger" />}
</BaseText>
{!!author && (

View file

@ -1,29 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export function AddonBadge({ text, color }) {
return (
<div className="vc-addon-badge" style={{
backgroundColor: color,
justifySelf: "flex-end",
marginLeft: "auto"
}}>
{text}
</div>
);
}

View file

@ -4,10 +4,10 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
export * from "../Badge";
export * from "../Switch";
export * from "./AddonCard";
export * from "./DonateButton";
export * from "./PluginBadge";
export * from "./QuickAction";
export * from "./SpecialCard";
export * from "./tabs";

View file

@ -48,19 +48,6 @@
grid-template-columns: 1fr 150px;
}
.vc-addon-badge {
padding: 0 6px;
font-family: var(--font-display);
font-weight: 500;
border-radius: 8px;
height: 16px;
font-size: 12px;
line-height: 16px;
color: var(--white-500);
text-align: center;
margin-right: 8px;
}
.vc-plugins-dep-name {
margin: 0 auto;
}
@ -298,4 +285,4 @@
.vc-plugins-ui-elements {
margin-top: 16px;
}
}

View file

@ -22,9 +22,11 @@ export default function CardTab() {
</Paragraph>
<Paragraph color="text-muted">
This is a {variant} card variant. Use for {
variant === "normal" ? "general content" :
variant === "primary" ? "general content" :
variant === "warning" ? "cautionary messages" :
"error or destructive actions"
variant === "danger" ? "error or destructive actions" :
variant === "success" ? "positive feedback" :
"branded content"
}.
</Paragraph>
</Card>
@ -60,7 +62,7 @@ export default function CardTab() {
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
Cards can be nested for complex layouts.
</Paragraph>
<Card variant="normal">
<Card variant="primary">
<Paragraph style={{ fontWeight: 600, marginBottom: 8 }}>Outer Card</Paragraph>
<Card variant="warning" style={{ marginBottom: 8 }}>
<Paragraph>Nested warning card</Paragraph>
@ -76,7 +78,7 @@ export default function CardTab() {
Cards accept custom className and style props.
</Paragraph>
<Card
variant="normal"
variant="primary"
style={{
borderLeft: "4px solid var(--text-brand)",
borderRadius: 4,
@ -87,7 +89,7 @@ export default function CardTab() {
</SectionWrapper>
<SectionWrapper title="Props">
<Paragraph color="text-muted">• variant - "normal" | "warning" | "danger" (default: "normal")</Paragraph>
<Paragraph color="text-muted">• variant - "primary" | "warning" | "danger" | "success" | "brand" (default: "primary")</Paragraph>
<Paragraph color="text-muted">• defaultPadding - Add 1em padding (default: true if no className)</Paragraph>
<Paragraph color="text-muted">• className - Custom CSS class</Paragraph>
<Paragraph color="text-muted">• style - Inline styles</Paragraph>

View file

@ -22,7 +22,7 @@ export type AvatarStatus = typeof AvatarStatuses[number];
export const GuildIconSizes = [16, 20, 24, 32, 40, 48, 56, 80, 96] as const;
export type GuildIconSize = typeof GuildIconSizes[number];
export const CardVariants = ["normal", "warning", "danger"] as const;
export const CardVariants = ["primary", "warning", "danger", "success", "brand"] as const;
export type CardVariant = typeof CardVariants[number];
export const SpinnerTypes = ["wanderingCubes", "chasingDots", "pulsingEllipsis", "spinningCircle", "spinningCircleSimple", "lowMotion"] as const;

View file

@ -39,7 +39,7 @@ export default definePlugin({
settingsAboutComponent() {
return (
<Card variant="normal">
<Card variant="primary">
<Flex flexDirection="column" gap="4px">
<Paragraph size="md" weight="semibold">The default behaviour is the following:</Paragraph>
<Paragraph>