make modal contents self-styling and add black variant

This commit is contained in:
vimtor
2026-05-14 21:15:14 +02:00
parent 50c3f2038e
commit 668133e604
8 changed files with 226 additions and 104 deletions

View File

@@ -64,4 +64,16 @@
color: var(--color-text);
text-align: center;
}
[data-slot="content"][data-variant="black"] {
background-color: #000;
border-color: rgba(255, 255, 255, 0.17);
color: rgba(255, 255, 255, 0.92);
font-family: var(--font-mono);
[data-slot="title"] {
color: rgba(255, 255, 255, 0.92);
font-family: var(--font-mono);
}
}
}

View File

@@ -6,6 +6,7 @@ interface ModalProps {
open: boolean
onClose: () => void
title?: string
variant?: "black"
children: JSX.Element
}
@@ -24,6 +25,7 @@ export function Modal(props: ModalProps) {
<Kobalte.Overlay data-component="modal" data-slot="overlay" onClick={props.onClose}>
<Kobalte.Content
data-slot="content"
data-variant={props.variant}
onClick={(e) => e.stopPropagation()}
onOpenAutoFocus={(e) => {
e.preventDefault()

View File

@@ -701,64 +701,6 @@
}
}
[data-slot="workspace-picker"] {
[data-slot="workspace-list"] {
width: 100%;
padding: 0;
margin: 0;
list-style: none;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
align-self: stretch;
outline: none;
overflow-y: auto;
max-height: 240px;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
[data-slot="workspace-item"] {
width: 100%;
display: flex;
padding: 8px 12px;
align-items: center;
gap: 8px;
align-self: stretch;
cursor: pointer;
[data-slot="selected-icon"] {
visibility: hidden;
color: rgba(255, 255, 255, 0.39);
font-family: "IBM Plex Mono", monospace;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 160%;
}
span:last-child {
color: rgba(255, 255, 255, 0.92);
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 160%;
}
&:hover,
&[data-active="true"] {
background: #161616;
[data-slot="selected-icon"] {
visibility: visible;
}
}
}
}
}
}
}
@@ -839,3 +781,64 @@
}
}
}
[data-component="black-workspace-picker-modal"] {
font-family: var(--font-mono);
[data-slot="workspace-list"] {
width: 100%;
padding: 0;
margin: 0;
list-style: none;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
align-self: stretch;
outline: none;
overflow-y: auto;
max-height: 240px;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
[data-slot="workspace-item"] {
width: 100%;
display: flex;
padding: 8px 12px;
align-items: center;
gap: 8px;
align-self: stretch;
cursor: pointer;
[data-slot="selected-icon"] {
visibility: hidden;
color: rgba(255, 255, 255, 0.39);
font-family: "IBM Plex Mono", monospace;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 160%;
}
span:last-child {
color: rgba(255, 255, 255, 0.92);
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 160%;
}
&:hover,
&[data-active="true"] {
background: #161616;
[data-slot="selected-icon"] {
visibility: visible;
}
}
}
}

View File

@@ -444,8 +444,13 @@ export default function BlackSubscribe() {
</div>
{/* Workspace picker modal */}
<Modal open={showWorkspacePicker() ?? false} onClose={() => {}} title={i18n.t("black.workspace.selectPlan")}>
<div data-slot="workspace-picker">
<Modal
open={showWorkspacePicker() ?? false}
onClose={() => {}}
title={i18n.t("black.workspace.selectPlan")}
variant="black"
>
<div data-component="black-workspace-picker-modal" data-slot="workspace-picker">
<ul
ref={listRef}
data-slot="workspace-list"

View File

@@ -34,6 +34,60 @@
background-color: var(--color-bg-surface);
}
}
}
[data-component="workspace-create-modal"] {
width: 100%;
button {
padding: var(--space-3) var(--space-4);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
background-color: var(--color-bg);
color: var(--color-text);
font-size: var(--font-size-sm);
font-family: var(--font-sans);
font-weight: 500;
cursor: pointer;
transition: all 0.15s ease;
&:hover:not(:disabled) {
background-color: var(--color-surface-hover);
border-color: var(--color-accent);
}
&:active {
transform: translateY(1px);
}
&:disabled {
opacity: 0.5;
transform: none;
}
&[data-color="primary"] {
background-color: var(--color-primary);
border-color: var(--color-primary);
color: var(--color-primary-text);
&:hover:not(:disabled) {
background-color: var(--color-primary-hover);
border-color: var(--color-primary-hover);
}
}
&[data-color="ghost"] {
background-color: transparent;
border-color: transparent;
color: var(--color-text-muted);
&:hover:not(:disabled) {
background-color: var(--color-surface-hover);
border-color: var(--color-border);
color: var(--color-text);
}
}
}
[data-slot="create-form"] {
width: 100%;

View File

@@ -98,26 +98,28 @@ export function WorkspacePicker() {
</Dropdown>
<Modal open={store.showForm} onClose={() => setStore("showForm", false)} title={i18n.t("workspace.modal.title")}>
<form data-slot="create-form" action={createWorkspace} method="post">
<div data-slot="create-input-group">
<input
ref={inputRef}
data-slot="create-input"
type="text"
name="workspaceName"
placeholder={i18n.t("workspace.modal.placeholder")}
required
/>
<div data-slot="button-group">
<button type="button" data-color="ghost" onClick={() => setStore("showForm", false)}>
{i18n.t("common.cancel")}
</button>
<button type="submit" data-color="primary" disabled={submission.pending}>
{submission.pending ? i18n.t("common.creating") : i18n.t("common.create")}
</button>
<div data-component="workspace-create-modal">
<form data-slot="create-form" action={createWorkspace} method="post">
<div data-slot="create-input-group">
<input
ref={inputRef}
data-slot="create-input"
type="text"
name="workspaceName"
placeholder={i18n.t("workspace.modal.placeholder")}
required
/>
<div data-slot="button-group">
<button type="button" data-color="ghost" onClick={() => setStore("showForm", false)}>
{i18n.t("common.cancel")}
</button>
<button type="submit" data-color="primary" disabled={submission.pending}>
{submission.pending ? i18n.t("common.creating") : i18n.t("common.create")}
</button>
</div>
</div>
</div>
</form>
</form>
</div>
</Modal>
</div>
)

View File

@@ -224,6 +224,48 @@
gap: 4px;
}
}
.paymentMethodModal {
button {
padding: var(--space-3) var(--space-4);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
background-color: var(--color-bg);
color: var(--color-text);
font-size: var(--font-size-sm);
font-family: var(--font-sans);
font-weight: 500;
cursor: pointer;
transition: all 0.15s ease;
&:hover:not(:disabled) {
background-color: var(--color-surface-hover);
border-color: var(--color-accent);
}
&:active {
transform: translateY(1px);
}
&:disabled {
opacity: 0.5;
transform: none;
}
&[data-color="ghost"] {
background-color: transparent;
border-color: transparent;
color: var(--color-text-muted);
&:hover:not(:disabled) {
background-color: var(--color-surface-hover);
border-color: var(--color-border);
color: var(--color-text);
}
}
}
[data-slot="modal-actions"] {
display: flex;
gap: var(--space-3);

View File

@@ -345,31 +345,33 @@ export function LiteSection() {
onClose={() => setStore("showModal", false)}
title={i18n.t("workspace.lite.promo.selectMethod")}
>
<div data-slot="modal-actions">
<button
type="button"
data-slot="method-button"
data-color="ghost"
disabled={checkoutSubmission.pending || busy()}
onClick={() => onClickSubscribe("alipay")}
>
<Show when={store.loading !== "alipay"}>
<IconAlipay style={{ width: "24px", height: "24px" }} />
</Show>
{store.loading === "alipay" ? i18n.t("workspace.lite.promo.subscribing") : "Alipay"}
</button>
<button
type="button"
data-slot="method-button"
data-color="ghost"
disabled={checkoutSubmission.pending || busy()}
onClick={() => onClickSubscribe("upi")}
>
<Show when={store.loading !== "upi"}>
<IconUpi style={{ width: "auto", height: "16px" }} />
</Show>
{store.loading === "upi" ? i18n.t("workspace.lite.promo.subscribing") : "UPI"}
</button>
<div class={styles.paymentMethodModal} data-component="lite-payment-method-modal">
<div data-slot="modal-actions">
<button
type="button"
data-slot="method-button"
data-color="ghost"
disabled={checkoutSubmission.pending || busy()}
onClick={() => onClickSubscribe("alipay")}
>
<Show when={store.loading !== "alipay"}>
<IconAlipay style={{ width: "24px", height: "24px" }} />
</Show>
{store.loading === "alipay" ? i18n.t("workspace.lite.promo.subscribing") : "Alipay"}
</button>
<button
type="button"
data-slot="method-button"
data-color="ghost"
disabled={checkoutSubmission.pending || busy()}
onClick={() => onClickSubscribe("upi")}
>
<Show when={store.loading !== "upi"}>
<IconUpi style={{ width: "auto", height: "16px" }} />
</Show>
{store.loading === "upi" ? i18n.t("workspace.lite.promo.subscribing") : "UPI"}
</button>
</div>
</div>
</Modal>
</section>