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:
parent
715b4bef5a
commit
b7f6e23fff
23 changed files with 490 additions and 189 deletions
54
src/components/Badge.css
Normal file
54
src/components/Badge.css
Normal 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
40
src/components/Badge.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
114
src/components/Notice.css
Normal 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);
|
||||
}
|
||||
|
|
@ -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} />;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue