1
0
mirror of https://github.com/bitwarden/jslib synced 2025-12-16 08:13:26 +00:00

Merge branch 'master' into Feature/EndUserVaultRefresh

This commit is contained in:
Thomas Rittson
2022-03-22 08:33:23 +10:00
23 changed files with 865 additions and 117 deletions

41
.github/workflows/chromatic.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
---
name: Chromatic
on: push
jobs:
chromatic:
name: Chromatic
runs-on: ubuntu-20.04
steps:
- name: Set up Node
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea # v2.1.5
with:
node-version: "16"
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
with:
fetch-depth: 0
- name: Cache npm
id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: "~/.npm"
key: ${{ runner.os }}-npm-chromatic-${{ hashFiles('**/package-lock.json') }}
- name: Install Node dependencies
run: npm ci
working-directory: ./components
- name: Publish to Chromatic
uses: chromaui/action@c72f0b48c8887c0ef0abe18ad865a6c1e01e73c6
with:
token: ${{ secrets.GITHUB_TOKEN }}
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
workingDir: ./components
exitOnceUploaded: true
onlyChanged: true
externals: "[\"components/**/*.scss\", \"components/tailwind.config*.js\"]"

View File

@@ -1,39 +1,11 @@
Bitwarden believes that working with security researchers across the globe is crucial to keeping our
users safe. If you believe you've found a security issue in our product or service, we encourage you to
notify us. We welcome working with you to resolve the issue promptly. Thanks in advance!
Bitwarden believes that working with security researchers across the globe is crucial to keeping our users safe. If you believe you've found a security issue in our product or service, we encourage you to please submit a report through our [HackerOne Program](https://hackerone.com/bitwarden/). We welcome working with you to resolve the issue promptly. Thanks in advance!
# Disclosure Policy
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every
effort to quickly resolve the issue.
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a
third-party. We may publicly disclose the issue before resolving it, if appropriate.
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or
degradation of our service. Only interact with accounts you own or with explicit permission of the
account holder.
- If you would like to encrypt your report, please use the PGP key with long ID
`0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool).
# In-scope
- Security issues in any current release of Bitwarden. This includes the web vault, browser extension,
and mobile apps (iOS and Android). Product downloads are available at https://bitwarden.com. Source
code is available at https://github.com/bitwarden.
# Exclusions
The following bug classes are out-of scope:
- Bugs that are already reported on any of Bitwarden's issue trackers (https://github.com/bitwarden),
or that we already know of. Note that some of our issue tracking is private.
- Issues in an upstream software dependency (ex: Xamarin, ASP.NET) which are already reported to the
upstream maintainer.
- Attacks requiring physical access to a user's device.
- Self-XSS
- Issues related to software or protocols not under Bitwarden's control
- Vulnerabilities in outdated versions of Bitwarden
- Missing security best practices that do not directly lead to a vulnerability
- Issues that do not have any impact on the general public
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every effort to quickly resolve the issue.
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a third-party. We may publicly disclose the issue before resolving it, if appropriate.
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or degradation of our service. Only interact with accounts you own or with explicit permission of the account holder.
- If you would like to encrypt your report, please use the PGP key with long ID `0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool).
While researching, we'd like to ask you to refrain from:
@@ -42,4 +14,8 @@ While researching, we'd like to ask you to refrain from:
- Social engineering (including phishing) of Bitwarden staff or contractors
- Any physical attempts against Bitwarden property or data centers
# We want to help you!
If you have something that you feel is close to exploitation, or if you'd like some information regarding the internal API, or generally have any questions regarding the app that would help in your efforts, please email us at https://bitwarden.com/contact and ask for that information. As stated above, Bitwarden wants to help you find issues, and is more than willing to help.
Thank you for helping keep Bitwarden and our users safe!

View File

@@ -31,10 +31,7 @@ export class ApiActionDirective implements OnChanges {
(e: any) => {
this.el.nativeElement.loading = false;
if (
(e instanceof ErrorResponse || e.constructor.name === "ErrorResponse") &&
(e as ErrorResponse).captchaRequired
) {
if ((e as ErrorResponse).captchaRequired) {
this.logService.error("Captcha required error response: " + e.getSingleMessage());
return;
}

View File

@@ -0,0 +1,94 @@
import { CommonModule } from "@angular/common";
import { NgModule } from "@angular/core";
import { AvatarComponent } from "./components/avatar.component";
import { CalloutComponent } from "./components/callout.component";
import { ExportScopeCalloutComponent } from "./components/export-scope-callout.component";
import { IconComponent } from "./components/icon.component";
import { BitwardenToastModule } from "./components/toastr.component";
import { VerifyMasterPasswordComponent } from "./components/verify-master-password.component";
import { A11yInvalidDirective } from "./directives/a11y-invalid.directive";
import { A11yTitleDirective } from "./directives/a11y-title.directive";
import { ApiActionDirective } from "./directives/api-action.directive";
import { AutofocusDirective } from "./directives/autofocus.directive";
import { BlurClickDirective } from "./directives/blur-click.directive";
import { BoxRowDirective } from "./directives/box-row.directive";
import { FallbackSrcDirective } from "./directives/fallback-src.directive";
import { InputStripSpacesDirective } from "./directives/input-strip-spaces.directive";
import { InputVerbatimDirective } from "./directives/input-verbatim.directive";
import { NotPremiumDirective } from "./directives/not-premium.directive";
import { SelectCopyDirective } from "./directives/select-copy.directive";
import { StopClickDirective } from "./directives/stop-click.directive";
import { StopPropDirective } from "./directives/stop-prop.directive";
import { TrueFalseValueDirective } from "./directives/true-false-value.directive";
import { ColorPasswordPipe } from "./pipes/color-password.pipe";
import { I18nPipe } from "./pipes/i18n.pipe";
import { SearchCiphersPipe } from "./pipes/search-ciphers.pipe";
import { SearchPipe } from "./pipes/search.pipe";
import { UserNamePipe } from "./pipes/user-name.pipe";
@NgModule({
imports: [
BitwardenToastModule.forRoot({
maxOpened: 5,
autoDismiss: true,
closeButton: true,
}),
CommonModule,
],
declarations: [
A11yInvalidDirective,
A11yTitleDirective,
ApiActionDirective,
AvatarComponent,
AutofocusDirective,
BlurClickDirective,
BoxRowDirective,
ColorPasswordPipe,
FallbackSrcDirective,
I18nPipe,
InputStripSpacesDirective,
InputVerbatimDirective,
NotPremiumDirective,
SearchCiphersPipe,
SearchPipe,
SelectCopyDirective,
StopClickDirective,
StopPropDirective,
TrueFalseValueDirective,
UserNamePipe,
CalloutComponent,
IconComponent,
VerifyMasterPasswordComponent,
ExportScopeCalloutComponent,
],
exports: [
A11yInvalidDirective,
A11yTitleDirective,
ApiActionDirective,
AvatarComponent,
AutofocusDirective,
BitwardenToastModule,
BlurClickDirective,
BoxRowDirective,
ColorPasswordPipe,
FallbackSrcDirective,
I18nPipe,
InputStripSpacesDirective,
InputVerbatimDirective,
NotPremiumDirective,
SearchCiphersPipe,
SearchPipe,
SelectCopyDirective,
StopClickDirective,
StopPropDirective,
TrueFalseValueDirective,
UserNamePipe,
CalloutComponent,
IconComponent,
VerifyMasterPasswordComponent,
ExportScopeCalloutComponent,
],
providers: [UserNamePipe, SearchPipe],
})
export class JslibModule {}

View File

@@ -0,0 +1,17 @@
import { Pipe, PipeTransform } from "@angular/core";
@Pipe({
name: "ellipsis",
})
export class EllipsisPipe implements PipeTransform {
transform(value: string, limit = 25, completeWords = false, ellipsis = "...") {
if (value.length <= limit) {
return value;
}
limit -= ellipsis.length;
if (completeWords && value.length > limit && value.indexOf(" ") > 0) {
limit = value.substring(0, limit).lastIndexOf(" ");
}
return value.substring(0, limit) + ellipsis;
}
}

View File

@@ -1029,7 +1029,7 @@ export class CipherService implements CipherServiceAbstraction {
ciphers[c.id].revisionDate = c.revisionDate;
};
if (cipher.constructor.name === "Array") {
if (cipher.constructor.name === Array.name) {
(cipher as { id: string; revisionDate: string }[]).forEach(clearDeletedDate);
} else {
clearDeletedDate(cipher as { id: string; revisionDate: string });

View File

@@ -13,6 +13,7 @@ import { SendService } from "../abstractions/send.service";
import { SettingsService } from "../abstractions/settings.service";
import { StateService } from "../abstractions/state.service";
import { SyncService as SyncServiceAbstraction } from "../abstractions/sync.service";
import { sequentialize } from "../misc/sequentialize";
import { CipherData } from "../models/data/cipherData";
import { CollectionData } from "../models/data/collectionData";
import { FolderData } from "../models/data/folderData";
@@ -71,6 +72,7 @@ export class SyncService implements SyncServiceAbstraction {
await this.stateService.setLastSync(date.toJSON(), { userId: userId });
}
@sequentialize(() => "fullSync")
async fullSync(forceSync: boolean, allowThrowOnError = false): Promise<boolean> {
this.syncStarted();
const isAuthenticated = await this.stateService.getIsAuthenticated();

View File

@@ -1,6 +1,6 @@
module.exports = {
stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
addons: ["@storybook/addon-links", "@storybook/addon-essentials"],
addons: ["@storybook/addon-links", "@storybook/addon-essentials", "@storybook/addon-a11y"],
framework: "@storybook/angular",
core: {
builder: "webpack5",

View File

@@ -17,6 +17,7 @@
"@angular/platform-browser": "^12.2.13",
"@angular/platform-browser-dynamic": "^12.2.13",
"@bitwarden/jslib-angular": "file:../angular",
"bootstrap": "4.6.0",
"tslib": "^2.3.0"
},
"devDependencies": {
@@ -26,6 +27,7 @@
"@angular/elements": "^12.2.13",
"@babel/core": "^7.16.10",
"@compodoc/compodoc": "^1.1.16",
"@storybook/addon-a11y": "^6.4.19",
"@storybook/addon-actions": "^6.4.18",
"@storybook/addon-essentials": "^6.4.18",
"@storybook/addon-links": "^6.4.18",
@@ -37,6 +39,7 @@
"@webcomponents/custom-elements": "^1.5.0",
"autoprefixer": "^10.4.2",
"babel-loader": "^8.2.3",
"chromatic": "^6.5.2",
"jasmine-core": "~3.10.0",
"karma": "~6.3.0",
"karma-chrome-launcher": "~3.1.0",
@@ -4757,6 +4760,46 @@
"node": ">= 0.6.0"
}
},
"node_modules/@storybook/addon-a11y": {
"version": "6.4.19",
"resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-6.4.19.tgz",
"integrity": "sha512-dG6easap6W4AqyggVZPq8lBrhza8StA8J4eYz/GVdoXINSGtq/casV0rkmY3+SUXhPYux5oGavHo86j5I4Q/0Q==",
"dev": true,
"dependencies": {
"@storybook/addons": "6.4.19",
"@storybook/api": "6.4.19",
"@storybook/channels": "6.4.19",
"@storybook/client-logger": "6.4.19",
"@storybook/components": "6.4.19",
"@storybook/core-events": "6.4.19",
"@storybook/csf": "0.0.2--canary.87bc651.0",
"@storybook/theming": "6.4.19",
"axe-core": "^4.2.0",
"core-js": "^3.8.2",
"global": "^4.4.0",
"lodash": "^4.17.21",
"react-sizeme": "^3.0.1",
"regenerator-runtime": "^0.13.7",
"ts-dedent": "^2.0.0",
"util-deprecate": "^1.0.2"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/storybook"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0",
"react-dom": "^16.8.0 || ^17.0.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/@storybook/addon-actions": {
"version": "6.4.19",
"resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-6.4.19.tgz",
@@ -13487,6 +13530,15 @@
"postcss": "^8.1.0"
}
},
"node_modules/axe-core": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.1.tgz",
"integrity": "sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/babel-loader": {
"version": "8.2.3",
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz",
@@ -14023,6 +14075,19 @@
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
"dev": true
},
"node_modules/bootstrap": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz",
"integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
},
"peerDependencies": {
"jquery": "1.9.1 - 3",
"popper.js": "^1.16.1"
}
},
"node_modules/boxen": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz",
@@ -14694,6 +14759,17 @@
"node": ">=10"
}
},
"node_modules/chromatic": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/chromatic/-/chromatic-6.5.2.tgz",
"integrity": "sha512-TiAJAF2we4BUflKEfiXs2CiBFvW6yNWkiXKIuBtlSLl2fj1cuueXlV5dYVVoyMDfzTX5JdEn/Bd5CVlpKCeY6A==",
"dev": true,
"bin": {
"chroma": "bin/main.cjs",
"chromatic": "bin/main.cjs",
"chromatic-cli": "bin/main.cjs"
}
},
"node_modules/chrome-trace-event": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
@@ -21897,6 +21973,12 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/jquery": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
"integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==",
"peer": true
},
"node_modules/js-string-escape": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz",
@@ -25131,6 +25213,17 @@
"node": ">=6.9.0"
}
},
"node_modules/popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
"deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/portfinder": {
"version": "1.0.28",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
@@ -37713,6 +37806,30 @@
"integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ==",
"dev": true
},
"@storybook/addon-a11y": {
"version": "6.4.19",
"resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-6.4.19.tgz",
"integrity": "sha512-dG6easap6W4AqyggVZPq8lBrhza8StA8J4eYz/GVdoXINSGtq/casV0rkmY3+SUXhPYux5oGavHo86j5I4Q/0Q==",
"dev": true,
"requires": {
"@storybook/addons": "6.4.19",
"@storybook/api": "6.4.19",
"@storybook/channels": "6.4.19",
"@storybook/client-logger": "6.4.19",
"@storybook/components": "6.4.19",
"@storybook/core-events": "6.4.19",
"@storybook/csf": "0.0.2--canary.87bc651.0",
"@storybook/theming": "6.4.19",
"axe-core": "^4.2.0",
"core-js": "^3.8.2",
"global": "^4.4.0",
"lodash": "^4.17.21",
"react-sizeme": "^3.0.1",
"regenerator-runtime": "^0.13.7",
"ts-dedent": "^2.0.0",
"util-deprecate": "^1.0.2"
}
},
"@storybook/addon-actions": {
"version": "6.4.19",
"resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-6.4.19.tgz",
@@ -44584,6 +44701,12 @@
"postcss-value-parser": "^4.2.0"
}
},
"axe-core": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.1.tgz",
"integrity": "sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw==",
"dev": true
},
"babel-loader": {
"version": "8.2.3",
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz",
@@ -45025,6 +45148,12 @@
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
"dev": true
},
"bootstrap": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz",
"integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==",
"requires": {}
},
"boxen": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz",
@@ -45548,6 +45677,12 @@
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
"dev": true
},
"chromatic": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/chromatic/-/chromatic-6.5.2.tgz",
"integrity": "sha512-TiAJAF2we4BUflKEfiXs2CiBFvW6yNWkiXKIuBtlSLl2fj1cuueXlV5dYVVoyMDfzTX5JdEn/Bd5CVlpKCeY6A==",
"dev": true
},
"chrome-trace-event": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
@@ -51165,6 +51300,12 @@
}
}
},
"jquery": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
"integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==",
"peer": true
},
"js-string-escape": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz",
@@ -53720,6 +53861,12 @@
}
}
},
"popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
"peer": true
},
"portfinder": {
"version": "1.0.28",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",

View File

@@ -9,7 +9,8 @@
"test": "ng test",
"docs:json": "compodoc -p ./tsconfig.json -e json -d .",
"storybook": "npm run docs:json && start-storybook -p 6006",
"build-storybook": "npm run docs:json && build-storybook"
"build-storybook": "npm run docs:json && build-storybook",
"chromatic": "chromatic --exit-zero-on-changes"
},
"private": true,
"dependencies": {
@@ -22,6 +23,7 @@
"@angular/platform-browser": "^12.2.13",
"@angular/platform-browser-dynamic": "^12.2.13",
"@bitwarden/jslib-angular": "file:../angular",
"bootstrap": "4.6.0",
"tslib": "^2.3.0"
},
"devDependencies": {
@@ -31,6 +33,7 @@
"@angular/elements": "^12.2.13",
"@babel/core": "^7.16.10",
"@compodoc/compodoc": "^1.1.16",
"@storybook/addon-a11y": "^6.4.19",
"@storybook/addon-actions": "^6.4.18",
"@storybook/addon-essentials": "^6.4.18",
"@storybook/addon-links": "^6.4.18",
@@ -42,6 +45,7 @@
"@webcomponents/custom-elements": "^1.5.0",
"autoprefixer": "^10.4.2",
"babel-loader": "^8.2.3",
"chromatic": "^6.5.2",
"jasmine-core": "~3.10.0",
"karma": "~6.3.0",
"karma-chrome-launcher": "~3.1.0",

View File

@@ -1,39 +1,68 @@
import { Component, Input } from "@angular/core";
import { Directive, ElementRef, HostBinding, Input, OnChanges, OnInit } from "@angular/core";
type BadgeTypes = "primary" | "secondary" | "success" | "danger" | "warning" | "info";
const styles: Record<BadgeTypes, string[]> = {
primary: ["tw-bg-primary-500", "hover:tw-bg-primary-700"],
secondary: ["tw-bg-secondary-500", "hover:tw-bg-secondary-700"],
success: ["tw-bg-success-500", "hover:tw-bg-success-700"],
danger: ["tw-bg-danger-500", "hover:tw-bg-danger-700"],
warning: ["tw-bg-warning-500", "hover:tw-bg-warning-700"],
info: ["tw-bg-info-500", "hover:tw-bg-info-700"],
primary: ["tw-bg-primary-500"],
secondary: ["tw-bg-text-muted"],
success: ["tw-bg-success-500"],
danger: ["tw-bg-danger-500"],
warning: ["tw-bg-warning-500"],
info: ["tw-bg-info-500"],
};
@Component({
selector: "bit-badge",
template: `<span [ngClass]="classes"><ng-content></ng-content></span>`,
const hoverStyles: Record<BadgeTypes, string[]> = {
primary: ["hover:tw-bg-primary-700"],
secondary: ["hover:tw-bg-secondary-700"],
success: ["hover:tw-bg-success-700"],
danger: ["hover:tw-bg-danger-700"],
warning: ["hover:tw-bg-warning-700"],
info: ["hover:tw-bg-info-700"],
};
@Directive({
selector: "span[bit-badge], a[bit-badge], button[bit-badge]",
})
export class BadgeComponent {
@Input()
type: BadgeTypes = "primary";
export class BadgeComponent implements OnInit, OnChanges {
@HostBinding("class") @Input("class") classList = "";
@Input() badgeType: BadgeTypes = "primary";
private isSpan = false;
constructor(private el: ElementRef<Element>) {
this.isSpan = el?.nativeElement?.nodeName == "SPAN";
}
ngOnInit(): void {
this.classList = this.classes.join(" ");
}
ngOnChanges() {
this.ngOnInit();
}
get classes() {
return [
"tw-inline-block",
"tw-py-0.5",
"tw-px-1",
"tw-py-1",
"tw-px-1.5",
"tw-font-bold",
"tw-leading-none",
"tw-text-center",
"tw-text-contrast",
"tw-align-baseline",
"!tw-text-contrast",
"tw-rounded",
"tw-border-collapse",
"tw-border-none",
"tw-box-border",
"tw-whitespace-no-wrap",
"tw-text-xs",
].concat(styles[this.type]);
"hover:tw-no-underline",
"focus:tw-outline-none",
"focus:tw-ring",
"focus:tw-ring-offset-2",
"focus:tw-ring-primary-700",
]
.concat(styles[this.badgeType])
.concat(this.isSpan ? [] : hoverStyles[this.badgeType]);
}
}

View File

@@ -13,7 +13,11 @@ export default {
const Template: Story<BadgeComponent> = (args: BadgeComponent) => ({
props: args,
template: `
<span class="tw-text-main">Test </span><bit-badge [type]="type">Content</bit-badge>
<span class="tw-text-main">Span </span><span bit-badge [badgeType]="type">Badge</span>
<br><br>
<span class="tw-text-main">Link </span><a href="#" bit-badge [badgeType]="type">Badge</a>
<br><br>
<span class="tw-text-main">Button </span><button bit-badge [badgeType]="type">Badge</button>
`,
});

View File

@@ -1,4 +1,4 @@
import { Input, HostBinding, OnChanges, Directive } from "@angular/core";
import { Input, HostBinding, OnChanges, Directive, OnInit } from "@angular/core";
export type ButtonTypes = "primary" | "secondary" | "danger";
@@ -18,10 +18,10 @@ const buttonStyles: Record<ButtonTypes, string> = {
"!tw-text-muted",
"hover:tw-bg-secondary-500",
"hover:tw-border-secondary-500",
"hover:tw-text-contrast",
"hover:!tw-text-contrast",
"focus:tw-bg-secondary-500",
"focus:tw-border-secondary-500",
"focus:tw-text-contrast",
"focus:!tw-text-contrast",
].join(" "),
danger: [
"tw-bg-transparent",
@@ -29,18 +29,18 @@ const buttonStyles: Record<ButtonTypes, string> = {
"!tw-text-danger",
"hover:tw-bg-danger-500",
"hover:tw-border-danger-500",
"hover:tw-text-contrast",
"hover:!tw-text-contrast",
"focus:tw-bg-danger-500",
"focus:tw-border-danger-500",
"focus:tw-text-contrast",
"focus:!tw-text-contrast",
].join(" "),
};
@Directive({
selector: "button[bit-button], a[bit-button]",
})
export class ButtonComponent implements OnChanges {
@HostBinding("class") @Input("class") classList = "";
export class ButtonComponent implements OnInit, OnChanges {
@HostBinding("class") @Input() classList = "";
@Input()
buttonType: ButtonTypes = "secondary";
@@ -48,10 +48,14 @@ export class ButtonComponent implements OnChanges {
@Input()
block = false;
ngOnChanges() {
ngOnInit(): void {
this.classList = this.classes.join(" ");
}
ngOnChanges() {
this.ngOnInit();
}
get classes(): string[] {
return [
"tw-font-semibold",

View File

@@ -15,7 +15,10 @@ export default {
// More on component templates: https://storybook.js.org/docs/angular/writing-stories/introduction#using-args
const Template: Story<ButtonComponent> = (args: ButtonComponent) => ({
props: args,
template: `<button bit-button [buttonType]="buttonType" [block]="block">Test</button>`,
template: `
<button bit-button [buttonType]="buttonType" [block]="block">Button</button>
<a bit-button [buttonType]="buttonType" [block]="block" href="#" class="tw-ml-2">Link</a>
`,
});
export const Primary = Template.bind({});

View File

@@ -1,5 +1,5 @@
<div
class="tw-py-3 tw-px-5 tw-mb-4 tw-leading-5 tw-rounded tw-bg-background-elevation tw-border tw-border-secondary-300 tw-border-solid tw-box-border tw-border-l-8 tw-text-main"
class="tw-py-3 tw-px-5 tw-mb-4 tw-leading-5 tw-rounded tw-bg-background-alt tw-border tw-border-secondary-300 tw-border-solid tw-box-border tw-border-l-8 tw-text-main"
[ngClass]="calloutClass"
>
<h3

View File

@@ -1,6 +1,6 @@
import { Meta } from "@storybook/addon-docs";
<Meta title="Jslib/Introduction" />
<Meta title="Common/Introduction" />
<style>{`
.subheading {
@@ -77,36 +77,9 @@ import { Meta } from "@storybook/addon-docs";
font-size: 14px;
line-height: 20px;
}
.tip {
display: inline-block;
border-radius: 1em;
font-size: 11px;
line-height: 12px;
font-weight: 700;
background: #E7FDD8;
color: #66BF3C;
padding: 4px 12px;
margin-right: 10px;
vertical-align: top;
}
.tip-wrapper {
font-size: 13px;
line-height: 20px;
margin-top: 40px;
margin-bottom: 40px;
}
.tip-wrapper code {
font-size: 12px;
display: inline-block;
}
`}</style>
# Welcome to Storybook
# Bitwarden Component Library
Storybook helps you build UI components in isolation from your app's business logic, data, and context.
That makes it easy to develop hard-to-reach states. Save these UI states as **stories** to revisit during development, testing, or QA.
@@ -188,8 +161,3 @@ We recommend building UIs with a [**component-driven**](https://componentdriven.
</span>
</a>
</div>
<div className="tip-wrapper">
<span className="tip">Tip</span>Edit the Markdown in{" "}
<code>src/stories/Introduction.stories.mdx</code>
</div>

View File

@@ -0,0 +1,85 @@
import { Meta } from "@storybook/addon-docs";
<Meta title="Common/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>
);
export const Table = (args) => (
<table class={"tw-table-auto border !tw-text-main " + args.class}>
<thead>
<tr>
<th>General usage</th>
<th class="tw-w-20"></th>
</tr>
</thead>
<tbody>
{Row("background")}
{Row("background-alt")}
{Row("background-alt2")}
</tbody>
<tbody>
{Row("primary-300")}
{Row("primary-500")}
{Row("primary-700")}
</tbody>
<tbody>
{Row("secondary-100")}
{Row("secondary-300")}
{Row("secondary-500")}
{Row("secondary-700")}
</tbody>
<tbody>
{Row("success-500")}
{Row("success-700")}
</tbody>
<tbody>
{Row("danger-500")}
{Row("danger-700")}
</tbody>
<tbody>
{Row("warning-500")}
{Row("warning-700")}
</tbody>
<tbody>
{Row("info-500")}
{Row("info-700")}
</tbody>
<tbody>
{Row("text-main")}
{Row("text-muted")}
{Row("text-contrast")}
</tbody>
</table>
);
<style>{`
table {
border-spacing: 0.5rem;
border-collapse: separate !important;
}
tr {
background: none !important;
border: none !important;
}
td, th {
color: inherit !important;
}
th {
border: none !important;
}
`}</style>
# Colors
<div class="tw-flex tw-space-x-4">
<Table />
<Table class="theme_dark tw-bg-background" />
</div>

View File

@@ -1,5 +1,5 @@
@import "../../angular/src/scss/webfonts.css";
@import "./../../../src/scss/variables";
@import "./variables";
@import "../../angular/src/scss/bwicons/styles/style.scss";
@import "../../angular/src/scss/icons.scss";

View File

@@ -1,6 +1,7 @@
:root {
--color-background: #ffffff;
--color-background-elevation: #fbfbfb;
--color-background-alt: #fbfbfb;
--color-background-alt2: #175ddc;
--color-primary-300: #6795e8;
--color-primary-500: #175ddc;
@@ -12,21 +13,22 @@
--color-secondary-700: #212529;
--color-success-500: #017e45;
--color-success-700: #003f23;
--color-success-700: #00552e;
--color-danger-500: #c83522;
--color-danger-700: #641a11;
--color-danger-700: #98291b;
--color-warning-500: #8b6609;
--color-warning-700: #463304;
--color-warning-700: #694d05;
--color-info-500: #555555;
--color-info-700: #2b2b2b;
--color-info-700: #3b3a3a;
--color-text-main: #212529;
--color-text-muted: #6d757e;
--color-text-contrast: #ffffff;
--tw-ring-offset-color: #1f242e;
--tw-ring-offset-color: #fff;
}
.theme_light {
@@ -35,7 +37,8 @@
.theme_dark {
--color-background: #1f242e;
--color-background-elevation: #161c26;
--color-background-alt: #161c26;
--color-background-alt2: #2f343d;
--color-primary-300: #175ddc;
--color-primary-500: #6a99f0;
@@ -61,4 +64,6 @@
--color-text-main: #ffffff;
--color-text-muted: #bac0ce;
--color-text-contrast: #191e26;
--tw-ring-offset-color: #1f242e;
}

View File

@@ -0,0 +1,357 @@
$dark-icon-themes: "theme_dark";
$primary: #175ddc;
$primary-accent: #1252a3;
$secondary: #ced4da;
$secondary-alt: #1a3b66;
$success: #00a65a;
$info: #555555;
$warning: #bf7e16;
$danger: #dd4b39;
$white: #ffffff;
// Bootstrap Variable Overrides
$theme-colors: (
"primary-accent": $primary-accent,
"secondary-alt": $secondary-alt,
);
$body-bg: $white;
$body-color: #333333;
$font-family-sans-serif: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
$h1-font-size: 1.7rem;
$h2-font-size: 1.3rem;
$h3-font-size: 1rem;
$h4-font-size: 1rem;
$h5-font-size: 1rem;
$h6-font-size: 1rem;
$small-font-size: 90%;
$font-size-lg: 1.15rem;
$code-font-size: 100%;
$navbar-padding-y: 0.75rem;
$grid-gutter-width: 20px;
$card-spacer-y: 0.6rem;
$list-group-item-padding-y: 0.6rem;
$list-group-active-color: $body-color;
$list-group-active-bg: $white;
$list-group-active-border-color: rgba(#000000, 0.125);
$dropdown-link-color: $body-color;
$dropdown-link-hover-bg: rgba(#000000, 0.06);
$dropdown-link-active-color: $dropdown-link-color;
$dropdown-link-active-bg: rgba(#000000, 0.1);
$dropdown-item-padding-x: 1rem;
$navbar-brand-font-size: 35px;
$navbar-brand-height: 35px;
$navbar-brand-padding-y: 0;
$navbar-dark-color: rgba($white, 0.7);
$navbar-dark-hover-color: rgba($white, 0.9);
$navbar-nav-link-padding-x: 0.8rem;
$input-bg: #fbfbfb;
$input-focus-bg: $white;
$input-disabled-bg: #e0e0e0;
$input-placeholder-color: #b4b4b4;
$table-accent-bg: rgba(#000000, 0.02);
$table-hover-bg: rgba(#000000, 0.03);
$modal-backdrop-opacity: 0.3;
$btn-font-weight: 600;
$lead-font-weight: normal;
$grid-breakpoints: (
xs: 0,
sm: 1px,
md: 2px,
lg: 3px,
xl: 4px,
);
$border-color: $secondary;
// MFA Types for logo styling with no dark theme alternative
$mfaTypes: 0, 2, 3, 4, 6;
// Theme Variables
// Light
$lightDangerHover: #c43421;
$lightInputColor: #465057;
$lightInputPlaceholderColor: #b6b8b8;
// Dark
$darkPrimary: #6a99f0;
$darkPrimary-alt: #b4ccf9;
$darkDanger: #ff8d85;
$darkDangerHover: #ffbfbb;
$darkSuccess: #52e07c;
$darkWarning: #ffeb66;
$darkInfo: #a4b0c6;
$darkLinks: #6a99f0;
$darkGrey1: #bac0ce;
$darkGrey2: #8d94a5;
$darkBlue1: #4c525f;
$darkBlue2: #3c424e;
$darkDarkBlue1: #2f343d;
$darkDarkBlue2: #1f242e;
$darkInputColor: $white;
$darkInputPlaceholderColor: $darkGrey1;
$themes: (
light: (
primary: $primary,
primaryAlt: $primary-accent,
danger: $danger,
info: #343a40,
success: $success,
warning: $warning,
backgroundColor: $white,
badgeDangerBackground: $danger,
badgeDangerText: $white,
badgeInfoBackground: #555555,
badgeInfoText: $white,
badgePrimaryBackground: $primary,
badgePrimaryBackgroundHover: #134eb9,
badgePrimaryText: $white,
badgeSecondaryBackground: #ced4da,
badgeSecondaryText: #212529,
bgLightColor: #f8f9fa,
bgPrimaryColor: $primary,
borderColor: $border-color,
borderPrimaryColor: $primary,
browserInputIconsFilter: invert(0),
btnDanger: $danger,
btnDangerHover: $lightDangerHover,
btnDangerText: $white,
btnLinkText: $primary,
btnLinkTextHover: #104097,
btnOutlineDangerBackground: $input-bg,
btnOutlineDangerBackgroundHover: $danger,
btnOutlineDangerBorder: #ced4da,
btnOutlineDangerBorderHover: $danger,
btnOutlineDangerText: $danger,
btnOutlineDangerTextHover: $white,
btnOutlinePrimaryBackground: $input-bg,
btnOutlinePrimaryBackgroundHover: $primary,
btnOutlinePrimaryBorder: #ced4da,
btnOutlinePrimaryBorderHover: $primary,
btnOutlinePrimaryText: $primary,
btnOutlinePrimaryTextHover: $white,
btnOutlineSecondaryBackground: $input-bg,
btnOutlineSecondaryBackgroundHover: #ced4da,
btnOutlineSecondaryBorder: #ced4da,
btnOutlineSecondaryBorderHover: #ced4da,
btnOutlineSecondaryText: #6c757d,
btnOutlineSecondaryTextHover: #333333,
btnPrimary: $primary,
btnPrimaryBorderHover: #1249ae,
btnPrimaryHover: #134eb9,
btnPrimaryText: $white,
btnSecondary: $secondary,
btnSecondaryBorder: $secondary,
btnSecondaryBorderHover: #b1bbc4,
btnSecondaryHover: #b8c1ca,
btnSecondaryText: #212529,
btnSecondaryTextHover: #212529,
calloutBackground: #fafafa,
calloutColor: #212529,
cdkDraggingBackground: $white,
codeColor: #e83e8c,
dropdownBackground: $white,
dropdownHover: rgba(0, 0, 0, 0.06),
dropdownTextColor: $body-color,
dropdownTextMuted: #6c757d,
focus: rgb(23 93 220 / 25%),
footerBackgroundColor: #fbfbfb,
foregroundColor: $white,
headerColor: rgba(0, 0, 0, 0.03),
iconColor: #777777,
iconHover: $body-color,
imgFilter: invert(0) grayscale(0),
inputBackgroundColor: $input-bg,
inputBorderColor: $border-color,
inputDisabledBackground: #e0e0e0,
inputDisabledColor: #6c757d,
inputPlaceholderColor: $lightInputPlaceholderColor,
inputTextColor: $lightInputColor,
layoutFrontendColor: #ecf0f5,
learnMoreHover: #104097,
linkColor: $primary,
linkColorHover: #104097,
linkWeight: 400,
listItemActive: $body-color,
listItemBorder: rgba(0, 0, 0, 0.125),
loadingSvg: url("../images/loading.svg"),
logoSuffix: "dark",
mfaLogoSuffix: ".png",
navActiveBackground: $white,
navActiveWeight: 600,
navBackground: $primary,
navBackgroundAlt: $secondary-alt,
navOrgBackgroundColor: #fbfbfb,
navWeight: 600,
pwLetter: $body-color,
pwNumber: #007fde,
pwSpecial: #c40800,
pwStrengthBackground: #e9ecef,
separator: $secondary,
separatorHr: rgb(0, 0, 0, 0.1),
tableColorHover: #333333,
tableLinkColor: $primary,
tableLinkColorHover: #104097,
tableRowHover: rgba(0, 0, 0, 0.03),
tableSeparator: #dee2e6,
textColor: $body-color,
textDangerColor: $white,
textInfoColor: $white,
textHeadingColor: #333333,
textMuted: #6c757d,
textSuccessColor: $white,
textWarningColor: $white,
),
dark: (
primary: $darkPrimary,
primaryAlt: $darkPrimary-alt,
danger: $darkDanger,
info: $darkInfo,
success: $darkSuccess,
warning: $darkWarning,
backgroundColor: $darkDarkBlue2,
badgeDangerBackground: $darkDanger,
badgeDangerText: $darkDarkBlue2,
badgeInfoBackground: $darkInfo,
badgeInfoText: $darkDarkBlue2,
badgePrimaryBackground: $darkLinks,
badgePrimaryBackgroundHover: $darkPrimary-alt,
badgePrimaryText: $darkDarkBlue2,
badgeSecondaryBackground: $darkGrey2,
badgeSecondaryText: $darkDarkBlue2,
bgLightColor: $darkDarkBlue2,
bgPrimaryColor: $darkPrimary,
borderColor: $darkBlue1,
borderPrimaryColor: $darkPrimary,
browserInputIconsFilter: invert(1),
btnDanger: $darkDanger,
btnDangerHover: $darkDangerHover,
btnDangerText: $darkDarkBlue2,
btnLinkText: $white,
btnLinkTextHover: $darkGrey1,
btnOutlineDangerBackground: $darkDanger,
btnOutlineDangerBackgroundHover: $darkDangerHover,
btnOutlineDangerBorder: $darkDanger,
btnOutlineDangerBorderHover: $darkDangerHover,
btnOutlineDangerText: $darkDarkBlue2,
btnOutlineDangerTextHover: $darkDarkBlue2,
btnOutlinePrimaryBackground: $darkPrimary,
btnOutlinePrimaryBackgroundHover: $darkPrimary-alt,
btnOutlinePrimaryBorder: $darkPrimary,
btnOutlinePrimaryBorderHover: $darkPrimary-alt,
btnOutlinePrimaryText: $darkDarkBlue2,
btnOutlinePrimaryTextHover: $darkDarkBlue2,
btnOutlineSecondaryBackground: transparent,
btnOutlineSecondaryBackgroundHover: transparent,
btnOutlineSecondaryBorder: $darkGrey1,
btnOutlineSecondaryBorderHover: $darkGrey2,
btnOutlineSecondaryText: $white,
btnOutlineSecondaryTextHover: $darkGrey2,
btnPrimary: $darkLinks,
btnPrimaryBorderHover: $darkPrimary-alt,
btnPrimaryHover: $darkPrimary-alt,
btnPrimaryText: $darkDarkBlue2,
btnSecondary: transparent,
btnSecondaryBorder: $darkGrey1,
btnSecondaryBorderHover: $darkGrey2,
btnSecondaryHover: transparent,
btnSecondaryText: $white,
btnSecondaryTextHover: $darkGrey2,
calloutBackground: $darkBlue2,
calloutColor: $white,
cdkDraggingBackground: $darkDarkBlue1,
codeColor: #e83e8c,
dropdownBackground: $darkDarkBlue1,
dropdownHover: rgba(255, 255, 255, 0.03),
dropdownTextColor: $white,
dropdownTextMuted: #bec6cf,
focus: rgb(106 153 240 / 25%),
footerBackgroundColor: $darkBlue1,
foregroundColor: $darkDarkBlue1,
headerColor: $darkBlue1,
iconColor: #777777,
iconHover: $darkGrey2,
imgFilter: invert(1) grayscale(1),
inputBackgroundColor: transparent,
inputBorderColor: $darkGrey1,
inputDisabledBackground: $darkBlue2,
inputDisabledColor: $darkGrey1,
inputPlaceholderColor: $darkInputPlaceholderColor,
inputTextColor: $darkInputColor,
layoutFrontendColor: $darkDarkBlue2,
learnMoreHover: $darkPrimary-alt,
linkColor: $darkLinks,
linkColorHover: $darkLinks,
linkWeight: 600,
listItemActive: $darkPrimary,
listItemBorder: $darkBlue1,
loadingSvg: url("../images/loading-white.svg"),
logoSuffix: "white",
mfaLogoSuffix: "-w.png",
navActiveBackground: $darkDarkBlue2,
navActiveWeight: 600,
navBackground: $darkDarkBlue1,
navBackgroundAlt: $darkDarkBlue1,
navOrgBackgroundColor: #161c26,
navWeight: 400,
pwLetter: $white,
pwNumber: #52bdfb,
pwSpecial: #ff7c70,
pwStrengthBackground: $darkBlue2,
separator: $darkBlue1,
separatorHr: $darkBlue1,
tableColorHover: $darkGrey1,
tableLinkColor: $white,
tableLinkColorHover: $white,
tableRowHover: rgba(255, 255, 255, 0.03),
tableSeparator: $darkBlue1,
textColor: $darkGrey1,
textDangerColor: $darkDarkBlue2,
textHeadingColor: $white,
textInfoColor: $darkDarkBlue2,
textMuted: $darkGrey1,
textSuccessColor: $darkDarkBlue2,
textWarningColor: $darkDarkBlue2,
),
);
@mixin themify($themes: $themes) {
@each $theme, $map in $themes {
html.theme_#{$theme} & {
$theme-map: () !global;
@each $key, $submap in $map {
$value: map-get(map-get($themes, $theme), "#{$key}");
$theme-map: map-merge(
$theme-map,
(
$key: $value,
)
) !global;
}
@content;
$theme-map: null !global;
}
}
}
@function themed($key) {
@return map-get($theme-map, $key);
} ;

View File

@@ -37,9 +37,16 @@ module.exports = {
500: "var(--color-info-500)",
700: "var(--color-info-700)",
},
"text-muted": "var(--color-text-muted)",
background: "var(--color-background)",
"background-elevation": "var(--color-background-elevation)",
text: {
main: "var(--color-text-main)",
muted: "var(--color-text-muted)",
contrast: "var(--color-text-contrast)",
},
background: {
DEFAULT: "var(--color-background)",
alt: "var(--color-background-alt)",
alt2: "var(--color-background-alt2)",
},
},
textColor: {
main: "var(--color-text-main)",
@@ -49,6 +56,9 @@ module.exports = {
danger: "var(--color-danger-500)",
warning: "var(--color-warning-500)",
info: "var(--color-info-500)",
primary: {
300: "var(--color-primary-300)",
},
},
ringOffsetColor: ({ theme }) => ({
DEFAULT: theme("colors.background"),

View File

@@ -1,6 +1,11 @@
/* eslint-disable */
const config = require("./tailwind.config.base");
config.content = ["./src/**/*.{html,ts}", "./.storybook/preview.js"];
config.content = ["./src/**/*.{html,ts,mdx}", "./.storybook/preview.js"];
config.safelist = [
{
pattern: /tw-bg-(.*)/,
},
];
module.exports = config;

View File

@@ -467,7 +467,7 @@ export class LoginCommand {
} catch (e) {
if (
e instanceof ErrorResponse ||
(e.constructor.name === "ErrorResponse" &&
(e.constructor.name === ErrorResponse.name &&
(e as ErrorResponse).message.includes("Captcha is invalid"))
) {
return badCaptcha;