mirror of
https://github.com/bitwarden/browser
synced 2026-01-30 16:23:53 +00:00
Merge branch 'main' into PM-7853-Clients-Hide-Send-from-navigation-when-user-is-subject-to-the-disable-Send-policy
This commit is contained in:
@@ -2498,7 +2498,7 @@
|
||||
}
|
||||
},
|
||||
"topLayerHijackWarning": {
|
||||
"message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure."
|
||||
"message": "Bu səhifə Bitwarden təcrübəsinə müdaxilə edir. Bitwarden daxili menyusu, təhlükəsizlik tədbiri olaraq müvəqqəti sıradan çıxarılıb."
|
||||
},
|
||||
"setMasterPassword": {
|
||||
"message": "Ana parolu ayarla"
|
||||
@@ -4124,7 +4124,7 @@
|
||||
"message": "Avto-doldurula bilmir"
|
||||
},
|
||||
"cannotAutofillExactMatch": {
|
||||
"message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item."
|
||||
"message": "İlkin uyuşma 'Tam Uyuşur' olaraq ayarlanıb. Hazırkı veb sayt, bu element üçün saxlanılmış giriş məlumatları ilə tam uyuşmur."
|
||||
},
|
||||
"okay": {
|
||||
"message": "Oldu"
|
||||
|
||||
@@ -1486,7 +1486,7 @@
|
||||
"message": "选择一个文件"
|
||||
},
|
||||
"itemsTransferred": {
|
||||
"message": "项目已传输"
|
||||
"message": "项目已转移"
|
||||
},
|
||||
"maxFileSize": {
|
||||
"message": "文件最大为 500 MB。"
|
||||
@@ -3804,7 +3804,7 @@
|
||||
"description": "Browser extension/addon"
|
||||
},
|
||||
"desktop": {
|
||||
"message": "桌面",
|
||||
"message": "桌面端",
|
||||
"description": "Desktop app"
|
||||
},
|
||||
"webVault": {
|
||||
@@ -5707,7 +5707,7 @@
|
||||
"message": "导入现有密码"
|
||||
},
|
||||
"emptyVaultNudgeBody": {
|
||||
"message": "使用导入器快速将登录传输到 Bitwarden 而无需手动添加。"
|
||||
"message": "使用导入器快速将登录转移到 Bitwarden 而无需手动添加。"
|
||||
},
|
||||
"emptyVaultNudgeButton": {
|
||||
"message": "立即导入"
|
||||
@@ -6014,7 +6014,7 @@
|
||||
"message": "我该如何管理我的密码库?"
|
||||
},
|
||||
"transferItemsToOrganizationTitle": {
|
||||
"message": "传输项目到 $ORGANIZATION$",
|
||||
"message": "转移项目到 $ORGANIZATION$",
|
||||
"placeholders": {
|
||||
"organization": {
|
||||
"content": "$1",
|
||||
@@ -6023,7 +6023,7 @@
|
||||
}
|
||||
},
|
||||
"transferItemsToOrganizationContent": {
|
||||
"message": "出于安全和合规考虑,$ORGANIZATION$ 要求所有项目归组织所有。点击「接受」以传输您的项目的所有权。",
|
||||
"message": "出于安全和合规考虑,$ORGANIZATION$ 要求所有项目归组织所有。点击「接受」以转移您的项目的所有权。",
|
||||
"placeholders": {
|
||||
"organization": {
|
||||
"content": "$1",
|
||||
@@ -6032,7 +6032,7 @@
|
||||
}
|
||||
},
|
||||
"acceptTransfer": {
|
||||
"message": "接受传输"
|
||||
"message": "接受转移"
|
||||
},
|
||||
"declineAndLeave": {
|
||||
"message": "拒绝并退出"
|
||||
|
||||
@@ -709,7 +709,7 @@
|
||||
"message": "添加附件"
|
||||
},
|
||||
"itemsTransferred": {
|
||||
"message": "项目已传输"
|
||||
"message": "项目已转移"
|
||||
},
|
||||
"fixEncryption": {
|
||||
"message": "修复加密"
|
||||
@@ -4454,7 +4454,7 @@
|
||||
"message": "我该如何管理我的密码库?"
|
||||
},
|
||||
"transferItemsToOrganizationTitle": {
|
||||
"message": "传输项目到 $ORGANIZATION$",
|
||||
"message": "转移项目到 $ORGANIZATION$",
|
||||
"placeholders": {
|
||||
"organization": {
|
||||
"content": "$1",
|
||||
@@ -4463,7 +4463,7 @@
|
||||
}
|
||||
},
|
||||
"transferItemsToOrganizationContent": {
|
||||
"message": "出于安全和合规考虑,$ORGANIZATION$ 要求所有项目归组织所有。点击「接受」以传输您的项目的所有权。",
|
||||
"message": "出于安全和合规考虑,$ORGANIZATION$ 要求所有项目归组织所有。点击「接受」以转移您的项目的所有权。",
|
||||
"placeholders": {
|
||||
"organization": {
|
||||
"content": "$1",
|
||||
@@ -4472,7 +4472,7 @@
|
||||
}
|
||||
},
|
||||
"acceptTransfer": {
|
||||
"message": "接受传输"
|
||||
"message": "接受转移"
|
||||
},
|
||||
"declineAndLeave": {
|
||||
"message": "拒绝并退出"
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</bit-toggle>
|
||||
</ng-container>
|
||||
</bit-toggle-group>
|
||||
<bit-table-scroll [dataSource]="dataSource" [rowSize]="53">
|
||||
<bit-table-scroll [dataSource]="dataSource" [rowSize]="75">
|
||||
<ng-container header>
|
||||
<th bitCell></th>
|
||||
<th bitCell bitSortable="name">{{ "name" | i18n }}</th>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</bit-toggle>
|
||||
</ng-container>
|
||||
</bit-toggle-group>
|
||||
<bit-table-scroll [dataSource]="dataSource" [rowSize]="53">
|
||||
<bit-table-scroll [dataSource]="dataSource" [rowSize]="75">
|
||||
<ng-container header>
|
||||
<th bitCell></th>
|
||||
<th bitCell bitSortable="name">{{ "name" | i18n }}</th>
|
||||
|
||||
@@ -82,7 +82,7 @@ class MockAccountService implements Partial<AccountService> {
|
||||
name: "Test User 1",
|
||||
email: "test@email.com",
|
||||
emailVerified: true,
|
||||
creationDate: "2024-01-01T00:00:00.000Z",
|
||||
creationDate: new Date("2024-01-01T00:00:00.000Z"),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ class MockAccountService implements Partial<AccountService> {
|
||||
name: "Test User 1",
|
||||
email: "test@email.com",
|
||||
emailVerified: true,
|
||||
creationDate: "2024-01-01T00:00:00.000Z",
|
||||
creationDate: new Date("2024-01-01T00:00:00.000Z"),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5871,7 +5871,7 @@
|
||||
"description": "This is the policy description shown in the policy list."
|
||||
},
|
||||
"organizationDataOwnershipDescContent": {
|
||||
"message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ",
|
||||
"message": "Bütün elementlər bir təşkilata məxsus olacaq və orada saxlanılacaq, bu da təşkilat üzrə kontrollar, görünürlük və hesabatları mümkün edəcək. İşə salındığı zaman, hər üzv üçün elementləri saxlaya biləcəyi ilkin bir kolleksiya mövcud olacaq. Daha ətraflı ",
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'"
|
||||
},
|
||||
"organizationDataOwnershipContentAnchor": {
|
||||
@@ -6752,7 +6752,7 @@
|
||||
"message": "Bütün üzvlər üçün maksimum bitmə vaxtını \"Heç vaxt\" olaraq icazə vermək istədiyinizə əminsiniz?"
|
||||
},
|
||||
"sessionTimeoutConfirmationNeverDescription": {
|
||||
"message": "This option will save your members' encryption keys on their devices. If you choose this option, ensure that their devices are adequately protected."
|
||||
"message": "Bu seçim, üzvlərinizin şifrələmə açarlarını onların cihazlarında saxlayacaq. Bu seçimi seçsəniz, onların cihazlarının lazımi səviyyədə qorunduğuna əmin olun."
|
||||
},
|
||||
"learnMoreAboutDeviceProtection": {
|
||||
"message": "Cihaz mühafizəsi barədə daha ətraflı"
|
||||
|
||||
@@ -12436,7 +12436,7 @@
|
||||
"message": "Du hast Bitwarden Premium"
|
||||
},
|
||||
"viewAndManagePremiumSubscription": {
|
||||
"message": "View and manage your Premium subscription"
|
||||
"message": "Dein Premium-Abonnement anzeigen und verwalten"
|
||||
},
|
||||
"youNeedToUpdateLicenseFile": {
|
||||
"message": "Du musst deine Lizenzdatei aktualisieren"
|
||||
@@ -12472,7 +12472,7 @@
|
||||
"message": "Du hast bereits ein Abonnement?"
|
||||
},
|
||||
"alreadyHaveSubscriptionSelfHostedMessage": {
|
||||
"message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below."
|
||||
"message": "Öffne die Abonnementseite in deinem Bitwarden Cloud-Konto und lade deine Lizenzdatei herunter. Gehe dann zu dieser Seite zurück und lade sie unten hoch."
|
||||
},
|
||||
"viewAllPlans": {
|
||||
"message": "Alle Tarife anzeigen"
|
||||
|
||||
@@ -1970,11 +1970,11 @@
|
||||
"message": "Le chiavi di cifratura dell'account sono uniche per ogni account utente Bitwarden, quindi non è possibile importare un'esportazione cifrata in un account diverso."
|
||||
},
|
||||
"exportNoun": {
|
||||
"message": "Export",
|
||||
"message": "Esporta",
|
||||
"description": "The noun form of the word Export"
|
||||
},
|
||||
"exportVerb": {
|
||||
"message": "Export",
|
||||
"message": "Esporta",
|
||||
"description": "The verb form of the word Export"
|
||||
},
|
||||
"exportFrom": {
|
||||
@@ -2303,11 +2303,11 @@
|
||||
"message": "Strumenti"
|
||||
},
|
||||
"importNoun": {
|
||||
"message": "Import",
|
||||
"message": "Importa",
|
||||
"description": "The noun form of the word Import"
|
||||
},
|
||||
"importVerb": {
|
||||
"message": "Import",
|
||||
"message": "Importa",
|
||||
"description": "The verb form of the word Import"
|
||||
},
|
||||
"importData": {
|
||||
@@ -3294,7 +3294,7 @@
|
||||
"message": "Avvia abbonamento cloud"
|
||||
},
|
||||
"launchCloudSubscriptionSentenceCase": {
|
||||
"message": "Launch cloud subscription"
|
||||
"message": "Avvia abbonamento cloud"
|
||||
},
|
||||
"storage": {
|
||||
"message": "Spazio di archiviazione"
|
||||
@@ -4212,10 +4212,10 @@
|
||||
}
|
||||
},
|
||||
"userAcceptedTransfer": {
|
||||
"message": "Accepted transfer to organization ownership."
|
||||
"message": "Trasferimento di proprietà all'organizzazione accettato."
|
||||
},
|
||||
"userDeclinedTransfer": {
|
||||
"message": "Revoked for declining transfer to organization ownership."
|
||||
"message": "Revocato per il rifiuto di trasferimento di proprietà all'organizzazione."
|
||||
},
|
||||
"invitedUserId": {
|
||||
"message": "Utente $ID$ invitato.",
|
||||
@@ -11607,7 +11607,7 @@
|
||||
"message": "Togli dall'archivio"
|
||||
},
|
||||
"unArchiveAndSave": {
|
||||
"message": "Unarchive and save"
|
||||
"message": "Togli dall'archivio e salva"
|
||||
},
|
||||
"itemsInArchive": {
|
||||
"message": "Elementi archiviati"
|
||||
@@ -12251,43 +12251,43 @@
|
||||
}
|
||||
},
|
||||
"removeMasterPasswordForOrgUserKeyConnector": {
|
||||
"message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain."
|
||||
"message": "La tua organizzazione non utilizza più le password principali per accedere a Bitwarden. Per continuare, verifica l'organizzazione e il dominio."
|
||||
},
|
||||
"continueWithLogIn": {
|
||||
"message": "Continue with log in"
|
||||
"message": "Accedi e continua"
|
||||
},
|
||||
"doNotContinue": {
|
||||
"message": "Do not continue"
|
||||
"message": "Non continuare"
|
||||
},
|
||||
"domain": {
|
||||
"message": "Domain"
|
||||
"message": "Dominio"
|
||||
},
|
||||
"keyConnectorDomainTooltip": {
|
||||
"message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin."
|
||||
"message": "Questo dominio memorizzerà le chiavi di crittografia del tuo account, quindi assicurati di impostarlo come affidabile. Se non hai la certezza che lo sia, verifica con l'amministratore."
|
||||
},
|
||||
"verifyYourOrganization": {
|
||||
"message": "Verify your organization to log in"
|
||||
"message": "Verifica la tua organizzazione per accedere"
|
||||
},
|
||||
"organizationVerified": {
|
||||
"message": "Organization verified"
|
||||
"message": "Organizzazione verificata"
|
||||
},
|
||||
"domainVerified": {
|
||||
"message": "Domain verified"
|
||||
"message": "Dominio verificato"
|
||||
},
|
||||
"leaveOrganizationContent": {
|
||||
"message": "If you don't verify your organization, your access to the organization will be revoked."
|
||||
"message": "Se non verifichi l'organizzazione, il tuo accesso sarà revocato."
|
||||
},
|
||||
"leaveNow": {
|
||||
"message": "Leave now"
|
||||
"message": "Abbandona"
|
||||
},
|
||||
"verifyYourDomainToLogin": {
|
||||
"message": "Verify your domain to log in"
|
||||
"message": "Verifica il tuo dominio per accedere"
|
||||
},
|
||||
"verifyYourDomainDescription": {
|
||||
"message": "To continue with log in, verify this domain."
|
||||
"message": "Per continuare con l'accesso, verifica questo dominio."
|
||||
},
|
||||
"confirmKeyConnectorOrganizationUserDescription": {
|
||||
"message": "To continue with log in, verify the organization and domain."
|
||||
"message": "Per continuare con l'accesso, verifica l'organizzazione e il dominio."
|
||||
},
|
||||
"confirmNoSelectedCriticalApplicationsTitle": {
|
||||
"message": "Non ci sono applicazioni contrassegnate come critiche"
|
||||
@@ -12433,13 +12433,13 @@
|
||||
"message": "Perché vedo questo avviso?"
|
||||
},
|
||||
"youHaveBitwardenPremium": {
|
||||
"message": "You have Bitwarden Premium"
|
||||
"message": "Hai Bitwarden Premium"
|
||||
},
|
||||
"viewAndManagePremiumSubscription": {
|
||||
"message": "View and manage your Premium subscription"
|
||||
"message": "Visualizza e gestisci il tuo abbonamento Premium"
|
||||
},
|
||||
"youNeedToUpdateLicenseFile": {
|
||||
"message": "You'll need to update your license file"
|
||||
"message": "Dovrai aggiornare il tuo file di licenza"
|
||||
},
|
||||
"youNeedToUpdateLicenseFileDate": {
|
||||
"message": "$DATE$.",
|
||||
@@ -12451,16 +12451,16 @@
|
||||
}
|
||||
},
|
||||
"uploadLicenseFile": {
|
||||
"message": "Upload license file"
|
||||
"message": "Carica il file di licenza"
|
||||
},
|
||||
"uploadYourLicenseFile": {
|
||||
"message": "Upload your license file"
|
||||
"message": "Carica il file di licenza"
|
||||
},
|
||||
"uploadYourPremiumLicenseFile": {
|
||||
"message": "Upload your Premium license file"
|
||||
"message": "Carica il tuo file di licenza Premium"
|
||||
},
|
||||
"uploadLicenseFileDesc": {
|
||||
"message": "Your license file name will be similar to: $FILE_NAME$",
|
||||
"message": "Il nome del file di licenza sarà simile a $FILE_NAME$",
|
||||
"placeholders": {
|
||||
"file_name": {
|
||||
"content": "$1",
|
||||
@@ -12469,15 +12469,15 @@
|
||||
}
|
||||
},
|
||||
"alreadyHaveSubscriptionQuestion": {
|
||||
"message": "Already have a subscription?"
|
||||
"message": "Hai già un abbonamento?"
|
||||
},
|
||||
"alreadyHaveSubscriptionSelfHostedMessage": {
|
||||
"message": "Open the subscription page on your Bitwarden cloud account and download your license file. Then return to this screen and upload it below."
|
||||
"message": "Vai alla pagina degli abbonamenti del tuo account Bitwarden e scarica il file di licenza, poi torna a caricarlo qui."
|
||||
},
|
||||
"viewAllPlans": {
|
||||
"message": "View all plans"
|
||||
"message": "Visualizza tutti i piani"
|
||||
},
|
||||
"planDescPremium": {
|
||||
"message": "Complete online security"
|
||||
"message": "Sicurezza online completa"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1943,7 +1943,7 @@
|
||||
"message": "Copiar UUID"
|
||||
},
|
||||
"errorRefreshingAccessToken": {
|
||||
"message": "Erro de recarregamento do token de acesso"
|
||||
"message": "Erro de Recarregamento do Token de Acesso"
|
||||
},
|
||||
"errorRefreshingAccessTokenDesc": {
|
||||
"message": "Nenhum token de atualização ou chave de API foi encontrado. Tente se desconectar e se conectar novamente."
|
||||
@@ -3294,7 +3294,7 @@
|
||||
"message": "Iniciar Assinatura na Nuvem"
|
||||
},
|
||||
"launchCloudSubscriptionSentenceCase": {
|
||||
"message": "Launch cloud subscription"
|
||||
"message": "Executar assinatura na nuvem"
|
||||
},
|
||||
"storage": {
|
||||
"message": "Armazenamento"
|
||||
|
||||
@@ -3862,7 +3862,7 @@
|
||||
"description": "Browser extension/addon"
|
||||
},
|
||||
"desktop": {
|
||||
"message": "桌面版应用",
|
||||
"message": "桌面端",
|
||||
"description": "Desktop app"
|
||||
},
|
||||
"webVault": {
|
||||
@@ -4212,10 +4212,10 @@
|
||||
}
|
||||
},
|
||||
"userAcceptedTransfer": {
|
||||
"message": "Accepted transfer to organization ownership."
|
||||
"message": "接受了转移至组织所有权。"
|
||||
},
|
||||
"userDeclinedTransfer": {
|
||||
"message": "Revoked for declining transfer to organization ownership."
|
||||
"message": "因拒绝转移至组织所有权而被撤销。"
|
||||
},
|
||||
"invitedUserId": {
|
||||
"message": "邀请了用户 $ID$。",
|
||||
@@ -5195,7 +5195,7 @@
|
||||
"message": "需要先修复您的密码库中的旧文件附件,然后才能轮换您账户的加密密钥。"
|
||||
},
|
||||
"itemsTransferred": {
|
||||
"message": "项目已传输"
|
||||
"message": "项目已转移"
|
||||
},
|
||||
"yourAccountsFingerprint": {
|
||||
"message": "您的账户指纹短语",
|
||||
@@ -6825,7 +6825,7 @@
|
||||
"message": "密码库超时不在允许的范围内。"
|
||||
},
|
||||
"disableExport": {
|
||||
"message": "移除导出"
|
||||
"message": "禁用导出"
|
||||
},
|
||||
"disablePersonalVaultExportDescription": {
|
||||
"message": "不允许成员从个人密码库导出数据。"
|
||||
@@ -12406,7 +12406,7 @@
|
||||
"message": "我该如何管理我的密码库?"
|
||||
},
|
||||
"transferItemsToOrganizationTitle": {
|
||||
"message": "传输项目到 $ORGANIZATION$",
|
||||
"message": "转移项目到 $ORGANIZATION$",
|
||||
"placeholders": {
|
||||
"organization": {
|
||||
"content": "$1",
|
||||
@@ -12415,7 +12415,7 @@
|
||||
}
|
||||
},
|
||||
"transferItemsToOrganizationContent": {
|
||||
"message": "出于安全和合规考虑,$ORGANIZATION$ 要求所有项目归组织所有。点击「接受」以传输您的项目的所有权。",
|
||||
"message": "出于安全和合规考虑,$ORGANIZATION$ 要求所有项目归组织所有。点击「接受」以转移您的项目的所有权。",
|
||||
"placeholders": {
|
||||
"organization": {
|
||||
"content": "$1",
|
||||
@@ -12424,7 +12424,7 @@
|
||||
}
|
||||
},
|
||||
"acceptTransfer": {
|
||||
"message": "接受传输"
|
||||
"message": "接受转移"
|
||||
},
|
||||
"declineAndLeave": {
|
||||
"message": "拒绝并退出"
|
||||
@@ -12439,7 +12439,7 @@
|
||||
"message": "查看和管理您的高级版订阅"
|
||||
},
|
||||
"youNeedToUpdateLicenseFile": {
|
||||
"message": "您需要更新您的许可文件"
|
||||
"message": "您需要更新您的许可证文件"
|
||||
},
|
||||
"youNeedToUpdateLicenseFileDate": {
|
||||
"message": "$DATE$。",
|
||||
@@ -12469,13 +12469,13 @@
|
||||
}
|
||||
},
|
||||
"alreadyHaveSubscriptionQuestion": {
|
||||
"message": "已经有一个订阅?"
|
||||
"message": "已经有一个订阅了吗?"
|
||||
},
|
||||
"alreadyHaveSubscriptionSelfHostedMessage": {
|
||||
"message": "打开您的 Bitwarden 云账户上的订阅页面并下载您的许可证文件,然后返回此屏幕并上传。"
|
||||
"message": "打开您的 Bitwarden 云账户中的订阅页面并下载您的许可证文件。然后返回此界面并在下方上传该文件。"
|
||||
},
|
||||
"viewAllPlans": {
|
||||
"message": "查看所有套餐"
|
||||
"message": "查看所有方案"
|
||||
},
|
||||
"planDescPremium": {
|
||||
"message": "全面的在线安全防护"
|
||||
|
||||
@@ -107,7 +107,7 @@ describe("LoginDecryptionOptionsComponent", () => {
|
||||
email: mockEmail,
|
||||
name: "Test User",
|
||||
emailVerified: true,
|
||||
creationDate: new Date().toISOString(),
|
||||
creationDate: new Date(),
|
||||
});
|
||||
platformUtilsService.getClientType.mockReturnValue(ClientType.Browser);
|
||||
deviceTrustService.getShouldTrustDevice.mockResolvedValue(true);
|
||||
|
||||
@@ -15,7 +15,7 @@ export function mockAccountInfoWith(info: Partial<AccountInfo> = {}): AccountInf
|
||||
name: "name",
|
||||
email: "email",
|
||||
emailVerified: true,
|
||||
creationDate: "2024-01-01T00:00:00.000Z",
|
||||
creationDate: new Date("2024-01-01T00:00:00.000Z"),
|
||||
...info,
|
||||
};
|
||||
}
|
||||
@@ -111,7 +111,7 @@ export class FakeAccountService implements AccountService {
|
||||
await this.mock.setAccountEmailVerified(userId, emailVerified);
|
||||
}
|
||||
|
||||
async setAccountCreationDate(userId: UserId, creationDate: string): Promise<void> {
|
||||
async setAccountCreationDate(userId: UserId, creationDate: Date): Promise<void> {
|
||||
await this.mock.setAccountCreationDate(userId, creationDate);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,33 +2,25 @@ import { Observable } from "rxjs";
|
||||
|
||||
import { UserId } from "../../types/guid";
|
||||
|
||||
/**
|
||||
* Holds state that represents a user's account with Bitwarden.
|
||||
* Any additions here should be added to the equality check in the AccountService
|
||||
* to ensure that emissions are done on every change.
|
||||
*
|
||||
* @property email - User's email address.
|
||||
* @property emailVerified - Whether the email has been verified.
|
||||
* @property name - User's display name (optional).
|
||||
* @property creationDate - Date when the account was created.
|
||||
* Will be undefined immediately after login until the first sync completes.
|
||||
*/
|
||||
export type AccountInfo = {
|
||||
email: string;
|
||||
emailVerified: boolean;
|
||||
name: string | undefined;
|
||||
creationDate: string | undefined;
|
||||
creationDate: Date | undefined;
|
||||
};
|
||||
|
||||
export type Account = { id: UserId } & AccountInfo;
|
||||
|
||||
export function accountInfoEqual(a: AccountInfo, b: AccountInfo) {
|
||||
if (a == null && b == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a == null || b == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const keys = new Set([...Object.keys(a), ...Object.keys(b)]) as Set<keyof AccountInfo>;
|
||||
for (const key of keys) {
|
||||
if (a[key] !== b[key]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export abstract class AccountService {
|
||||
abstract accounts$: Observable<Record<UserId, AccountInfo>>;
|
||||
|
||||
@@ -77,7 +69,7 @@ export abstract class AccountService {
|
||||
* @param userId
|
||||
* @param creationDate
|
||||
*/
|
||||
abstract setAccountCreationDate(userId: UserId, creationDate: string): Promise<void>;
|
||||
abstract setAccountCreationDate(userId: UserId, creationDate: Date): Promise<void>;
|
||||
/**
|
||||
* updates the `accounts$` observable with the new VerifyNewDeviceLogin property for the account.
|
||||
* @param userId
|
||||
|
||||
@@ -17,7 +17,7 @@ import { LogService } from "../../platform/abstractions/log.service";
|
||||
import { MessagingService } from "../../platform/abstractions/messaging.service";
|
||||
import { Utils } from "../../platform/misc/utils";
|
||||
import { UserId } from "../../types/guid";
|
||||
import { AccountInfo, accountInfoEqual } from "../abstractions/account.service";
|
||||
import { AccountInfo } from "../abstractions/account.service";
|
||||
|
||||
import {
|
||||
ACCOUNT_ACCOUNTS,
|
||||
@@ -27,63 +27,6 @@ import {
|
||||
AccountServiceImplementation,
|
||||
} from "./account.service";
|
||||
|
||||
describe("accountInfoEqual", () => {
|
||||
const accountInfo = mockAccountInfoWith();
|
||||
|
||||
it("compares nulls", () => {
|
||||
expect(accountInfoEqual(null, null)).toBe(true);
|
||||
expect(accountInfoEqual(null, accountInfo)).toBe(false);
|
||||
expect(accountInfoEqual(accountInfo, null)).toBe(false);
|
||||
});
|
||||
|
||||
it("compares all keys, not just those defined in AccountInfo", () => {
|
||||
const different = { ...accountInfo, extra: "extra" };
|
||||
|
||||
expect(accountInfoEqual(accountInfo, different)).toBe(false);
|
||||
});
|
||||
|
||||
it("compares name", () => {
|
||||
const same = { ...accountInfo };
|
||||
const different = { ...accountInfo, name: "name2" };
|
||||
|
||||
expect(accountInfoEqual(accountInfo, same)).toBe(true);
|
||||
expect(accountInfoEqual(accountInfo, different)).toBe(false);
|
||||
});
|
||||
|
||||
it("compares email", () => {
|
||||
const same = { ...accountInfo };
|
||||
const different = { ...accountInfo, email: "email2" };
|
||||
|
||||
expect(accountInfoEqual(accountInfo, same)).toBe(true);
|
||||
expect(accountInfoEqual(accountInfo, different)).toBe(false);
|
||||
});
|
||||
|
||||
it("compares emailVerified", () => {
|
||||
const same = { ...accountInfo };
|
||||
const different = { ...accountInfo, emailVerified: false };
|
||||
|
||||
expect(accountInfoEqual(accountInfo, same)).toBe(true);
|
||||
expect(accountInfoEqual(accountInfo, different)).toBe(false);
|
||||
});
|
||||
|
||||
it("compares creationDate", () => {
|
||||
const same = { ...accountInfo };
|
||||
const different = { ...accountInfo, creationDate: "2024-12-31T00:00:00.000Z" };
|
||||
|
||||
expect(accountInfoEqual(accountInfo, same)).toBe(true);
|
||||
expect(accountInfoEqual(accountInfo, different)).toBe(false);
|
||||
});
|
||||
|
||||
it("compares undefined creationDate", () => {
|
||||
const accountWithoutCreationDate = mockAccountInfoWith({ creationDate: undefined });
|
||||
const same = { ...accountWithoutCreationDate };
|
||||
const different = { ...accountWithoutCreationDate, creationDate: "2024-01-01T00:00:00.000Z" };
|
||||
|
||||
expect(accountInfoEqual(accountWithoutCreationDate, same)).toBe(true);
|
||||
expect(accountInfoEqual(accountWithoutCreationDate, different)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("accountService", () => {
|
||||
let messagingService: MockProxy<MessagingService>;
|
||||
let logService: MockProxy<LogService>;
|
||||
@@ -121,6 +64,60 @@ describe("accountService", () => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe("accountInfoEqual", () => {
|
||||
const accountInfo = mockAccountInfoWith();
|
||||
|
||||
it("compares nulls", () => {
|
||||
expect((sut as any).accountInfoEqual(null, null)).toBe(true);
|
||||
expect((sut as any).accountInfoEqual(null, accountInfo)).toBe(false);
|
||||
expect((sut as any).accountInfoEqual(accountInfo, null)).toBe(false);
|
||||
});
|
||||
|
||||
it("compares name", () => {
|
||||
const same = { ...accountInfo };
|
||||
const different = { ...accountInfo, name: "name2" };
|
||||
|
||||
expect((sut as any).accountInfoEqual(accountInfo, same)).toBe(true);
|
||||
expect((sut as any).accountInfoEqual(accountInfo, different)).toBe(false);
|
||||
});
|
||||
|
||||
it("compares email", () => {
|
||||
const same = { ...accountInfo };
|
||||
const different = { ...accountInfo, email: "email2" };
|
||||
|
||||
expect((sut as any).accountInfoEqual(accountInfo, same)).toBe(true);
|
||||
expect((sut as any).accountInfoEqual(accountInfo, different)).toBe(false);
|
||||
});
|
||||
|
||||
it("compares emailVerified", () => {
|
||||
const same = { ...accountInfo };
|
||||
const different = { ...accountInfo, emailVerified: false };
|
||||
|
||||
expect((sut as any).accountInfoEqual(accountInfo, same)).toBe(true);
|
||||
expect((sut as any).accountInfoEqual(accountInfo, different)).toBe(false);
|
||||
});
|
||||
|
||||
it("compares creationDate", () => {
|
||||
const same = { ...accountInfo };
|
||||
const different = { ...accountInfo, creationDate: new Date("2024-12-31T00:00:00.000Z") };
|
||||
|
||||
expect((sut as any).accountInfoEqual(accountInfo, same)).toBe(true);
|
||||
expect((sut as any).accountInfoEqual(accountInfo, different)).toBe(false);
|
||||
});
|
||||
|
||||
it("compares undefined creationDate", () => {
|
||||
const accountWithoutCreationDate = mockAccountInfoWith({ creationDate: undefined });
|
||||
const same = { ...accountWithoutCreationDate };
|
||||
const different = {
|
||||
...accountWithoutCreationDate,
|
||||
creationDate: new Date("2024-01-01T00:00:00.000Z"),
|
||||
};
|
||||
|
||||
expect((sut as any).accountInfoEqual(accountWithoutCreationDate, same)).toBe(true);
|
||||
expect((sut as any).accountInfoEqual(accountWithoutCreationDate, different)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("activeAccount$", () => {
|
||||
it("should emit null if no account is active", () => {
|
||||
const emissions = trackEmissions(sut.activeAccount$);
|
||||
@@ -281,7 +278,7 @@ describe("accountService", () => {
|
||||
});
|
||||
|
||||
it("should update the account with a new creation date", async () => {
|
||||
const newCreationDate = "2024-12-31T00:00:00.000Z";
|
||||
const newCreationDate = new Date("2024-12-31T00:00:00.000Z");
|
||||
await sut.setAccountCreationDate(userId, newCreationDate);
|
||||
const currentState = await firstValueFrom(accountsState.state$);
|
||||
|
||||
@@ -297,6 +294,24 @@ describe("accountService", () => {
|
||||
expect(currentState).toEqual(initialState);
|
||||
});
|
||||
|
||||
it("should not update if the creation date has the same timestamp but different Date object", async () => {
|
||||
const sameTimestamp = new Date(userInfo.creationDate.getTime());
|
||||
await sut.setAccountCreationDate(userId, sameTimestamp);
|
||||
const currentState = await firstValueFrom(accountsState.state$);
|
||||
|
||||
expect(currentState).toEqual(initialState);
|
||||
});
|
||||
|
||||
it("should update if the creation date has a different timestamp", async () => {
|
||||
const differentDate = new Date(userInfo.creationDate.getTime() + 1000);
|
||||
await sut.setAccountCreationDate(userId, differentDate);
|
||||
const currentState = await firstValueFrom(accountsState.state$);
|
||||
|
||||
expect(currentState).toEqual({
|
||||
[userId]: { ...userInfo, creationDate: differentDate },
|
||||
});
|
||||
});
|
||||
|
||||
it("should update from undefined to a defined creation date", async () => {
|
||||
const accountWithoutCreationDate = mockAccountInfoWith({
|
||||
...userInfo,
|
||||
@@ -304,7 +319,7 @@ describe("accountService", () => {
|
||||
});
|
||||
accountsState.stateSubject.next({ [userId]: accountWithoutCreationDate });
|
||||
|
||||
const newCreationDate = "2024-06-15T12:30:00.000Z";
|
||||
const newCreationDate = new Date("2024-06-15T12:30:00.000Z");
|
||||
await sut.setAccountCreationDate(userId, newCreationDate);
|
||||
const currentState = await firstValueFrom(accountsState.state$);
|
||||
|
||||
@@ -313,14 +328,19 @@ describe("accountService", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should update to a different creation date string format", async () => {
|
||||
const newCreationDate = "2023-03-15T08:45:30.123Z";
|
||||
await sut.setAccountCreationDate(userId, newCreationDate);
|
||||
const currentState = await firstValueFrom(accountsState.state$);
|
||||
|
||||
expect(currentState).toEqual({
|
||||
[userId]: { ...userInfo, creationDate: newCreationDate },
|
||||
it("should not update when both creation dates are undefined", async () => {
|
||||
const accountWithoutCreationDate = mockAccountInfoWith({
|
||||
...userInfo,
|
||||
creationDate: undefined,
|
||||
});
|
||||
accountsState.stateSubject.next({ [userId]: accountWithoutCreationDate });
|
||||
|
||||
// Attempt to set to undefined (shouldn't trigger update)
|
||||
const currentStateBefore = await firstValueFrom(accountsState.state$);
|
||||
|
||||
// We can't directly call setAccountCreationDate with undefined, but we can verify
|
||||
// the behavior through setAccountInfo which accountInfoEqual uses internally
|
||||
expect(currentStateBefore[userId].creationDate).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
Account,
|
||||
AccountInfo,
|
||||
InternalAccountService,
|
||||
accountInfoEqual,
|
||||
} from "../../auth/abstractions/account.service";
|
||||
import { LogService } from "../../platform/abstractions/log.service";
|
||||
import { MessagingService } from "../../platform/abstractions/messaging.service";
|
||||
@@ -37,7 +36,10 @@ export const ACCOUNT_ACCOUNTS = KeyDefinition.record<AccountInfo, UserId>(
|
||||
ACCOUNT_DISK,
|
||||
"accounts",
|
||||
{
|
||||
deserializer: (accountInfo) => accountInfo,
|
||||
deserializer: (accountInfo) => ({
|
||||
...accountInfo,
|
||||
creationDate: accountInfo.creationDate ? new Date(accountInfo.creationDate) : undefined,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -111,7 +113,7 @@ export class AccountServiceImplementation implements InternalAccountService {
|
||||
this.activeAccount$ = this.activeAccountIdState.state$.pipe(
|
||||
combineLatestWith(this.accounts$),
|
||||
map(([id, accounts]) => (id ? ({ id, ...(accounts[id] as AccountInfo) } as Account) : null)),
|
||||
distinctUntilChanged((a, b) => a?.id === b?.id && accountInfoEqual(a, b)),
|
||||
distinctUntilChanged((a, b) => a?.id === b?.id && this.accountInfoEqual(a, b)),
|
||||
shareReplay({ bufferSize: 1, refCount: false }),
|
||||
);
|
||||
this.accountActivity$ = this.globalStateProvider
|
||||
@@ -168,7 +170,7 @@ export class AccountServiceImplementation implements InternalAccountService {
|
||||
await this.setAccountInfo(userId, { emailVerified });
|
||||
}
|
||||
|
||||
async setAccountCreationDate(userId: UserId, creationDate: string): Promise<void> {
|
||||
async setAccountCreationDate(userId: UserId, creationDate: Date): Promise<void> {
|
||||
await this.setAccountInfo(userId, { creationDate });
|
||||
}
|
||||
|
||||
@@ -274,6 +276,23 @@ export class AccountServiceImplementation implements InternalAccountService {
|
||||
this._showHeader$.next(visible);
|
||||
}
|
||||
|
||||
private accountInfoEqual(a: AccountInfo, b: AccountInfo) {
|
||||
if (a == null && b == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a == null || b == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
a.email === b.email &&
|
||||
a.emailVerified === b.emailVerified &&
|
||||
a.name === b.name &&
|
||||
a.creationDate?.getTime() === b.creationDate?.getTime()
|
||||
);
|
||||
}
|
||||
|
||||
private async setAccountInfo(userId: UserId, update: Partial<AccountInfo>): Promise<void> {
|
||||
function newAccountInfo(oldAccountInfo: AccountInfo): AccountInfo {
|
||||
return { ...oldAccountInfo, ...update };
|
||||
@@ -291,7 +310,7 @@ export class AccountServiceImplementation implements InternalAccountService {
|
||||
throw new Error("Account does not exist");
|
||||
}
|
||||
|
||||
return !accountInfoEqual(accounts[userId], newAccountInfo(accounts[userId]));
|
||||
return !this.accountInfoEqual(accounts[userId], newAccountInfo(accounts[userId]));
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -279,8 +279,8 @@ export class DefaultSyncService extends CoreSyncService {
|
||||
await this.avatarService.setSyncAvatarColor(response.id, response.avatarColor);
|
||||
await this.tokenService.setSecurityStamp(response.securityStamp, response.id);
|
||||
await this.accountService.setAccountEmailVerified(response.id, response.emailVerified);
|
||||
await this.accountService.setAccountCreationDate(response.id, new Date(response.creationDate));
|
||||
await this.accountService.setAccountVerifyNewDeviceLogin(response.id, response.verifyDevices);
|
||||
await this.accountService.setAccountCreationDate(response.id, response.creationDate);
|
||||
|
||||
await this.billingAccountProfileStateService.setHasPremium(
|
||||
response.premiumPersonally,
|
||||
|
||||
@@ -2,127 +2,772 @@ import { Meta } from "@storybook/addon-docs/blocks";
|
||||
|
||||
<Meta title="Documentation/Colors" />
|
||||
|
||||
export const Row = (name) => (
|
||||
<tr class="tw-h-16">
|
||||
<td class="!tw-border-none">{name}</td>
|
||||
<td class={"tw-bg-" + name + " !tw-border-secondary-300"}></td>
|
||||
</tr>
|
||||
);
|
||||
# Color System
|
||||
|
||||
export const Table = (args) => (
|
||||
<table class={"border tw-table-auto !tw-text-main " + args.class}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="tw-w-40">General usage</th>
|
||||
<th class="tw-w-20"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Row("background")}
|
||||
{Row("background-alt")}
|
||||
{Row("background-alt2")}
|
||||
{Row("background-alt3")}
|
||||
{Row("background-alt4")}
|
||||
</tbody>
|
||||
<tbody>
|
||||
{Row("primary-100")}
|
||||
{Row("primary-300")}
|
||||
{Row("primary-600")}
|
||||
{Row("primary-700")}
|
||||
</tbody>
|
||||
<tbody>
|
||||
{Row("secondary-100")}
|
||||
{Row("secondary-300")}
|
||||
{Row("secondary-500")}
|
||||
{Row("secondary-600")}
|
||||
{Row("secondary-700")}
|
||||
</tbody>
|
||||
<tbody>
|
||||
{Row("success-100")}
|
||||
{Row("success-600")}
|
||||
{Row("success-700")}
|
||||
</tbody>
|
||||
<tbody>
|
||||
{Row("danger-100")}
|
||||
{Row("danger-600")}
|
||||
{Row("danger-700")}
|
||||
</tbody>
|
||||
<tbody>
|
||||
{Row("warning-100")}
|
||||
{Row("warning-600")}
|
||||
{Row("warning-700")}
|
||||
</tbody>
|
||||
<tbody>
|
||||
{Row("info-100")}
|
||||
{Row("info-600")}
|
||||
{Row("info-700")}
|
||||
</tbody>
|
||||
<tbody>
|
||||
{Row("notification-100")}
|
||||
{Row("notification-600")}
|
||||
</tbody>
|
||||
<tbody>
|
||||
{Row("illustration-outline")}
|
||||
{Row("illustration-bg-primary")}
|
||||
{Row("illustration-bg-secondary")}
|
||||
{Row("illustration-bg-tertiary")}
|
||||
{Row("illustration-tertiary")}
|
||||
{Row("illustration-logo")}
|
||||
</tbody>
|
||||
Bitwarden uses a three-tier color token architecture:
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Text</th>
|
||||
<th class="tw-w-20"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Row("text-main")}
|
||||
{Row("text-muted")}
|
||||
{Row("text-contrast")}
|
||||
{Row("text-alt2")}
|
||||
{Row("text-code")}
|
||||
</tbody>
|
||||
- **Primitive Colors** - Raw color values from the Figma design system
|
||||
- **Semantic Tokens** - Meaningful names that reference primitives
|
||||
- **Tailwind Utilities** - CSS classes for components
|
||||
|
||||
</table>
|
||||
);
|
||||
## Color Token Structure
|
||||
|
||||
<style>
|
||||
{`
|
||||
table {
|
||||
border-spacing: 0.5rem;
|
||||
border-collapse: separate !important;
|
||||
}
|
||||
### Primitive Colors (Hex format)
|
||||
|
||||
tr {
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
Location: `libs/components/src/tw-theme.css`
|
||||
|
||||
td, th {
|
||||
color: inherit !important;
|
||||
}
|
||||
- 10 color families: `brand`, `gray`, `red`, `orange`, `yellow`, `green`, `pink`, `coral`, `teal`,
|
||||
`purple`
|
||||
- 11 shades each: `050`, `100`, `200`, `300`, `400`, `500`, `600`, `700`, `800`, `900`, `950`
|
||||
- Format: `--color-{family}-{shade}` (e.g., `--color-brand-600`)
|
||||
|
||||
th {
|
||||
border: none !important;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
### Semantic Foreground Tokens
|
||||
|
||||
# Colors
|
||||
- **Neutral**: `fg-white`, `fg-dark`, `fg-contrast`, `fg-heading`, `fg-body`, `fg-body-subtle`,
|
||||
`fg-disabled`
|
||||
- **Brand**: `fg-brand-soft`, `fg-brand`, `fg-brand-strong`
|
||||
- **Status**: `fg-success`, `fg-success-strong`, `fg-danger`, `fg-danger-strong`, `fg-warning`,
|
||||
`fg-warning-strong`, `fg-sensitive`
|
||||
- **Accent**: `fg-accent-primary`, `fg-accent-secondary`, `fg-accent-tertiary` (with `-soft` and
|
||||
`-strong` variants)
|
||||
- Format: `--color-fg-{name}`
|
||||
|
||||
Tailwind traditionally has a very large color palette. Bitwarden has their own more limited color
|
||||
palette instead.
|
||||
### Semantic Background Tokens
|
||||
|
||||
This has a couple of advantages:
|
||||
- **Neutral**: `bg-white`, `bg-dark`, `bg-contrast`, `bg-contrast-strong`, `bg-primary`,
|
||||
`bg-secondary`, `bg-tertiary`, `bg-quaternary`, `bg-gray`, `bg-disabled`
|
||||
- **Brand**: `bg-brand-softer`, `bg-brand-soft`, `bg-brand-medium`, `bg-brand`, `bg-brand-strong`
|
||||
- **Status**: `bg-success-soft`, `bg-success-medium`, `bg-success`, `bg-success-strong`,
|
||||
`bg-danger-soft`, `bg-danger-medium`, `bg-danger`, `bg-danger-strong`, `bg-warning-soft`,
|
||||
`bg-warning-medium`, `bg-warning`, `bg-warning-strong`
|
||||
- **Accent**: `bg-accent-primary-soft`, `bg-accent-primary-medium`, `bg-accent-primary`,
|
||||
`bg-accent-secondary-soft`, `bg-accent-secondary-medium`, `bg-accent-secondary`,
|
||||
`bg-accent-tertiary-soft`, `bg-accent-tertiary-medium`, `bg-accent-tertiary`
|
||||
- **Special**: `bg-hover`, `bg-overlay`
|
||||
- Format: `--color-bg-{name}`
|
||||
|
||||
- Promotes consistency across the application.
|
||||
- Easier to maintain and make adjustments.
|
||||
- Allows us to support more than two themes light & dark, should it be needed.
|
||||
### Semantic Border Tokens
|
||||
|
||||
Below are all the permited colors. Please consult design before considering adding a new color.
|
||||
- **Neutral**: `border-muted`, `border-light`, `border-base`, `border-strong`, `border-buffer`
|
||||
- **Brand**: `border-brand-soft`, `border-brand`, `border-brand-strong`
|
||||
- **Status**: `border-success-soft`, `border-success`, `border-success-strong`,
|
||||
`border-danger-soft`, `border-danger`, `border-danger-strong`, `border-warning-soft`,
|
||||
`border-warning`, `border-warning-strong`
|
||||
- **Accent**: `border-accent-primary-soft`, `border-accent-primary`, `border-accent-secondary-soft`,
|
||||
`border-accent-secondary`, `border-accent-tertiary-soft`, `border-accent-tertiary`
|
||||
- **Focus**: `border-focus`
|
||||
- Format: `--color-border-{name}`
|
||||
|
||||
<div class="tw-flex tw-space-x-4">
|
||||
<Table />
|
||||
<Table class="theme_dark tw-bg-background" />
|
||||
## Semantic Color Tokens
|
||||
|
||||
> **Note:** Due to Tailwind's utility naming and our semantic token structure, class names will
|
||||
> appear repetitive (e.g., `tw-bg-bg-primary`). This repetition is intentional:
|
||||
>
|
||||
> - `tw-` = Tailwind prefix
|
||||
> - `bg-` = Tailwind utility type (background)
|
||||
> - `bg-primary` = Our semantic token name
|
||||
|
||||
### Background Colors
|
||||
|
||||
Use `tw-bg-bg-*` for background colors. These tokens automatically adapt to dark mode.
|
||||
|
||||
export const Swatch = ({ name }) => {
|
||||
const swatchClass = `tw-h-10 tw-w-10 tw-shrink-0 tw-rounded-lg tw-border tw-border-border-base tw-bg-${name}`;
|
||||
return <div className={swatchClass} />;
|
||||
};
|
||||
|
||||
export const BackgroundCard = ({ name, primitiveColor }) => {
|
||||
const bgClass = `tw-flex tw-items-center tw-gap-3 tw-rounded-xl tw-p-4 tw-border tw-border-border-base tw-bg-bg-primary`;
|
||||
const swatchClass = `tw-h-10 tw-w-10 tw-shrink-0 tw-rounded-lg tw-border tw-border-base tw-bg-bg-${name}`;
|
||||
return (
|
||||
<div className={bgClass}>
|
||||
<div className="tw-flex-1 tw-min-w-0">
|
||||
<div className="tw-font-mono tw-text-sm tw-font-semibold tw-text-fg-heading">bg-{name}</div>
|
||||
<div className="tw-text-xs tw-text-fg-body-subtle tw-mt-0.5">({primitiveColor})</div>
|
||||
</div>
|
||||
<Swatch name={`bg-${name}`} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
<div class="sb-unstyled tw-grid tw-grid-cols-2 tw-gap-8 tw-my-6">
|
||||
<div class="tw-bg-bg-primary tw-p-6 tw-rounded-lg tw-border tw-border-border-base">
|
||||
<h3 class="sb-unstyled sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Light mode</h3>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Neutral</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<BackgroundCard name="white" primitiveColor="white" />
|
||||
<BackgroundCard name="dark" primitiveColor="gray-800" />
|
||||
<BackgroundCard name="contrast" primitiveColor="gray-800" />
|
||||
<BackgroundCard name="contrast-strong" primitiveColor="gray-950" />
|
||||
<BackgroundCard name="primary" primitiveColor="white" />
|
||||
<BackgroundCard name="secondary" primitiveColor="gray-050" />
|
||||
<BackgroundCard name="tertiary" primitiveColor="gray-050" />
|
||||
<BackgroundCard name="quaternary" primitiveColor="gray-200" />
|
||||
<BackgroundCard name="gray" primitiveColor="gray-300" />
|
||||
<BackgroundCard name="disabled" primitiveColor="gray-100" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Brand</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<BackgroundCard name="brand-softer" primitiveColor="brand-050" />
|
||||
<BackgroundCard name="brand-soft" primitiveColor="brand-100" />
|
||||
<BackgroundCard name="brand-medium" primitiveColor="brand-200" />
|
||||
<BackgroundCard name="brand" primitiveColor="brand-700" />
|
||||
<BackgroundCard name="brand-strong" primitiveColor="brand-800" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Status</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<BackgroundCard name="success-soft" primitiveColor="green-050" />
|
||||
<BackgroundCard name="success-medium" primitiveColor="green-100" />
|
||||
<BackgroundCard name="success" primitiveColor="green-600" />
|
||||
<BackgroundCard name="success-strong" primitiveColor="green-700" />
|
||||
<BackgroundCard name="danger-soft" primitiveColor="red-050" />
|
||||
<BackgroundCard name="danger-medium" primitiveColor="red-100" />
|
||||
<BackgroundCard name="danger" primitiveColor="red-600" />
|
||||
<BackgroundCard name="danger-strong" primitiveColor="red-700" />
|
||||
<BackgroundCard name="warning-soft" primitiveColor="orange-050" />
|
||||
<BackgroundCard name="warning-medium" primitiveColor="orange-100" />
|
||||
<BackgroundCard name="warning" primitiveColor="orange-600" />
|
||||
<BackgroundCard name="warning-strong" primitiveColor="orange-700" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Accent</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<BackgroundCard name="accent-primary-soft" primitiveColor="teal-050" />
|
||||
<BackgroundCard name="accent-primary-medium" primitiveColor="teal-100" />
|
||||
<BackgroundCard name="accent-primary" primitiveColor="teal-400" />
|
||||
<BackgroundCard name="accent-secondary-soft" primitiveColor="coral-050" />
|
||||
<BackgroundCard name="accent-secondary-medium" primitiveColor="coral-100" />
|
||||
<BackgroundCard name="accent-secondary" primitiveColor="coral-400" />
|
||||
<BackgroundCard name="accent-tertiary-soft" primitiveColor="purple-050" />
|
||||
<BackgroundCard name="accent-tertiary-medium" primitiveColor="purple-100" />
|
||||
<BackgroundCard name="accent-tertiary" primitiveColor="purple-600" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Hover</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<BackgroundCard name="hover" primitiveColor="rgba" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Overlay</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<BackgroundCard name="overlay" primitiveColor="rgba" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="theme_dark tw-bg-bg-primary tw-p-6 tw-rounded-lg">
|
||||
<h3 class="sb-unstyled sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Dark mode</h3>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Neutral</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<BackgroundCard name="white" primitiveColor="white" />
|
||||
<BackgroundCard name="dark" primitiveColor="gray-800" />
|
||||
<BackgroundCard name="contrast" primitiveColor="gray-050" />
|
||||
<BackgroundCard name="contrast-strong" primitiveColor="gray-950" />
|
||||
<BackgroundCard name="primary" primitiveColor="gray-900" />
|
||||
<BackgroundCard name="secondary" primitiveColor="gray-800" />
|
||||
<BackgroundCard name="tertiary" primitiveColor="gray-950" />
|
||||
<BackgroundCard name="quaternary" primitiveColor="gray-950" />
|
||||
<BackgroundCard name="gray" primitiveColor="gray-600" />
|
||||
<BackgroundCard name="disabled" primitiveColor="gray-950" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Brand</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<BackgroundCard name="brand-softer" primitiveColor="brand-950" />
|
||||
<BackgroundCard name="brand-soft" primitiveColor="brand-900" />
|
||||
<BackgroundCard name="brand-medium" primitiveColor="brand-800" />
|
||||
<BackgroundCard name="brand" primitiveColor="brand-400" />
|
||||
<BackgroundCard name="brand-strong" primitiveColor="brand-300" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Status</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<BackgroundCard name="success-soft" primitiveColor="green-950" />
|
||||
<BackgroundCard name="success-medium" primitiveColor="green-900" />
|
||||
<BackgroundCard name="success" primitiveColor="green-400" />
|
||||
<BackgroundCard name="success-strong" primitiveColor="green-300" />
|
||||
<BackgroundCard name="danger-soft" primitiveColor="red-950" />
|
||||
<BackgroundCard name="danger-medium" primitiveColor="red-900" />
|
||||
<BackgroundCard name="danger" primitiveColor="red-400" />
|
||||
<BackgroundCard name="danger-strong" primitiveColor="red-300" />
|
||||
<BackgroundCard name="warning-soft" primitiveColor="orange-950" />
|
||||
<BackgroundCard name="warning-medium" primitiveColor="orange-900" />
|
||||
<BackgroundCard name="warning" primitiveColor="orange-400" />
|
||||
<BackgroundCard name="warning-strong" primitiveColor="orange-300" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Accent</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<BackgroundCard name="accent-primary-soft" primitiveColor="teal-950" />
|
||||
<BackgroundCard name="accent-primary-medium" primitiveColor="teal-900" />
|
||||
<BackgroundCard name="accent-primary" primitiveColor="teal-400" />
|
||||
<BackgroundCard name="accent-secondary-soft" primitiveColor="coral-950" />
|
||||
<BackgroundCard name="accent-secondary-medium" primitiveColor="coral-900" />
|
||||
<BackgroundCard name="accent-secondary" primitiveColor="coral-400" />
|
||||
<BackgroundCard name="accent-tertiary-soft" primitiveColor="purple-950" />
|
||||
<BackgroundCard name="accent-tertiary-medium" primitiveColor="purple-900" />
|
||||
<BackgroundCard name="accent-tertiary" primitiveColor="purple-600" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Hover</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<BackgroundCard name="hover" primitiveColor="rgba" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Overlay</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<BackgroundCard name="overlay" primitiveColor="rgba" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
### Foreground Colors
|
||||
|
||||
Use `tw-text-fg-*` for text colors. These tokens automatically adapt to dark mode.
|
||||
|
||||
export const ForegroundCard = ({ name, primitiveColor }) => {
|
||||
const textClass = `tw-text-fg-${name} tw-text-2xl tw-font-bold tw-shrink-0`;
|
||||
return (
|
||||
<div className="tw-flex tw-items-center tw-gap-3 tw-rounded-xl tw-p-4 tw-border tw-border-border-base tw-bg-bg-primary">
|
||||
<div className="tw-flex-1 tw-min-w-0">
|
||||
<div className="tw-font-mono tw-text-sm tw-font-semibold tw-text-fg-heading">fg-{name}</div>
|
||||
<div className="tw-text-xs tw-text-fg-body-subtle tw-mt-0.5">({primitiveColor})</div>
|
||||
</div>
|
||||
<Swatch name={`fg-${name}`} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
<div class="sb-unstyled tw-grid tw-grid-cols-2 tw-gap-8 tw-my-6">
|
||||
<div class="tw-bg-bg-primary tw-p-6 tw-rounded-lg tw-border tw-border-border-base">
|
||||
<h3 class="sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Light mode</h3>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Neutral</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<ForegroundCard name="white" primitiveColor="#ffffff" />
|
||||
<ForegroundCard name="dark" primitiveColor="gray-900" />
|
||||
<ForegroundCard name="contrast" primitiveColor="white" />
|
||||
<ForegroundCard name="heading" primitiveColor="gray-900" />
|
||||
<ForegroundCard name="body" primitiveColor="gray-600" />
|
||||
<ForegroundCard name="body-subtle" primitiveColor="gray-500" />
|
||||
<ForegroundCard name="disabled" primitiveColor="gray-400" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Brand</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<ForegroundCard name="brand-soft" primitiveColor="brand-200" />
|
||||
<ForegroundCard name="brand" primitiveColor="brand-700" />
|
||||
<ForegroundCard name="brand-strong" primitiveColor="brand-900" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Status</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<ForegroundCard name="success" primitiveColor="green-700" />
|
||||
<ForegroundCard name="success-strong" primitiveColor="green-900" />
|
||||
<ForegroundCard name="danger" primitiveColor="red-700" />
|
||||
<ForegroundCard name="danger-strong" primitiveColor="red-900" />
|
||||
<ForegroundCard name="warning" primitiveColor="orange-600" />
|
||||
<ForegroundCard name="warning-strong" primitiveColor="orange-900" />
|
||||
<ForegroundCard name="sensitive" primitiveColor="pink-600" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Accent</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<ForegroundCard name="accent-primary-soft" primitiveColor="teal-200" />
|
||||
<ForegroundCard name="accent-primary" primitiveColor="teal-400" />
|
||||
<ForegroundCard name="accent-primary-strong" primitiveColor="teal-800" />
|
||||
<ForegroundCard name="accent-secondary-soft" primitiveColor="coral-200" />
|
||||
<ForegroundCard name="accent-secondary" primitiveColor="coral-400" />
|
||||
<ForegroundCard name="accent-secondary-strong" primitiveColor="coral-900" />
|
||||
<ForegroundCard name="accent-tertiary-soft" primitiveColor="purple-200" />
|
||||
<ForegroundCard name="accent-tertiary" primitiveColor="purple-700" />
|
||||
<ForegroundCard name="accent-tertiary-strong" primitiveColor="purple-900" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="theme_dark tw-bg-bg-primary tw-p-6 tw-rounded-lg">
|
||||
<h3 class="sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Dark mode</h3>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Neutral</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<ForegroundCard name="white" primitiveColor="#ffffff" />
|
||||
<ForegroundCard name="dark" primitiveColor="gray-900" />
|
||||
<ForegroundCard name="contrast" primitiveColor="gray-900" />
|
||||
<ForegroundCard name="heading" primitiveColor="gray-050" />
|
||||
<ForegroundCard name="body" primitiveColor="gray-200" />
|
||||
<ForegroundCard name="body-subtle" primitiveColor="gray-400" />
|
||||
<ForegroundCard name="disabled" primitiveColor="gray-600" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Brand</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<ForegroundCard name="brand-soft" primitiveColor="brand-500" />
|
||||
<ForegroundCard name="brand" primitiveColor="brand-400" />
|
||||
<ForegroundCard name="brand-strong" primitiveColor="brand-200" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Status</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<ForegroundCard name="success" primitiveColor="green-400" />
|
||||
<ForegroundCard name="success-strong" primitiveColor="green-100" />
|
||||
<ForegroundCard name="danger" primitiveColor="red-400" />
|
||||
<ForegroundCard name="danger-strong" primitiveColor="red-100" />
|
||||
<ForegroundCard name="warning" primitiveColor="orange-400" />
|
||||
<ForegroundCard name="warning-strong" primitiveColor="orange-100" />
|
||||
<ForegroundCard name="sensitive" primitiveColor="pink-300" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Accent</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<ForegroundCard name="accent-primary-soft" primitiveColor="teal-400" />
|
||||
<ForegroundCard name="accent-primary" primitiveColor="teal-300" />
|
||||
<ForegroundCard name="accent-primary-strong" primitiveColor="teal-100" />
|
||||
<ForegroundCard name="accent-secondary-soft" primitiveColor="coral-500" />
|
||||
<ForegroundCard name="accent-secondary" primitiveColor="coral-400" />
|
||||
<ForegroundCard name="accent-secondary-strong" primitiveColor="coral-100" />
|
||||
<ForegroundCard name="accent-tertiary-soft" primitiveColor="purple-500" />
|
||||
<ForegroundCard name="accent-tertiary" primitiveColor="purple-400" />
|
||||
<ForegroundCard name="accent-tertiary-strong" primitiveColor="purple-100" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
### Border Colors
|
||||
|
||||
Use `tw-border-border-*` for border colors. These tokens automatically adapt to dark mode.
|
||||
|
||||
export const BorderCard = ({ name, primitiveColor }) => {
|
||||
return (
|
||||
<div className="tw-flex tw-items-center tw-gap-3 tw-rounded-xl tw-p-4 tw-border tw-border-border-base tw-bg-bg-primary">
|
||||
<div className="tw-flex-1 tw-min-w-0">
|
||||
<div className="tw-font-mono tw-text-sm tw-font-semibold tw-text-fg-heading">
|
||||
border-{name}
|
||||
</div>
|
||||
<div className="tw-text-xs tw-text-fg-body-subtle tw-mt-0.5">({primitiveColor})</div>
|
||||
</div>
|
||||
<Swatch name={`border-${name}`} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
<div class="sb-unstyled tw-grid tw-grid-cols-2 tw-gap-8 tw-my-6">
|
||||
<div class="tw-bg-bg-primary tw-p-6 tw-rounded-lg tw-border tw-border-border-base">
|
||||
<h3 class="sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Light mode</h3>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Neutral</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<BorderCard name="muted" primitiveColor="gray-100" />
|
||||
<BorderCard name="light" primitiveColor="gray-200" />
|
||||
<BorderCard name="base" primitiveColor="gray-200" />
|
||||
<BorderCard name="strong" primitiveColor="gray-300" />
|
||||
<BorderCard name="buffer" primitiveColor="gray-100" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Brand</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<BorderCard name="brand-soft" primitiveColor="brand-200" />
|
||||
<BorderCard name="brand" primitiveColor="brand-700" />
|
||||
<BorderCard name="brand-strong" primitiveColor="brand-900" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Status</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<BorderCard name="success-soft" primitiveColor="green-200" />
|
||||
<BorderCard name="success" primitiveColor="green-700" />
|
||||
<BorderCard name="success-strong" primitiveColor="green-900" />
|
||||
<BorderCard name="danger-soft" primitiveColor="red-200" />
|
||||
<BorderCard name="danger" primitiveColor="red-700" />
|
||||
<BorderCard name="danger-strong" primitiveColor="red-900" />
|
||||
<BorderCard name="warning-soft" primitiveColor="orange-200" />
|
||||
<BorderCard name="warning" primitiveColor="orange-600" />
|
||||
<BorderCard name="warning-strong" primitiveColor="orange-900" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Accent</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<BorderCard name="accent-primary-soft" primitiveColor="teal-200" />
|
||||
<BorderCard name="accent-primary" primitiveColor="teal-600" />
|
||||
<BorderCard name="accent-secondary-soft" primitiveColor="coral-200" />
|
||||
<BorderCard name="accent-secondary" primitiveColor="coral-600" />
|
||||
<BorderCard name="accent-tertiary-soft" primitiveColor="purple-200" />
|
||||
<BorderCard name="accent-tertiary" primitiveColor="purple-600" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Focus</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<BorderCard name="focus" primitiveColor="#000000" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="theme_dark tw-bg-bg-primary tw-p-6 tw-rounded-lg">
|
||||
<h3 class="sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Dark mode</h3>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Neutral</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<BorderCard name="muted" primitiveColor="gray-800" />
|
||||
<BorderCard name="light" primitiveColor="gray-700" />
|
||||
<BorderCard name="base" primitiveColor="gray-700" />
|
||||
<BorderCard name="strong" primitiveColor="gray-600" />
|
||||
<BorderCard name="buffer" primitiveColor="gray-900" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Brand</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<BorderCard name="brand-soft" primitiveColor="brand-800" />
|
||||
<BorderCard name="brand" primitiveColor="brand-700" />
|
||||
<BorderCard name="brand-strong" primitiveColor="brand-600" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Status</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<BorderCard name="success-soft" primitiveColor="green-800" />
|
||||
<BorderCard name="success" primitiveColor="green-400" />
|
||||
<BorderCard name="success-strong" primitiveColor="green-200" />
|
||||
<BorderCard name="danger-soft" primitiveColor="red-800" />
|
||||
<BorderCard name="danger" primitiveColor="red-400" />
|
||||
<BorderCard name="danger-strong" primitiveColor="red-200" />
|
||||
<BorderCard name="warning-soft" primitiveColor="orange-800" />
|
||||
<BorderCard name="warning" primitiveColor="orange-400" />
|
||||
<BorderCard name="warning-strong" primitiveColor="orange-200" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Accent</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<BorderCard name="accent-primary-soft" primitiveColor="teal-800" />
|
||||
<BorderCard name="accent-primary" primitiveColor="teal-600" />
|
||||
<BorderCard name="accent-secondary-soft" primitiveColor="coral-800" />
|
||||
<BorderCard name="accent-secondary" primitiveColor="coral-500" />
|
||||
<BorderCard name="accent-tertiary-soft" primitiveColor="purple-800" />
|
||||
<BorderCard name="accent-tertiary" primitiveColor="purple-500" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Focus</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<BorderCard name="focus" primitiveColor="#ffffff" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Usage Guidelines
|
||||
|
||||
### ✅ DO - Use semantic tokens via Tailwind
|
||||
|
||||
```html
|
||||
<!-- Text colors -->
|
||||
<h1 class="tw-text-fg-heading">Heading text</h1>
|
||||
<p class="tw-text-fg-body">Body text</p>
|
||||
<button class="tw-text-fg-brand">Brand action</button>
|
||||
<span class="tw-text-fg-danger">Error message</span>
|
||||
|
||||
<!-- Background colors -->
|
||||
<div class="tw-bg-bg-primary">Primary background</div>
|
||||
<div class="tw-bg-bg-secondary">Secondary background</div>
|
||||
<button class="tw-bg-bg-brand tw-text-fg-white">Brand button</button>
|
||||
<div class="tw-bg-bg-danger-soft tw-text-fg-danger">Danger alert</div>
|
||||
|
||||
<!-- Border colors -->
|
||||
<div class="tw-border tw-border-border-base">Base border</div>
|
||||
<input class="tw-border tw-border-border-light focus:tw-border-border-focus" />
|
||||
<div class="tw-border-2 tw-border-border-brand">Brand border</div>
|
||||
<button class="tw-border tw-border-border-danger">Danger border</button>
|
||||
|
||||
<!-- Combined examples -->
|
||||
<div
|
||||
class="tw-bg-bg-success-soft tw-text-fg-success tw-border tw-border-border-success-soft tw-rounded tw-p-4"
|
||||
>
|
||||
Success alert with matching colors
|
||||
</div>
|
||||
|
||||
<!-- Hover states -->
|
||||
<div class="hover:tw-bg-bg-hover">Hover effect</div>
|
||||
|
||||
<!-- Overlays -->
|
||||
<div class="tw-bg-bg-overlay">Modal overlay</div>
|
||||
```
|
||||
|
||||
### ❌ DON'T - Use primitive colors directly
|
||||
|
||||
```html
|
||||
<!-- Bad: These Tailwind classes don't exist (primitives not exposed) -->
|
||||
<p class="tw-text-brand-900">Text</p>
|
||||
<div class="tw-bg-brand-600">Background</div>
|
||||
|
||||
<!-- Bad: Using primitives with Tailwind bracket notation -->
|
||||
<p class="tw-text-[var(--color-brand-600)]">Text</p>
|
||||
<div class="tw-bg-[var(--color-success-700)]">Background</div>
|
||||
|
||||
<!-- Bad: Using primitive CSS variables bypasses the semantic layer -->
|
||||
<span style="color: var(--color-brand-600)">Text</span>
|
||||
<div style="background: var(--color-success-700)">Background</div>
|
||||
```
|
||||
|
||||
**Why this is wrong:** Primitives aren't semantic and may change. Always use semantic tokens like
|
||||
`tw-text-fg-brand`, `tw-bg-success`, etc.
|
||||
|
||||
---
|
||||
|
||||
## Dark Mode
|
||||
|
||||
- Semantic tokens automatically adapt to dark mode via `.theme_dark` class
|
||||
- No component changes needed when theme switches
|
||||
- The same semantic token name works in both light and dark themes
|
||||
- All color values are automatically swapped based on the active theme
|
||||
|
||||
---
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
- **New components:** Use semantic tokens (`fg-*`, `bg-*`, `border-*`) exclusively
|
||||
- **Existing components:** Keep legacy tokens until refactoring
|
||||
- **When refactoring:** Replace legacy tokens with semantic equivalents
|
||||
|
||||
---
|
||||
|
||||
## Legacy Colors
|
||||
|
||||
**Legacy colors (RGB format)** still exist for backwards compatibility:
|
||||
|
||||
- `primary-*`, `secondary-*`, `success-*`, `danger-*`, `warning-*`, etc.
|
||||
- Use these only when updating existing components
|
||||
- Migrate to new semantic tokens when refactoring
|
||||
|
||||
The following legacy colors are displayed below with both light and dark mode values:
|
||||
|
||||
export const LegacyCard = ({ name }) => {
|
||||
return (
|
||||
<div className="tw-flex tw-items-center tw-gap-3 tw-rounded-xl tw-p-4 tw-border tw-border-border-base tw-bg-bg-primary">
|
||||
<div className="tw-flex-1 tw-min-w-0">
|
||||
<div className="tw-font-mono tw-text-sm tw-font-semibold tw-text-fg-heading">{name}</div>
|
||||
<div className="tw-text-xs tw-text-fg-body-subtle tw-mt-0.5">(legacy RGB format)</div>
|
||||
</div>
|
||||
<Swatch name={name} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
<div class="tw-grid tw-grid-cols-2 tw-gap-8">
|
||||
<div class="tw-bg-bg-primary tw-p-6 tw-rounded-lg">
|
||||
<h3 class="sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Light mode</h3>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">General</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<LegacyCard name="background" />
|
||||
<LegacyCard name="background-alt" />
|
||||
<LegacyCard name="background-alt2" />
|
||||
<LegacyCard name="background-alt3" />
|
||||
<LegacyCard name="background-alt4" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Primary</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<LegacyCard name="primary-100" />
|
||||
<LegacyCard name="primary-300" />
|
||||
<LegacyCard name="primary-600" />
|
||||
<LegacyCard name="primary-700" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">
|
||||
Secondary
|
||||
</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<LegacyCard name="secondary-100" />
|
||||
<LegacyCard name="secondary-300" />
|
||||
<LegacyCard name="secondary-500" />
|
||||
<LegacyCard name="secondary-600" />
|
||||
<LegacyCard name="secondary-700" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Success</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<LegacyCard name="success-100" />
|
||||
<LegacyCard name="success-600" />
|
||||
<LegacyCard name="success-700" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Danger</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<LegacyCard name="danger-100" />
|
||||
<LegacyCard name="danger-600" />
|
||||
<LegacyCard name="danger-700" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Warning</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<LegacyCard name="warning-100" />
|
||||
<LegacyCard name="warning-600" />
|
||||
<LegacyCard name="warning-700" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Info</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<LegacyCard name="info-100" />
|
||||
<LegacyCard name="info-600" />
|
||||
<LegacyCard name="info-700" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="theme_dark tw-bg-bg-primary tw-p-6 tw-rounded-lg">
|
||||
<h3 class="sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Dark mode</h3>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">General</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<LegacyCard name="background" />
|
||||
<LegacyCard name="background-alt" />
|
||||
<LegacyCard name="background-alt2" />
|
||||
<LegacyCard name="background-alt3" />
|
||||
<LegacyCard name="background-alt4" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Primary</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<LegacyCard name="primary-100" />
|
||||
<LegacyCard name="primary-300" />
|
||||
<LegacyCard name="primary-600" />
|
||||
<LegacyCard name="primary-700" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">
|
||||
Secondary
|
||||
</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<LegacyCard name="secondary-100" />
|
||||
<LegacyCard name="secondary-300" />
|
||||
<LegacyCard name="secondary-500" />
|
||||
<LegacyCard name="secondary-600" />
|
||||
<LegacyCard name="secondary-700" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Success</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<LegacyCard name="success-100" />
|
||||
<LegacyCard name="success-600" />
|
||||
<LegacyCard name="success-700" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Danger</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<LegacyCard name="danger-100" />
|
||||
<LegacyCard name="danger-600" />
|
||||
<LegacyCard name="danger-700" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Warning</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<LegacyCard name="warning-100" />
|
||||
<LegacyCard name="warning-600" />
|
||||
<LegacyCard name="warning-700" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sb-unstyled tw-mb-6">
|
||||
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Info</h4>
|
||||
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
|
||||
<LegacyCard name="info-100" />
|
||||
<LegacyCard name="info-600" />
|
||||
<LegacyCard name="info-700" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,12 @@
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
/* ========================================
|
||||
* LEGACY COLORS (RGB format)
|
||||
* These are the original colors used throughout the app.
|
||||
* Use these for existing components until migration is complete.
|
||||
* ======================================== */
|
||||
|
||||
--color-transparent-hover: rgb(0 0 0 / 0.02);
|
||||
--color-shadow: 168 179 200;
|
||||
|
||||
@@ -74,6 +80,279 @@
|
||||
--color-illustration-bg-tertiary: 255 255 255;
|
||||
--color-illustration-tertiary: 255 191 0;
|
||||
--color-illustration-logo: 23 93 220;
|
||||
|
||||
/* ========================================
|
||||
* NEW COLOR PALETTE (Hex format)
|
||||
* These colors are from the new Figma design system.
|
||||
* Use these for new components and features.
|
||||
* Format: --color-{family}-{shade} where shade ranges from 050 to 950
|
||||
* ======================================== */
|
||||
|
||||
/* Brand Colors */
|
||||
--color-brand-050: #eef6ff;
|
||||
--color-brand-100: #dbeafe;
|
||||
--color-brand-200: #bedbff;
|
||||
--color-brand-300: #8ec5ff;
|
||||
--color-brand-400: #6baefa;
|
||||
--color-brand-500: #418bfb;
|
||||
--color-brand-600: #2a70f4;
|
||||
--color-brand-700: #175ddc;
|
||||
--color-brand-800: #0d43af;
|
||||
--color-brand-900: #0c3276;
|
||||
--color-brand-950: #162455;
|
||||
|
||||
/* Gray Colors */
|
||||
--color-gray-050: #f9fafb;
|
||||
--color-gray-100: #f3f4f6;
|
||||
--color-gray-200: #e5e7eb;
|
||||
--color-gray-300: #d1d5dc;
|
||||
--color-gray-400: #99a1af;
|
||||
--color-gray-500: #6a7282;
|
||||
--color-gray-600: #4a5565;
|
||||
--color-gray-700: #333e4f;
|
||||
--color-gray-800: #1e2939;
|
||||
--color-gray-900: #101828;
|
||||
--color-gray-950: #070b18;
|
||||
--color-gray-950-rgb: 7, 11, 24;
|
||||
|
||||
/* Red Colors */
|
||||
--color-red-050: #fef2f2;
|
||||
--color-red-100: #ffe2e2;
|
||||
--color-red-200: #ffc9c9;
|
||||
--color-red-300: #ffa2a2;
|
||||
--color-red-400: #ff6467;
|
||||
--color-red-500: #fb2c36;
|
||||
--color-red-600: #e7000b;
|
||||
--color-red-700: #c10007;
|
||||
--color-red-800: #9f0712;
|
||||
--color-red-900: #791112;
|
||||
--color-red-950: #460809;
|
||||
|
||||
/* Orange Colors */
|
||||
--color-orange-050: #fff8f1;
|
||||
--color-orange-100: #feecdc;
|
||||
--color-orange-200: #fcd9bd;
|
||||
--color-orange-300: #fdba8c;
|
||||
--color-orange-400: #ff8a4c;
|
||||
--color-orange-500: #ff5a1f;
|
||||
--color-orange-600: #d03801;
|
||||
--color-orange-700: #b43403;
|
||||
--color-orange-800: #8a2c0d;
|
||||
--color-orange-900: #70240b;
|
||||
--color-orange-950: #441306;
|
||||
|
||||
/* Yellow Colors */
|
||||
--color-yellow-050: #fefce8;
|
||||
--color-yellow-100: #fef9c2;
|
||||
--color-yellow-200: #fff085;
|
||||
--color-yellow-300: #ffdf20;
|
||||
--color-yellow-400: #fdc700;
|
||||
--color-yellow-500: #f0b100;
|
||||
--color-yellow-600: #d08700;
|
||||
--color-yellow-700: #a65f00;
|
||||
--color-yellow-800: #894b00;
|
||||
--color-yellow-900: #733e0a;
|
||||
--color-yellow-950: #432004;
|
||||
|
||||
/* Green Colors */
|
||||
--color-green-050: #f0fdf4;
|
||||
--color-green-100: #dcfce7;
|
||||
--color-green-200: #b9f8cf;
|
||||
--color-green-300: #7bf1a8;
|
||||
--color-green-400: #18dc7a;
|
||||
--color-green-500: #0abf52;
|
||||
--color-green-600: #00a63e;
|
||||
--color-green-700: #008236;
|
||||
--color-green-800: #016630;
|
||||
--color-green-900: #0d542b;
|
||||
--color-green-950: #032e15;
|
||||
|
||||
/* Pink Colors */
|
||||
--color-pink-050: #fdf2f8;
|
||||
--color-pink-100: #fce7f3;
|
||||
--color-pink-200: #fccee8;
|
||||
--color-pink-300: #fda5d5;
|
||||
--color-pink-400: #fb64b6;
|
||||
--color-pink-500: #f6339a;
|
||||
--color-pink-600: #e60076;
|
||||
--color-pink-700: #c6005c;
|
||||
--color-pink-800: #a3004c;
|
||||
--color-pink-900: #861043;
|
||||
--color-pink-950: #510424;
|
||||
|
||||
/* Coral Colors */
|
||||
--color-coral-050: #fff2f0;
|
||||
--color-coral-100: #ffe0dc;
|
||||
--color-coral-200: #ffc1b9;
|
||||
--color-coral-300: #ff9585;
|
||||
--color-coral-400: #ff6550;
|
||||
--color-coral-500: #ff4026;
|
||||
--color-coral-600: #e11f05;
|
||||
--color-coral-700: #c71800;
|
||||
--color-coral-800: #a81400;
|
||||
--color-coral-900: #7e0f00;
|
||||
--color-coral-950: #4d0900;
|
||||
|
||||
/* Teal Colors */
|
||||
--color-teal-050: #ecfeff;
|
||||
--color-teal-100: #cefafe;
|
||||
--color-teal-200: #a2f4fd;
|
||||
--color-teal-300: #70ecf5;
|
||||
--color-teal-400: #2cdde9;
|
||||
--color-teal-500: #00c5db;
|
||||
--color-teal-600: #009cb8;
|
||||
--color-teal-700: #007c95;
|
||||
--color-teal-800: #006278;
|
||||
--color-teal-900: #0f495c;
|
||||
--color-teal-950: #042e3e;
|
||||
|
||||
/* Purple Colors */
|
||||
--color-purple-050: #faf5ff;
|
||||
--color-purple-100: #f3e8ff;
|
||||
--color-purple-200: #e9d4ff;
|
||||
--color-purple-300: #dab2ff;
|
||||
--color-purple-400: #c27aff;
|
||||
--color-purple-500: #ad46ff;
|
||||
--color-purple-600: #9810fa;
|
||||
--color-purple-700: #8200db;
|
||||
--color-purple-800: #6e11b0;
|
||||
--color-purple-900: #59168b;
|
||||
--color-purple-950: #3c0366;
|
||||
|
||||
/* White and Black */
|
||||
--color-white: #ffffff;
|
||||
--color-white-rgb: 255, 255, 255;
|
||||
--color-black: #000000;
|
||||
|
||||
/* ========================================
|
||||
* SEMANTIC FOREGROUND COLORS (Light Mode)
|
||||
* These are the tokens that should be exposed to Tailwind
|
||||
* They reference the primitive colors above
|
||||
* ======================================== */
|
||||
|
||||
/* Neutral Foreground */
|
||||
--color-fg-white: var(--color-white);
|
||||
--color-fg-dark: var(--color-gray-900);
|
||||
--color-fg-contrast: var(--color-white);
|
||||
--color-fg-heading: var(--color-gray-900);
|
||||
--color-fg-body: var(--color-gray-600);
|
||||
--color-fg-body-subtle: var(--color-gray-500);
|
||||
--color-fg-disabled: var(--color-gray-400);
|
||||
|
||||
/* Brand Foreground */
|
||||
--color-fg-brand-soft: var(--color-brand-200);
|
||||
--color-fg-brand: var(--color-brand-700);
|
||||
--color-fg-brand-strong: var(--color-brand-900);
|
||||
|
||||
/* Status Foreground */
|
||||
--color-fg-success: var(--color-green-700);
|
||||
--color-fg-success-strong: var(--color-green-900);
|
||||
--color-fg-danger: var(--color-red-700);
|
||||
--color-fg-danger-strong: var(--color-red-900);
|
||||
--color-fg-warning: var(--color-orange-600);
|
||||
--color-fg-warning-strong: var(--color-orange-900);
|
||||
--color-fg-sensitive: var(--color-pink-600);
|
||||
|
||||
/* Accent Foreground */
|
||||
--color-fg-accent-primary-soft: var(--color-teal-200);
|
||||
--color-fg-accent-primary: var(--color-teal-400);
|
||||
--color-fg-accent-primary-strong: var(--color-teal-800);
|
||||
--color-fg-accent-secondary-soft: var(--color-coral-200);
|
||||
--color-fg-accent-secondary: var(--color-coral-400);
|
||||
--color-fg-accent-secondary-strong: var(--color-coral-900);
|
||||
--color-fg-accent-tertiary-soft: var(--color-purple-200);
|
||||
--color-fg-accent-tertiary: var(--color-purple-700);
|
||||
--color-fg-accent-tertiary-strong: var(--color-purple-900);
|
||||
|
||||
/* ========================================
|
||||
* SEMANTIC BACKGROUND COLORS (Light Mode)
|
||||
* ======================================== */
|
||||
|
||||
/* Neutral Background */
|
||||
--color-bg-white: var(--color-white);
|
||||
--color-bg-dark: var(--color-gray-800);
|
||||
--color-bg-contrast: var(--color-gray-800);
|
||||
--color-bg-contrast-strong: var(--color-gray-950);
|
||||
--color-bg-primary: var(--color-white);
|
||||
--color-bg-secondary: var(--color-gray-050);
|
||||
--color-bg-tertiary: var(--color-gray-050);
|
||||
--color-bg-quaternary: var(--color-gray-200);
|
||||
--color-bg-gray: var(--color-gray-300);
|
||||
--color-bg-disabled: var(--color-gray-100);
|
||||
|
||||
/* Brand Background */
|
||||
--color-bg-brand-softer: var(--color-brand-050);
|
||||
--color-bg-brand-soft: var(--color-brand-100);
|
||||
--color-bg-brand-medium: var(--color-brand-200);
|
||||
--color-bg-brand: var(--color-brand-700);
|
||||
--color-bg-brand-strong: var(--color-brand-800);
|
||||
|
||||
/* Status Background */
|
||||
--color-bg-success-soft: var(--color-green-050);
|
||||
--color-bg-success-medium: var(--color-green-100);
|
||||
--color-bg-success: var(--color-green-700);
|
||||
--color-bg-success-strong: var(--color-green-800);
|
||||
--color-bg-danger-soft: var(--color-red-050);
|
||||
--color-bg-danger-medium: var(--color-red-100);
|
||||
--color-bg-danger: var(--color-red-700);
|
||||
--color-bg-danger-strong: var(--color-red-800);
|
||||
--color-bg-warning-soft: var(--color-orange-050);
|
||||
--color-bg-warning-medium: var(--color-orange-100);
|
||||
--color-bg-warning: var(--color-orange-600);
|
||||
--color-bg-warning-strong: var(--color-orange-700);
|
||||
|
||||
/* Accent Background */
|
||||
--color-bg-accent-primary-soft: var(--color-teal-050);
|
||||
--color-bg-accent-primary-medium: var(--color-teal-100);
|
||||
--color-bg-accent-primary: var(--color-teal-400);
|
||||
--color-bg-accent-secondary-soft: var(--color-coral-050);
|
||||
--color-bg-accent-secondary-medium: var(--color-coral-100);
|
||||
--color-bg-accent-secondary: var(--color-coral-400);
|
||||
--color-bg-accent-tertiary-soft: var(--color-purple-050);
|
||||
--color-bg-accent-tertiary-medium: var(--color-purple-100);
|
||||
--color-bg-accent-tertiary: var(--color-purple-600);
|
||||
|
||||
/* Hover & Overlay */
|
||||
--color-bg-hover: rgba(var(--color-gray-950-rgb), 0.05);
|
||||
--color-bg-overlay: rgba(var(--color-gray-950-rgb), 0.3);
|
||||
|
||||
/* ========================================
|
||||
* SEMANTIC BORDER COLORS (Light Mode)
|
||||
* ======================================== */
|
||||
|
||||
/* Neutral Border */
|
||||
--color-border-muted: var(--color-gray-050);
|
||||
--color-border-light: var(--color-gray-100);
|
||||
--color-border-base: var(--color-gray-200);
|
||||
--color-border-strong: var(--color-gray-800);
|
||||
--color-border-buffer: var(--color-white);
|
||||
|
||||
/* Brand Border */
|
||||
--color-border-brand-soft: var(--color-brand-200);
|
||||
--color-border-brand: var(--color-brand-700);
|
||||
--color-border-brand-strong: var(--color-brand-900);
|
||||
|
||||
/* Status Border */
|
||||
--color-border-success-soft: var(--color-green-200);
|
||||
--color-border-success: var(--color-green-700);
|
||||
--color-border-success-strong: var(--color-green-900);
|
||||
--color-border-danger-soft: var(--color-red-200);
|
||||
--color-border-danger: var(--color-red-700);
|
||||
--color-border-danger-strong: var(--color-red-900);
|
||||
--color-border-warning-soft: var(--color-orange-200);
|
||||
--color-border-warning: var(--color-orange-600);
|
||||
--color-border-warning-strong: var(--color-orange-900);
|
||||
|
||||
/* Accent Border */
|
||||
--color-border-accent-primary-soft: var(--color-teal-200);
|
||||
--color-border-accent-primary: var(--color-teal-600);
|
||||
--color-border-accent-secondary-soft: var(--color-coral-200);
|
||||
--color-border-accent-secondary: var(--color-coral-600);
|
||||
--color-border-accent-tertiary-soft: var(--color-purple-200);
|
||||
--color-border-accent-tertiary: var(--color-purple-600);
|
||||
|
||||
/* Focus Border */
|
||||
--color-border-focus: var(--color-black);
|
||||
}
|
||||
|
||||
.theme_light {
|
||||
@@ -140,6 +419,129 @@
|
||||
--color-illustration-bg-tertiary: 243 246 249;
|
||||
--color-illustration-tertiary: 255 191 0;
|
||||
--color-illustration-logo: 255 255 255;
|
||||
|
||||
/* ========================================
|
||||
* SEMANTIC FOREGROUND COLORS (Dark Mode Overrides)
|
||||
* ======================================== */
|
||||
|
||||
/* Neutral Foreground */
|
||||
--color-fg-contrast: var(--color-gray-900);
|
||||
--color-fg-heading: var(--color-gray-050);
|
||||
--color-fg-body: var(--color-gray-200);
|
||||
--color-fg-body-subtle: var(--color-gray-400);
|
||||
--color-fg-disabled: var(--color-gray-600);
|
||||
|
||||
/* Brand Foreground */
|
||||
--color-fg-brand-soft: var(--color-brand-500);
|
||||
--color-fg-brand: var(--color-brand-400);
|
||||
--color-fg-brand-strong: var(--color-brand-200);
|
||||
|
||||
/* Status Foreground */
|
||||
--color-fg-success: var(--color-green-400);
|
||||
--color-fg-success-strong: var(--color-green-100);
|
||||
--color-fg-danger: var(--color-red-400);
|
||||
--color-fg-danger-strong: var(--color-red-100);
|
||||
--color-fg-warning: var(--color-orange-400);
|
||||
--color-fg-warning-strong: var(--color-orange-100);
|
||||
--color-fg-sensitive: var(--color-pink-300);
|
||||
|
||||
/* Accent Foreground */
|
||||
--color-fg-accent-primary-soft: var(--color-teal-400);
|
||||
--color-fg-accent-primary: var(--color-teal-300);
|
||||
--color-fg-accent-primary-strong: var(--color-teal-100);
|
||||
--color-fg-accent-secondary-soft: var(--color-coral-500);
|
||||
--color-fg-accent-secondary: var(--color-coral-400);
|
||||
--color-fg-accent-secondary-strong: var(--color-coral-100);
|
||||
--color-fg-accent-tertiary-soft: var(--color-purple-500);
|
||||
--color-fg-accent-tertiary: var(--color-purple-400);
|
||||
--color-fg-accent-tertiary-strong: var(--color-purple-100);
|
||||
|
||||
/* ========================================
|
||||
* SEMANTIC BACKGROUND COLORS (Dark Mode Overrides)
|
||||
* ======================================== */
|
||||
|
||||
/* Neutral Background */
|
||||
--color-bg-contrast: var(--color-gray-050);
|
||||
--color-bg-contrast-strong: var(--color-gray-050);
|
||||
--color-bg-primary: var(--color-gray-900);
|
||||
--color-bg-secondary: var(--color-gray-800);
|
||||
--color-bg-tertiary: var(--color-gray-950);
|
||||
--color-bg-quaternary: var(--color-gray-700);
|
||||
--color-bg-gray: var(--color-gray-600);
|
||||
--color-bg-disabled: var(--color-gray-950);
|
||||
|
||||
/* Brand Background */
|
||||
--color-bg-brand-softer: var(--color-brand-950);
|
||||
--color-bg-brand-soft: var(--color-brand-900);
|
||||
--color-bg-brand-medium: var(--color-brand-800);
|
||||
--color-bg-brand: var(--color-brand-400);
|
||||
--color-bg-brand-strong: var(--color-brand-300);
|
||||
|
||||
/* Status Background */
|
||||
--color-bg-success-soft: var(--color-green-950);
|
||||
--color-bg-success-medium: var(--color-green-900);
|
||||
--color-bg-success: var(--color-green-400);
|
||||
--color-bg-success-strong: var(--color-green-300);
|
||||
--color-bg-danger-soft: var(--color-red-950);
|
||||
--color-bg-danger-medium: var(--color-red-900);
|
||||
--color-bg-danger: var(--color-red-400);
|
||||
--color-bg-danger-strong: var(--color-red-300);
|
||||
--color-bg-warning-soft: var(--color-orange-950);
|
||||
--color-bg-warning-medium: var(--color-orange-900);
|
||||
--color-bg-warning: var(--color-orange-400);
|
||||
--color-bg-warning-strong: var(--color-orange-300);
|
||||
|
||||
/* Accent Background */
|
||||
--color-bg-accent-primary-soft: var(--color-teal-950);
|
||||
--color-bg-accent-primary-medium: var(--color-teal-900);
|
||||
--color-bg-accent-primary: var(--color-teal-400);
|
||||
--color-bg-accent-secondary-soft: var(--color-coral-950);
|
||||
--color-bg-accent-secondary-medium: var(--color-coral-900);
|
||||
--color-bg-accent-secondary: var(--color-coral-400);
|
||||
--color-bg-accent-tertiary-soft: var(--color-purple-950);
|
||||
--color-bg-accent-tertiary-medium: var(--color-purple-900);
|
||||
--color-bg-accent-tertiary: var(--color-purple-600);
|
||||
|
||||
/* Hover & Overlay */
|
||||
--color-bg-hover: rgba(var(--color-white-rgb), 0.05);
|
||||
--color-bg-overlay: rgba(var(--color-gray-950-rgb), 0.85);
|
||||
|
||||
/* ========================================
|
||||
* SEMANTIC BORDER COLORS (Dark Mode Overrides)
|
||||
* ======================================== */
|
||||
|
||||
/* Neutral Border */
|
||||
--color-border-muted: var(--color-gray-900);
|
||||
--color-border-light: var(--color-gray-800);
|
||||
--color-border-base: var(--color-gray-700);
|
||||
--color-border-strong: var(--color-gray-400);
|
||||
--color-border-buffer: var(--color-gray-950);
|
||||
|
||||
/* Brand Border */
|
||||
--color-border-brand-soft: var(--color-brand-800);
|
||||
--color-border-brand: var(--color-brand-400);
|
||||
--color-border-brand-strong: var(--color-brand-200);
|
||||
|
||||
/* Status Border */
|
||||
--color-border-success-soft: var(--color-green-800);
|
||||
--color-border-success: var(--color-green-400);
|
||||
--color-border-success-strong: var(--color-green-200);
|
||||
--color-border-danger-soft: var(--color-red-800);
|
||||
--color-border-danger: var(--color-red-400);
|
||||
--color-border-danger-strong: var(--color-red-200);
|
||||
--color-border-warning-soft: var(--color-orange-800);
|
||||
--color-border-warning: var(--color-orange-400);
|
||||
--color-border-warning-strong: var(--color-orange-200);
|
||||
|
||||
/* Accent Border */
|
||||
--color-border-accent-primary-soft: var(--color-teal-800);
|
||||
--color-border-accent-secondary-soft: var(--color-coral-800);
|
||||
--color-border-accent-secondary: var(--color-coral-500);
|
||||
--color-border-accent-tertiary-soft: var(--color-purple-800);
|
||||
--color-border-accent-tertiary: var(--color-purple-500);
|
||||
|
||||
/* Focus Border */
|
||||
--color-border-focus: var(--color-white);
|
||||
}
|
||||
|
||||
@layer components {
|
||||
|
||||
@@ -9,9 +9,9 @@ function rgba(color) {
|
||||
module.exports = {
|
||||
prefix: "tw-",
|
||||
content: [
|
||||
"./src/**/*.{html,ts}",
|
||||
"./src/**/*.{html,ts,mdx}",
|
||||
"../../libs/assets/src/**/*.{html,ts}",
|
||||
"../../libs/components/src/**/*.{html,ts}",
|
||||
"../../libs/components/src/**/*.{html,ts,mdx}",
|
||||
"../../libs/key-management-ui/src/**/*.{html,ts}",
|
||||
"../../libs/auth/src/**/*.{html,ts}",
|
||||
],
|
||||
@@ -78,6 +78,46 @@ module.exports = {
|
||||
alt3: rgba("--color-background-alt3"),
|
||||
alt4: rgba("--color-background-alt4"),
|
||||
},
|
||||
bg: {
|
||||
white: "var(--color-bg-white)",
|
||||
dark: "var(--color-bg-dark)",
|
||||
contrast: "var(--color-bg-contrast)",
|
||||
"contrast-strong": "var(--color-bg-contrast-strong)",
|
||||
primary: "var(--color-bg-primary)",
|
||||
secondary: "var(--color-bg-secondary)",
|
||||
tertiary: "var(--color-bg-tertiary)",
|
||||
quaternary: "var(--color-bg-quaternary)",
|
||||
gray: "var(--color-bg-gray)",
|
||||
disabled: "var(--color-bg-disabled)",
|
||||
"brand-softer": "var(--color-bg-brand-softer)",
|
||||
"brand-soft": "var(--color-bg-brand-soft)",
|
||||
"brand-medium": "var(--color-bg-brand-medium)",
|
||||
brand: "var(--color-bg-brand)",
|
||||
"brand-strong": "var(--color-bg-brand-strong)",
|
||||
"success-soft": "var(--color-bg-success-soft)",
|
||||
"success-medium": "var(--color-bg-success-medium)",
|
||||
success: "var(--color-bg-success)",
|
||||
"success-strong": "var(--color-bg-success-strong)",
|
||||
"danger-soft": "var(--color-bg-danger-soft)",
|
||||
"danger-medium": "var(--color-bg-danger-medium)",
|
||||
danger: "var(--color-bg-danger)",
|
||||
"danger-strong": "var(--color-bg-danger-strong)",
|
||||
"warning-soft": "var(--color-bg-warning-soft)",
|
||||
"warning-medium": "var(--color-bg-warning-medium)",
|
||||
warning: "var(--color-bg-warning)",
|
||||
"warning-strong": "var(--color-bg-warning-strong)",
|
||||
"accent-primary-soft": "var(--color-bg-accent-primary-soft)",
|
||||
"accent-primary-medium": "var(--color-bg-accent-primary-medium)",
|
||||
"accent-primary": "var(--color-bg-accent-primary)",
|
||||
"accent-secondary-soft": "var(--color-bg-accent-secondary-soft)",
|
||||
"accent-secondary-medium": "var(--color-bg-accent-secondary-medium)",
|
||||
"accent-secondary": "var(--color-bg-accent-secondary)",
|
||||
"accent-tertiary-soft": "var(--color-bg-accent-tertiary-soft)",
|
||||
"accent-tertiary-medium": "var(--color-bg-accent-tertiary-medium)",
|
||||
"accent-tertiary": "var(--color-bg-accent-tertiary)",
|
||||
hover: "var(--color-bg-hover)",
|
||||
overlay: "var(--color-bg-overlay)",
|
||||
},
|
||||
hover: {
|
||||
default: "var(--color-hover-default)",
|
||||
contrast: "var(--color-hover-contrast)",
|
||||
@@ -92,8 +132,62 @@ module.exports = {
|
||||
tertiary: rgba("--color-illustration-tertiary"),
|
||||
logo: rgba("--color-illustration-logo"),
|
||||
},
|
||||
fg: {
|
||||
white: "var(--color-fg-white)",
|
||||
dark: "var(--color-fg-dark)",
|
||||
contrast: "var(--color-fg-contrast)",
|
||||
heading: "var(--color-fg-heading)",
|
||||
body: "var(--color-fg-body)",
|
||||
"body-subtle": "var(--color-fg-body-subtle)",
|
||||
disabled: "var(--color-fg-disabled)",
|
||||
"brand-soft": "var(--color-fg-brand-soft)",
|
||||
brand: "var(--color-fg-brand)",
|
||||
"brand-strong": "var(--color-fg-brand-strong)",
|
||||
success: "var(--color-fg-success)",
|
||||
"success-strong": "var(--color-fg-success-strong)",
|
||||
danger: "var(--color-fg-danger)",
|
||||
"danger-strong": "var(--color-fg-danger-strong)",
|
||||
warning: "var(--color-fg-warning)",
|
||||
"warning-strong": "var(--color-fg-warning-strong)",
|
||||
sensitive: "var(--color-fg-sensitive)",
|
||||
"accent-primary-soft": "var(--color-fg-accent-primary-soft)",
|
||||
"accent-primary": "var(--color-fg-accent-primary)",
|
||||
"accent-primary-strong": "var(--color-fg-accent-primary-strong)",
|
||||
"accent-secondary-soft": "var(--color-fg-accent-secondary-soft)",
|
||||
"accent-secondary": "var(--color-fg-accent-secondary)",
|
||||
"accent-secondary-strong": "var(--color-fg-accent-secondary-strong)",
|
||||
"accent-tertiary-soft": "var(--color-fg-accent-tertiary-soft)",
|
||||
"accent-tertiary": "var(--color-fg-accent-tertiary)",
|
||||
"accent-tertiary-strong": "var(--color-fg-accent-tertiary-strong)",
|
||||
},
|
||||
border: {
|
||||
muted: "var(--color-border-muted)",
|
||||
light: "var(--color-border-light)",
|
||||
base: "var(--color-border-base)",
|
||||
strong: "var(--color-border-strong)",
|
||||
buffer: "var(--color-border-buffer)",
|
||||
"brand-soft": "var(--color-border-brand-soft)",
|
||||
brand: "var(--color-border-brand)",
|
||||
"brand-strong": "var(--color-border-brand-strong)",
|
||||
"success-soft": "var(--color-border-success-soft)",
|
||||
success: "var(--color-border-success)",
|
||||
"success-strong": "var(--color-border-success-strong)",
|
||||
"danger-soft": "var(--color-border-danger-soft)",
|
||||
danger: "var(--color-border-danger)",
|
||||
"danger-strong": "var(--color-border-danger-strong)",
|
||||
"warning-soft": "var(--color-border-warning-soft)",
|
||||
warning: "var(--color-border-warning)",
|
||||
"warning-strong": "var(--color-border-warning-strong)",
|
||||
"accent-primary-soft": "var(--color-border-accent-primary-soft)",
|
||||
"accent-primary": "var(--color-border-accent-primary)",
|
||||
"accent-secondary-soft": "var(--color-border-accent-secondary-soft)",
|
||||
"accent-secondary": "var(--color-border-accent-secondary)",
|
||||
"accent-tertiary-soft": "var(--color-border-accent-tertiary-soft)",
|
||||
"accent-tertiary": "var(--color-border-accent-tertiary)",
|
||||
focus: "var(--color-border-focus)",
|
||||
},
|
||||
},
|
||||
textColor: {
|
||||
textColor: () => ({
|
||||
main: rgba("--color-text-main"),
|
||||
muted: rgba("--color-text-muted"),
|
||||
contrast: rgba("--color-text-contrast"),
|
||||
@@ -132,7 +226,62 @@ module.exports = {
|
||||
notification: {
|
||||
600: rgba("--color-notification-600"),
|
||||
},
|
||||
},
|
||||
// New semantic fg tokens - manually flattened to generate tw-text-fg-* utilities
|
||||
"fg-white": "var(--color-fg-white)",
|
||||
"fg-dark": "var(--color-fg-dark)",
|
||||
"fg-contrast": "var(--color-fg-contrast)",
|
||||
"fg-heading": "var(--color-fg-heading)",
|
||||
"fg-body": "var(--color-fg-body)",
|
||||
"fg-body-subtle": "var(--color-fg-body-subtle)",
|
||||
"fg-disabled": "var(--color-fg-disabled)",
|
||||
"fg-brand-soft": "var(--color-fg-brand-soft)",
|
||||
"fg-brand": "var(--color-fg-brand)",
|
||||
"fg-brand-strong": "var(--color-fg-brand-strong)",
|
||||
"fg-success": "var(--color-fg-success)",
|
||||
"fg-success-strong": "var(--color-fg-success-strong)",
|
||||
"fg-danger": "var(--color-fg-danger)",
|
||||
"fg-danger-strong": "var(--color-fg-danger-strong)",
|
||||
"fg-warning": "var(--color-fg-warning)",
|
||||
"fg-warning-strong": "var(--color-fg-warning-strong)",
|
||||
"fg-sensitive": "var(--color-fg-sensitive)",
|
||||
"fg-accent-primary-soft": "var(--color-fg-accent-primary-soft)",
|
||||
"fg-accent-primary": "var(--color-fg-accent-primary)",
|
||||
"fg-accent-primary-strong": "var(--color-fg-accent-primary-strong)",
|
||||
"fg-accent-secondary-soft": "var(--color-fg-accent-secondary-soft)",
|
||||
"fg-accent-secondary": "var(--color-fg-accent-secondary)",
|
||||
"fg-accent-secondary-strong": "var(--color-fg-accent-secondary-strong)",
|
||||
"fg-accent-tertiary-soft": "var(--color-fg-accent-tertiary-soft)",
|
||||
"fg-accent-tertiary": "var(--color-fg-accent-tertiary)",
|
||||
"fg-accent-tertiary-strong": "var(--color-fg-accent-tertiary-strong)",
|
||||
}),
|
||||
borderColor: ({ theme }) => ({
|
||||
...theme("colors"),
|
||||
// New semantic border tokens - manually flattened to generate tw-border-border-* utilities
|
||||
"border-muted": "var(--color-border-muted)",
|
||||
"border-light": "var(--color-border-light)",
|
||||
"border-base": "var(--color-border-base)",
|
||||
"border-strong": "var(--color-border-strong)",
|
||||
"border-buffer": "var(--color-border-buffer)",
|
||||
"border-brand-soft": "var(--color-border-brand-soft)",
|
||||
"border-brand": "var(--color-border-brand)",
|
||||
"border-brand-strong": "var(--color-border-brand-strong)",
|
||||
"border-success-soft": "var(--color-border-success-soft)",
|
||||
"border-success": "var(--color-border-success)",
|
||||
"border-success-strong": "var(--color-border-success-strong)",
|
||||
"border-danger-soft": "var(--color-border-danger-soft)",
|
||||
"border-danger": "var(--color-border-danger)",
|
||||
"border-danger-strong": "var(--color-border-danger-strong)",
|
||||
"border-warning-soft": "var(--color-border-warning-soft)",
|
||||
"border-warning": "var(--color-border-warning)",
|
||||
"border-warning-strong": "var(--color-border-warning-strong)",
|
||||
"border-accent-primary-soft": "var(--color-border-accent-primary-soft)",
|
||||
"border-accent-primary": "var(--color-border-accent-primary)",
|
||||
"border-accent-secondary-soft": "var(--color-border-accent-secondary-soft)",
|
||||
"border-accent-secondary": "var(--color-border-accent-secondary)",
|
||||
"border-accent-tertiary-soft": "var(--color-border-accent-tertiary-soft)",
|
||||
"border-accent-tertiary": "var(--color-border-accent-tertiary)",
|
||||
"border-focus": "var(--color-border-focus)",
|
||||
}),
|
||||
fontFamily: {
|
||||
sans: "var(--font-sans)",
|
||||
serif: "var(--font-serif)",
|
||||
|
||||
@@ -11,11 +11,16 @@ config.content = [
|
||||
"bitwarden_license/bit-web/src/**/*.{html,ts,mdx}",
|
||||
".storybook/preview.tsx",
|
||||
];
|
||||
|
||||
// Safelist is required for dynamic color classes in Storybook color documentation (colors.mdx).
|
||||
// Tailwind's JIT compiler cannot detect dynamically constructed class names like `tw-bg-${name}`,
|
||||
// so we must explicitly safelist these patterns to ensure all color utilities are generated.
|
||||
config.safelist = [
|
||||
{
|
||||
pattern: /tw-bg-(.*)/,
|
||||
},
|
||||
];
|
||||
|
||||
config.corePlugins.preflight = true;
|
||||
|
||||
module.exports = config;
|
||||
|
||||
Reference in New Issue
Block a user