diff --git a/web/package-lock.json b/web/package-lock.json
index 5ec58978c1..1d61b57cd4 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -47,23 +47,18 @@
"yaml": "^2.4.2"
},
"devDependencies": {
- "@babel/core": "^7.24.5",
- "@babel/plugin-proposal-class-properties": "^7.18.6",
- "@babel/plugin-proposal-decorators": "^7.24.1",
- "@babel/plugin-transform-private-methods": "^7.24.1",
- "@babel/plugin-transform-private-property-in-object": "^7.24.5",
- "@babel/plugin-transform-runtime": "^7.24.3",
- "@babel/preset-env": "^7.24.5",
- "@babel/preset-typescript": "^7.24.1",
"@hcaptcha/types": "^1.0.3",
"@jeysal/storybook-addon-css-user-preferences": "^0.2.0",
"@lit/localize-tools": "^0.7.2",
"@rollup/plugin-replace": "^5.0.5",
"@spotlightjs/spotlight": "^1.2.17",
+ "@storybook/addon-controls": "^8.1.1",
+ "@storybook/addon-docs": "^8.1.1",
"@storybook/addon-essentials": "^8.1.1",
"@storybook/addon-links": "^8.1.1",
"@storybook/api": "^7.6.17",
"@storybook/blocks": "^8.0.8",
+ "@storybook/web-components-vite": "^8.1.1",
"@testing-library/webdriverio": "^3.2.1",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/chart.js": "^2.9.41",
@@ -77,8 +72,6 @@
"@wdio/cli": "^8.36.1",
"@wdio/mocha-framework": "^8.36.1",
"@wdio/spec-reporter": "^8.36.1",
- "babel-plugin-macros": "^3.1.0",
- "babel-plugin-tsconfig-paths": "^1.0.3",
"chokidar": "^3.6.0",
"cross-env": "^7.0.3",
"esbuild": "^0.21.3",
@@ -92,10 +85,9 @@
"glob": "^10.3.15",
"lit-analyzer": "^2.0.3",
"npm-run-all": "^4.1.5",
+ "polished": "^4.3.1",
"prettier": "^3.2.5",
"pseudolocale": "^2.0.0",
- "react": "^18.2.0",
- "react-dom": "^18.3.1",
"rollup-plugin-modify": "^3.0.0",
"rollup-plugin-postcss-lit": "^2.1.0",
"storybook": "^8.1.1",
@@ -626,23 +618,6 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-proposal-decorators": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.24.1.tgz",
- "integrity": "sha512-zPEvzFijn+hRvJuX2Vu3KbEBN39LN3f7tW3MQO2LsIs57B26KU+kUc82BdAktS1VCM6libzh45eKGI65lg0cpA==",
- "dev": true,
- "dependencies": {
- "@babel/helper-create-class-features-plugin": "^7.24.1",
- "@babel/helper-plugin-utils": "^7.24.0",
- "@babel/plugin-syntax-decorators": "^7.24.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
"node_modules/@babel/plugin-proposal-private-property-in-object": {
"version": "7.21.0-placeholder-for-preset-env.2",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
@@ -694,21 +669,6 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-syntax-decorators": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.24.1.tgz",
- "integrity": "sha512-05RJdO/cCrtVWuAaSn1tS3bH8jbsJa/Y1uD186u6J4C/1mnHFxseeuWpsqr9anvo7TUulev7tm7GDwRV+VuhDw==",
- "dev": true,
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.24.0"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
"node_modules/@babel/plugin-syntax-dynamic-import": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz",
@@ -1598,26 +1558,6 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-runtime": {
- "version": "7.24.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.3.tgz",
- "integrity": "sha512-J0BuRPNlNqlMTRJ72eVptpt9VcInbxO6iP3jaxr+1NPhC0UkKL+6oeX6VXMEYdADnuqmMmsBspt4d5w8Y/TCbQ==",
- "dev": true,
- "dependencies": {
- "@babel/helper-module-imports": "^7.24.3",
- "@babel/helper-plugin-utils": "^7.24.0",
- "babel-plugin-polyfill-corejs2": "^0.4.10",
- "babel-plugin-polyfill-corejs3": "^0.10.1",
- "babel-plugin-polyfill-regenerator": "^0.6.1",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
"node_modules/@babel/plugin-transform-shorthand-properties": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz",
@@ -5739,6 +5679,151 @@
"node": ">=14.14"
}
},
+ "node_modules/@storybook/builder-vite": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-8.1.1.tgz",
+ "integrity": "sha512-+BSmXuZ9j95oKCvHcKztzjZNzBVeXYMoRO2TuflLnknMUA0v9ySp1PhiQxHM4DgAW6t9db1akzc9HoTA5sjTWg==",
+ "dev": true,
+ "dependencies": {
+ "@storybook/channels": "8.1.1",
+ "@storybook/client-logger": "8.1.1",
+ "@storybook/core-common": "8.1.1",
+ "@storybook/core-events": "8.1.1",
+ "@storybook/csf-plugin": "8.1.1",
+ "@storybook/node-logger": "8.1.1",
+ "@storybook/preview": "8.1.1",
+ "@storybook/preview-api": "8.1.1",
+ "@storybook/types": "8.1.1",
+ "@types/find-cache-dir": "^3.2.1",
+ "browser-assert": "^1.2.1",
+ "es-module-lexer": "^1.5.0",
+ "express": "^4.17.3",
+ "find-cache-dir": "^3.0.0",
+ "fs-extra": "^11.1.0",
+ "magic-string": "^0.30.0",
+ "ts-dedent": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ },
+ "peerDependencies": {
+ "@preact/preset-vite": "*",
+ "typescript": ">= 4.3.x",
+ "vite": "^4.0.0 || ^5.0.0",
+ "vite-plugin-glimmerx": "*"
+ },
+ "peerDependenciesMeta": {
+ "@preact/preset-vite": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ },
+ "vite-plugin-glimmerx": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@storybook/builder-vite/node_modules/@storybook/channels": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-8.1.1.tgz",
+ "integrity": "sha512-vG7y97QB++TRkuxYLNKaWJmgr9QBUHyjQgNCWvHIeSYW5zxum9sm6VSR2j1r2G3XUGFSxDwenYBTQuwZJLhWNQ==",
+ "dev": true,
+ "dependencies": {
+ "@storybook/client-logger": "8.1.1",
+ "@storybook/core-events": "8.1.1",
+ "@storybook/global": "^5.0.0",
+ "telejson": "^7.2.0",
+ "tiny-invariant": "^1.3.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ }
+ },
+ "node_modules/@storybook/builder-vite/node_modules/@storybook/client-logger": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-8.1.1.tgz",
+ "integrity": "sha512-9AWPgIN3K0eLusChJUqB5Ft+9P2pW5/s4vOMoj3TCvu8lrdq8AH8ctvxk7x2Kw2wEwQ/g9DyE6C/rDQUARbxew==",
+ "dev": true,
+ "dependencies": {
+ "@storybook/global": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ }
+ },
+ "node_modules/@storybook/builder-vite/node_modules/@storybook/core-events": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-8.1.1.tgz",
+ "integrity": "sha512-WpeiBV6RWTZ6t8SI1YdQh8NlbvQtZs9WRr4CPfpzHAly+oxFy6PtPz0h5TMKsU5/kt/L9yL7tE9ZzPYzvFWH/A==",
+ "dev": true,
+ "dependencies": {
+ "@storybook/csf": "^0.1.7",
+ "ts-dedent": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ }
+ },
+ "node_modules/@storybook/builder-vite/node_modules/@storybook/preview-api": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.1.1.tgz",
+ "integrity": "sha512-5EcByqtJgj7a7ZWICMLif8mK3cRmdIMbdSPEDf4X6aTQ8LZOg6updLrkb/Eh6qfeYv46TK/MP8BXa89wfOxWGQ==",
+ "dev": true,
+ "dependencies": {
+ "@storybook/channels": "8.1.1",
+ "@storybook/client-logger": "8.1.1",
+ "@storybook/core-events": "8.1.1",
+ "@storybook/csf": "^0.1.7",
+ "@storybook/global": "^5.0.0",
+ "@storybook/types": "8.1.1",
+ "@types/qs": "^6.9.5",
+ "dequal": "^2.0.2",
+ "lodash": "^4.17.21",
+ "memoizerific": "^1.11.3",
+ "qs": "^6.10.0",
+ "tiny-invariant": "^1.3.1",
+ "ts-dedent": "^2.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ }
+ },
+ "node_modules/@storybook/builder-vite/node_modules/@storybook/types": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/@storybook/types/-/types-8.1.1.tgz",
+ "integrity": "sha512-QSQ63aKr2IXrGjX2/Fg1oiGWk+2Nuf+TplaHRC2NKBMgvyn+M0BHUgMTDHQVrFaH4bpl2PkE0r0tzOKP4JI43A==",
+ "dev": true,
+ "dependencies": {
+ "@storybook/channels": "8.1.1",
+ "@types/express": "^4.7.0",
+ "file-system-cache": "2.3.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ }
+ },
+ "node_modules/@storybook/builder-vite/node_modules/fs-extra": {
+ "version": "11.2.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
+ "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
"node_modules/@storybook/channels": {
"version": "7.6.17",
"dev": true,
@@ -7088,6 +7173,16 @@
"url": "https://opencollective.com/storybook"
}
},
+ "node_modules/@storybook/preview": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/@storybook/preview/-/preview-8.1.1.tgz",
+ "integrity": "sha512-P8iBi9v/62AhTztbCYjVxH6idNO0h9uO583GHwi3uq2Io7F1gUSgwG/HYZ7PnclOsMnmG0FJvAwrvdRc6sWSNw==",
+ "dev": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ }
+ },
"node_modules/@storybook/preview-api": {
"version": "7.6.17",
"dev": true,
@@ -7289,6 +7384,197 @@
"url": "https://opencollective.com/storybook"
}
},
+ "node_modules/@storybook/web-components": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/@storybook/web-components/-/web-components-8.1.1.tgz",
+ "integrity": "sha512-o7Lt5RSKlrHG86604exFir5KYl0aUkvwVhoMbqqIhzygMlEllSb8YqJVBlnAmRW3vKd3Bp+FoO0JiQQl8gREJw==",
+ "dev": true,
+ "dependencies": {
+ "@storybook/client-logger": "8.1.1",
+ "@storybook/docs-tools": "8.1.1",
+ "@storybook/global": "^5.0.0",
+ "@storybook/manager-api": "8.1.1",
+ "@storybook/preview-api": "8.1.1",
+ "@storybook/types": "8.1.1",
+ "tiny-invariant": "^1.3.1",
+ "ts-dedent": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ },
+ "peerDependencies": {
+ "lit": "^2.0.0 || ^3.0.0"
+ }
+ },
+ "node_modules/@storybook/web-components-vite": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/@storybook/web-components-vite/-/web-components-vite-8.1.1.tgz",
+ "integrity": "sha512-96zWh51ugm3DiJlvWgqnFjNmq/6/XaxUoA+nPXHbVra6Y3MW6eUfi6qpErI2umNjjLrjVBPbkKIaMtOI5l90xA==",
+ "dev": true,
+ "dependencies": {
+ "@storybook/builder-vite": "8.1.1",
+ "@storybook/core-server": "8.1.1",
+ "@storybook/node-logger": "8.1.1",
+ "@storybook/types": "8.1.1",
+ "@storybook/web-components": "8.1.1",
+ "magic-string": "^0.30.0"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ }
+ },
+ "node_modules/@storybook/web-components-vite/node_modules/@storybook/channels": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-8.1.1.tgz",
+ "integrity": "sha512-vG7y97QB++TRkuxYLNKaWJmgr9QBUHyjQgNCWvHIeSYW5zxum9sm6VSR2j1r2G3XUGFSxDwenYBTQuwZJLhWNQ==",
+ "dev": true,
+ "dependencies": {
+ "@storybook/client-logger": "8.1.1",
+ "@storybook/core-events": "8.1.1",
+ "@storybook/global": "^5.0.0",
+ "telejson": "^7.2.0",
+ "tiny-invariant": "^1.3.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ }
+ },
+ "node_modules/@storybook/web-components-vite/node_modules/@storybook/client-logger": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-8.1.1.tgz",
+ "integrity": "sha512-9AWPgIN3K0eLusChJUqB5Ft+9P2pW5/s4vOMoj3TCvu8lrdq8AH8ctvxk7x2Kw2wEwQ/g9DyE6C/rDQUARbxew==",
+ "dev": true,
+ "dependencies": {
+ "@storybook/global": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ }
+ },
+ "node_modules/@storybook/web-components-vite/node_modules/@storybook/core-events": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-8.1.1.tgz",
+ "integrity": "sha512-WpeiBV6RWTZ6t8SI1YdQh8NlbvQtZs9WRr4CPfpzHAly+oxFy6PtPz0h5TMKsU5/kt/L9yL7tE9ZzPYzvFWH/A==",
+ "dev": true,
+ "dependencies": {
+ "@storybook/csf": "^0.1.7",
+ "ts-dedent": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ }
+ },
+ "node_modules/@storybook/web-components-vite/node_modules/@storybook/types": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/@storybook/types/-/types-8.1.1.tgz",
+ "integrity": "sha512-QSQ63aKr2IXrGjX2/Fg1oiGWk+2Nuf+TplaHRC2NKBMgvyn+M0BHUgMTDHQVrFaH4bpl2PkE0r0tzOKP4JI43A==",
+ "dev": true,
+ "dependencies": {
+ "@storybook/channels": "8.1.1",
+ "@types/express": "^4.7.0",
+ "file-system-cache": "2.3.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ }
+ },
+ "node_modules/@storybook/web-components/node_modules/@storybook/channels": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-8.1.1.tgz",
+ "integrity": "sha512-vG7y97QB++TRkuxYLNKaWJmgr9QBUHyjQgNCWvHIeSYW5zxum9sm6VSR2j1r2G3XUGFSxDwenYBTQuwZJLhWNQ==",
+ "dev": true,
+ "dependencies": {
+ "@storybook/client-logger": "8.1.1",
+ "@storybook/core-events": "8.1.1",
+ "@storybook/global": "^5.0.0",
+ "telejson": "^7.2.0",
+ "tiny-invariant": "^1.3.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ }
+ },
+ "node_modules/@storybook/web-components/node_modules/@storybook/client-logger": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-8.1.1.tgz",
+ "integrity": "sha512-9AWPgIN3K0eLusChJUqB5Ft+9P2pW5/s4vOMoj3TCvu8lrdq8AH8ctvxk7x2Kw2wEwQ/g9DyE6C/rDQUARbxew==",
+ "dev": true,
+ "dependencies": {
+ "@storybook/global": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ }
+ },
+ "node_modules/@storybook/web-components/node_modules/@storybook/core-events": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-8.1.1.tgz",
+ "integrity": "sha512-WpeiBV6RWTZ6t8SI1YdQh8NlbvQtZs9WRr4CPfpzHAly+oxFy6PtPz0h5TMKsU5/kt/L9yL7tE9ZzPYzvFWH/A==",
+ "dev": true,
+ "dependencies": {
+ "@storybook/csf": "^0.1.7",
+ "ts-dedent": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ }
+ },
+ "node_modules/@storybook/web-components/node_modules/@storybook/preview-api": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.1.1.tgz",
+ "integrity": "sha512-5EcByqtJgj7a7ZWICMLif8mK3cRmdIMbdSPEDf4X6aTQ8LZOg6updLrkb/Eh6qfeYv46TK/MP8BXa89wfOxWGQ==",
+ "dev": true,
+ "dependencies": {
+ "@storybook/channels": "8.1.1",
+ "@storybook/client-logger": "8.1.1",
+ "@storybook/core-events": "8.1.1",
+ "@storybook/csf": "^0.1.7",
+ "@storybook/global": "^5.0.0",
+ "@storybook/types": "8.1.1",
+ "@types/qs": "^6.9.5",
+ "dequal": "^2.0.2",
+ "lodash": "^4.17.21",
+ "memoizerific": "^1.11.3",
+ "qs": "^6.10.0",
+ "tiny-invariant": "^1.3.1",
+ "ts-dedent": "^2.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ }
+ },
+ "node_modules/@storybook/web-components/node_modules/@storybook/types": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/@storybook/types/-/types-8.1.1.tgz",
+ "integrity": "sha512-QSQ63aKr2IXrGjX2/Fg1oiGWk+2Nuf+TplaHRC2NKBMgvyn+M0BHUgMTDHQVrFaH4bpl2PkE0r0tzOKP4JI43A==",
+ "dev": true,
+ "dependencies": {
+ "@storybook/channels": "8.1.1",
+ "@types/express": "^4.7.0",
+ "file-system-cache": "2.3.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ }
+ },
"node_modules/@swagger-api/apidom-ast": {
"version": "0.96.0",
"license": "Apache-2.0",
@@ -8398,6 +8684,12 @@
"@types/send": "*"
}
},
+ "node_modules/@types/find-cache-dir": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/@types/find-cache-dir/-/find-cache-dir-3.2.1.tgz",
+ "integrity": "sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw==",
+ "dev": true
+ },
"node_modules/@types/grecaptcha": {
"version": "3.0.9",
"dev": true,
@@ -8508,12 +8800,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@types/parse-json": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
- "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
- "dev": true
- },
"node_modules/@types/pretty-hrtime": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@types/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",
@@ -10603,21 +10889,6 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/babel-plugin-macros": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
- "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
- "dev": true,
- "dependencies": {
- "@babel/runtime": "^7.12.5",
- "cosmiconfig": "^7.0.0",
- "resolve": "^1.19.0"
- },
- "engines": {
- "node": ">=10",
- "npm": ">=6"
- }
- },
"node_modules/babel-plugin-polyfill-corejs2": {
"version": "0.4.11",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz",
@@ -10657,15 +10928,6 @@
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
}
},
- "node_modules/babel-plugin-tsconfig-paths": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/babel-plugin-tsconfig-paths/-/babel-plugin-tsconfig-paths-1.0.3.tgz",
- "integrity": "sha512-eBTjzXpx0CXO2gooYPyIU1joS/eK1Vk2+oLhJDwRwIgh2+2kD/j649eYNtHjFKuXr36/4Y0ytPORLyiey7MLRA==",
- "dev": true,
- "peerDependencies": {
- "@babel/core": "^7.9.0"
- }
- },
"node_modules/balanced-match": {
"version": "1.0.2",
"license": "MIT"
@@ -11554,31 +11816,6 @@
"layout-base": "^1.0.0"
}
},
- "node_modules/cosmiconfig": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
- "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
- "dev": true,
- "dependencies": {
- "@types/parse-json": "^4.0.0",
- "import-fresh": "^3.2.1",
- "parse-json": "^5.0.0",
- "path-type": "^4.0.0",
- "yaml": "^1.10.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/cosmiconfig/node_modules/yaml": {
- "version": "1.10.2",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
- "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
- "dev": true,
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/country-flag-icons": {
"version": "1.5.11",
"license": "MIT"
@@ -12872,6 +13109,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/es-module-lexer": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.2.tgz",
+ "integrity": "sha512-l60ETUTmLqbVbVHv1J4/qj+M8nq7AwMzEcg3kmJDt9dCNrTk+yHcYFf/Kw75pMDwd9mPcIGCG5LcS20SxYRzFA==",
+ "dev": true
+ },
"node_modules/es-set-tostringtag": {
"version": "2.0.3",
"dev": true,
diff --git a/web/package.json b/web/package.json
index 6d2303e18b..0d6c0c6c17 100644
--- a/web/package.json
+++ b/web/package.json
@@ -71,23 +71,18 @@
"yaml": "^2.4.2"
},
"devDependencies": {
- "@babel/core": "^7.24.5",
- "@babel/plugin-proposal-class-properties": "^7.18.6",
- "@babel/plugin-proposal-decorators": "^7.24.1",
- "@babel/plugin-transform-private-methods": "^7.24.1",
- "@babel/plugin-transform-private-property-in-object": "^7.24.5",
- "@babel/plugin-transform-runtime": "^7.24.3",
- "@babel/preset-env": "^7.24.5",
- "@babel/preset-typescript": "^7.24.1",
"@hcaptcha/types": "^1.0.3",
"@jeysal/storybook-addon-css-user-preferences": "^0.2.0",
"@lit/localize-tools": "^0.7.2",
"@rollup/plugin-replace": "^5.0.5",
"@spotlightjs/spotlight": "^1.2.17",
+ "@storybook/addon-controls": "^8.1.1",
+ "@storybook/addon-docs": "^8.1.1",
"@storybook/addon-essentials": "^8.1.1",
"@storybook/addon-links": "^8.1.1",
"@storybook/api": "^7.6.17",
"@storybook/blocks": "^8.0.8",
+ "@storybook/web-components-vite": "^8.1.1",
"@testing-library/webdriverio": "^3.2.1",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/chart.js": "^2.9.41",
@@ -101,8 +96,6 @@
"@wdio/cli": "^8.36.1",
"@wdio/mocha-framework": "^8.36.1",
"@wdio/spec-reporter": "^8.36.1",
- "babel-plugin-macros": "^3.1.0",
- "babel-plugin-tsconfig-paths": "^1.0.3",
"chokidar": "^3.6.0",
"cross-env": "^7.0.3",
"esbuild": "^0.21.3",
@@ -116,10 +109,9 @@
"glob": "^10.3.15",
"lit-analyzer": "^2.0.3",
"npm-run-all": "^4.1.5",
+ "polished": "^4.3.1",
"prettier": "^3.2.5",
"pseudolocale": "^2.0.0",
- "react": "^18.2.0",
- "react-dom": "^18.3.1",
"rollup-plugin-modify": "^3.0.0",
"rollup-plugin-postcss-lit": "^2.1.0",
"storybook": "^8.1.1",
diff --git a/web/src/elements/Alert.ts b/web/src/elements/Alert.ts
index e75d2c3936..c0a642c52f 100644
--- a/web/src/elements/Alert.ts
+++ b/web/src/elements/Alert.ts
@@ -1,11 +1,20 @@
import { AKElement } from "@goauthentik/elements/Base";
-import { CSSResult, TemplateResult, html } from "lit";
+import { html } from "lit";
import { customElement, property } from "lit/decorators.js";
+import { classMap } from "lit/directives/class-map.js";
import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
+export interface IAlert {
+ inline?: boolean;
+ warning?: boolean;
+ info?: boolean;
+ success?: boolean;
+ danger?: boolean;
+}
+
export enum Level {
Warning = "pf-m-warning",
Info = "pf-m-info",
@@ -13,20 +22,88 @@ export enum Level {
Danger = "pf-m-danger",
}
+/**
+ * @class Alert
+ * @element ak-alert
+ *
+ * Alerts are in-page elements intended to draw the user's attention and alert them to important
+ * details. Alerts are used alongside form elements to warn users of potential mistakes they can
+ * make, as well as in in-line documentation.
+ */
@customElement("ak-alert")
-export class Alert extends AKElement {
+export class Alert extends AKElement implements IAlert {
+ /**
+ * Whether or not to display the entire component's contents in-line or not.
+ *
+ * @attr
+ */
@property({ type: Boolean })
inline = false;
+ /**
+ * Fallback method of determining severity
+ *
+ * @attr
+ */
@property()
level: Level = Level.Warning;
- static get styles(): CSSResult[] {
+ /**
+ * Highest severity level.
+ *
+ * @attr
+ */
+ @property({ type: Boolean })
+ danger = false;
+
+ /**
+ * Next severity level.
+ *
+ * @attr
+ */
+ @property({ type: Boolean })
+ warning = false;
+
+ /**
+ * Next severity level. The default severity level.
+ *
+ * @attr
+ */
+ @property({ type: Boolean })
+ success = false;
+
+ /**
+ * Lowest severity level.
+ *
+ * @attr
+ */
+ @property({ type: Boolean })
+ info = false;
+
+ static get styles() {
return [PFBase, PFAlert];
}
- render(): TemplateResult {
- return html`
+ get classmap() {
+ const leveltags = ["danger", "warning", "success", "info"].filter(
+ // @ts-ignore
+ (level) => this[level] && this[level] === true,
+ );
+
+ if (leveltags.length > 1) {
+ console.warn("ak-alert has multiple levels defined");
+ }
+ const level = leveltags.length > 0 ? `pf-m-${leveltags[0]}` : this.level;
+
+ return {
+ "pf-c-alert": true,
+ "pf-m-inline": this.inline,
+ [level]: true,
+ };
+ }
+
+ render() {
+ return html`
@@ -36,3 +113,9 @@ export class Alert extends AKElement {
`;
}
}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ak-alert": Alert;
+ }
+}
diff --git a/web/src/elements/Divider.ts b/web/src/elements/Divider.ts
index 3f90bb1c74..7fb18952ee 100644
--- a/web/src/elements/Divider.ts
+++ b/web/src/elements/Divider.ts
@@ -5,6 +5,14 @@ import { customElement } from "lit/decorators.js";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
+/**
+ * @class Divider
+ * @element ak-divider
+ *
+ * Divider is a horizontal rule, an in-page element to separate displayed items.
+ *
+ * @slot - HTML to display in-line in the middle of the horizontal rule.
+ */
@customElement("ak-divider")
export class Divider extends AKElement {
static get styles(): CSSResult[] {
@@ -39,3 +47,9 @@ export class Divider extends AKElement {
return html`
`;
}
}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ak-divider": Divider;
+ }
+}
diff --git a/web/src/elements/EmptyState.ts b/web/src/elements/EmptyState.ts
index 8842badea2..ba15b140f3 100644
--- a/web/src/elements/EmptyState.ts
+++ b/web/src/elements/EmptyState.ts
@@ -9,17 +9,64 @@ import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-sta
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
-@customElement("ak-empty-state")
-export class EmptyState extends AKElement {
- @property({ type: String })
- icon = "";
+export interface IEmptyState {
+ icon?: string;
+ loading?: boolean;
+ fullHeight?: boolean;
+ header?: string;
+}
+/**
+ * @class EmptyState
+ * @element ak-empty-state
+ *
+ * The EmptyState is an in-page element to indicate that something is either loading or unavailable.
+ * When "loading" is true it displays a spinner, otherwise it displays a static icon. The default
+ * icon is a question mark in a circle.
+ *
+ * @slot body - Optional low-priority text that appears beneath the state indicator.
+ * @slot primary - Optional high-priority text that appears some distance between the state indicator.
+ *
+ * The layout of the component is always centered, and from top to bottom:
+ *
+ * ```
+ * icon or spinner
+ * header
+ * body
+ * primary
+ * ```
+ */
+@customElement("ak-empty-state")
+export class EmptyState extends AKElement implements IEmptyState {
+ /**
+ * The Font Awesome icon to display. Defaults to the � symbol.
+ *
+ * @attr
+ */
+ @property({ type: String })
+ icon = "fa-question-circle";
+
+ /**
+ * Whether or not to show the spinner, or the end icon
+ *
+ * @attr
+ */
@property({ type: Boolean })
loading = false;
+ /**
+ * If set, will attempt to occupy the full viewport.
+ *
+ * @attr
+ */
@property({ type: Boolean })
fullHeight = false;
+ /**
+ * [Optional] If set, will display a message in large text beneath the icon
+ *
+ * @attr
+ */
@property()
header = "";
@@ -45,8 +92,7 @@ export class EmptyState extends AKElement {
`
: html``}
${this.header}
@@ -60,3 +106,9 @@ export class EmptyState extends AKElement {
`;
}
}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ak-empty-state": EmptyState;
+ }
+}
diff --git a/web/src/elements/Expand.ts b/web/src/elements/Expand.ts
index 1cc1629996..98f706c884 100644
--- a/web/src/elements/Expand.ts
+++ b/web/src/elements/Expand.ts
@@ -1,24 +1,53 @@
import { AKElement } from "@goauthentik/elements/Base";
import { msg } from "@lit/localize";
-import { CSSResult, TemplateResult, css, html } from "lit";
+import { css, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import PFExpandableSection from "@patternfly/patternfly/components/ExpandableSection/expandable-section.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
+export interface IExpand {
+ expanded?: boolean;
+ textOpen?: string;
+ textClosed?: string;
+}
+/**
+ * @class Expand
+ * @element ak-expand
+ *
+ * An `ak-expand` is used to hide cluttering details that a user may wish to reveal, such as the raw
+ * details of an alert or event.
+ *
+ * slot - The contents to be hidden or displayed.
+ */
@customElement("ak-expand")
export class Expand extends AKElement {
- @property({ type: Boolean })
+ /**
+ * The state of the expanded content
+ *
+ * @attr
+ */
+ @property({ type: Boolean, reflect: true })
expanded = false;
+ /**
+ * The text to display next to the open/close control when the accordion is closed.
+ *
+ * @attr
+ */
@property()
textOpen = msg("Show less");
+ /**
+ * The text to display next to the open/close control when the accordion is .
+ *
+ * @attr
+ */
@property()
textClosed = msg("Show more");
- static get styles(): CSSResult[] {
+ static get styles() {
return [
PFBase,
PFExpandableSection,
@@ -30,7 +59,7 @@ export class Expand extends AKElement {
];
}
- render(): TemplateResult {
+ render() {
return html``;
}
}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ak-expand": Expand;
+ }
+}
diff --git a/web/src/elements/buttons/ActionButton/ak-action-button.stories.ts b/web/src/elements/buttons/ActionButton/ak-action-button.stories.ts
index 45430cce4a..150ab62d40 100644
--- a/web/src/elements/buttons/ActionButton/ak-action-button.stories.ts
+++ b/web/src/elements/buttons/ActionButton/ak-action-button.stories.ts
@@ -4,9 +4,9 @@ import { Meta } from "@storybook/web-components";
import { TemplateResult, html } from "lit";
import "./ak-action-button";
-import AKActionButton from "./ak-action-button";
+import { ActionButton } from "./ak-action-button";
-const metadata: Meta
= {
+const metadata: Meta = {
title: "Elements / Action Button",
component: "ak-action-button",
parameters: {
diff --git a/web/src/elements/buttons/ActionButton/ak-action-button.ts b/web/src/elements/buttons/ActionButton/ak-action-button.ts
index e8681cf585..79b1e89cb4 100644
--- a/web/src/elements/buttons/ActionButton/ak-action-button.ts
+++ b/web/src/elements/buttons/ActionButton/ak-action-button.ts
@@ -1,9 +1,14 @@
import { MessageLevel } from "@goauthentik/common/messages";
-import { BaseTaskButton } from "@goauthentik/elements/buttons/SpinnerButton/BaseTaskButton";
+import {
+ BaseTaskButton,
+ type IBaseTaskButton,
+} from "@goauthentik/elements/buttons/SpinnerButton/BaseTaskButton";
import { showMessage } from "@goauthentik/elements/messages/MessageContainer";
import { customElement, property } from "lit/decorators.js";
+type IActionButton = IBaseTaskButton & { apiRequest: () => Promise };
+
/**
* A button associated with an event handler for loading data. Takes an asynchronous function as its
* only property.
@@ -19,7 +24,7 @@ import { customElement, property } from "lit/decorators.js";
*/
@customElement("ak-action-button")
-export class ActionButton extends BaseTaskButton {
+export class ActionButton extends BaseTaskButton implements IActionButton {
/**
* The command to run when the button is pressed. Must return a promise. If the promise is a
* reject or throw, we process the content of the promise and deliver it to the Notification
@@ -27,7 +32,6 @@ export class ActionButton extends BaseTaskButton {
*
* @attr
*/
-
@property({ attribute: false })
apiRequest: () => Promise = () => {
throw new Error();
@@ -52,4 +56,10 @@ export class ActionButton extends BaseTaskButton {
}
}
+declare global {
+ interface HTMLElementTagNameMap {
+ "ak-action-button": ActionButton;
+ }
+}
+
export default ActionButton;
diff --git a/web/src/elements/buttons/SpinnerButton/BaseTaskButton.ts b/web/src/elements/buttons/SpinnerButton/BaseTaskButton.ts
index 7e07dc3a4b..458e54e39d 100644
--- a/web/src/elements/buttons/SpinnerButton/BaseTaskButton.ts
+++ b/web/src/elements/buttons/SpinnerButton/BaseTaskButton.ts
@@ -36,6 +36,10 @@ const StatusMap = new Map([
const SPINNER_TIMEOUT = 1000 * 1.5; // milliseconds
+export interface IBaseTaskButton {
+ disabled?: boolean;
+}
+
/**
* BaseTaskButton
*
@@ -46,7 +50,6 @@ const SPINNER_TIMEOUT = 1000 * 1.5; // milliseconds
* `onFailure` call their `super.` equivalents.
*
*/
-
export abstract class BaseTaskButton extends CustomEmitterElement(AKElement) {
eventPrefix = "ak-button";
diff --git a/web/src/elements/buttons/SpinnerButton/ak-spinner-button.ts b/web/src/elements/buttons/SpinnerButton/ak-spinner-button.ts
index e3b07e2fb0..9cf75df297 100644
--- a/web/src/elements/buttons/SpinnerButton/ak-spinner-button.ts
+++ b/web/src/elements/buttons/SpinnerButton/ak-spinner-button.ts
@@ -1,7 +1,7 @@
import { customElement } from "lit/decorators.js";
import { property } from "lit/decorators.js";
-import { BaseTaskButton } from "./BaseTaskButton";
+import { BaseTaskButton, type IBaseTaskButton } from "./BaseTaskButton";
/**
* A button associated with an event handler for loading data. Takes an asynchronous function as its
@@ -17,8 +17,10 @@ import { BaseTaskButton } from "./BaseTaskButton";
* @fires ak-button-reset - When the button is reset after the async process completes
*/
+type ISpinnerButton = IBaseTaskButton & { callAction: () => Promise };
+
@customElement("ak-spinner-button")
-export class SpinnerButton extends BaseTaskButton {
+export class SpinnerButton extends BaseTaskButton implements ISpinnerButton {
/**
* The command to run when the button is pressed. Must return a promise. We don't do anything
* with that promise other than check if it's a resolve or reject, and rethrow the event after.
@@ -29,4 +31,10 @@ export class SpinnerButton extends BaseTaskButton {
callAction!: () => Promise;
}
+declare global {
+ interface HTMLElementTagNameMap {
+ "ak-spinner-button": SpinnerButton;
+ }
+}
+
export default SpinnerButton;
diff --git a/web/src/elements/cards/stories/AggregateCard.tests.ts b/web/src/elements/cards/stories/AggregateCard.test.ts
similarity index 100%
rename from web/src/elements/cards/stories/AggregateCard.tests.ts
rename to web/src/elements/cards/stories/AggregateCard.test.ts
diff --git a/web/src/elements/cards/stories/AggregatePromiseCard.tests.ts b/web/src/elements/cards/stories/AggregatePromiseCard.test.ts
similarity index 100%
rename from web/src/elements/cards/stories/AggregatePromiseCard.tests.ts
rename to web/src/elements/cards/stories/AggregatePromiseCard.test.ts
diff --git a/web/src/elements/cards/stories/QuickActionCard.tests.ts b/web/src/elements/cards/stories/QuickActionCard.test.ts
similarity index 100%
rename from web/src/elements/cards/stories/QuickActionCard.tests.ts
rename to web/src/elements/cards/stories/QuickActionCard.test.ts
diff --git a/web/src/elements/stories/Alert.docs.mdx b/web/src/elements/stories/Alert.docs.mdx
new file mode 100644
index 0000000000..92f7ff7bf9
--- /dev/null
+++ b/web/src/elements/stories/Alert.docs.mdx
@@ -0,0 +1,43 @@
+import { Canvas, Description, Meta, Story, Title } from "@storybook/blocks";
+
+import * as AlertStories from "./Alert.stories";
+
+
+
+# Alerts
+
+Alerts are in-page elements intended to draw the user's attention and alert them to important
+details. Alerts are used alongside form elements to warn users of potential mistakes they can
+make, as well as in in-line documentation.
+
+## Usage
+
+```Typescript
+import "@goauthentik/elements/Alert.js";
+```
+
+Note that the content of an alert _must_ be a valid HTML component; plain text does not work here.
+
+```html
+This is the content of your alert!
+```
+
+## Demo
+
+### Default
+
+The default state of an alert is _warning_.
+
+
+
+### Info
+
+
+
+### Success
+
+
+
+### Danger
+
+
diff --git a/web/src/elements/stories/Alert.stories.ts b/web/src/elements/stories/Alert.stories.ts
new file mode 100644
index 0000000000..eea8e624d6
--- /dev/null
+++ b/web/src/elements/stories/Alert.stories.ts
@@ -0,0 +1,77 @@
+import type { Meta, StoryObj } from "@storybook/web-components";
+
+import { html } from "lit";
+
+import { Alert, type IAlert } from "../Alert.js";
+import "../Alert.js";
+
+type IAlertForTesting = IAlert & { message: string };
+
+const metadata: Meta = {
+ title: "Elements/",
+ component: "ak-alert",
+ parameters: {
+ docs: {
+ description: "An alert",
+ },
+ },
+ argTypes: {
+ inline: { control: "boolean" },
+ warning: { control: "boolean" },
+ info: { control: "boolean" },
+ success: { control: "boolean" },
+ danger: { control: "boolean" },
+ // @ts-ignore
+ message: { control: "text" },
+ },
+};
+
+export default metadata;
+
+export const DefaultStory: StoryObj = {
+ args: {
+ inline: false,
+ warning: false,
+ info: false,
+ success: false,
+ danger: false,
+ message: "You should be alarmed.",
+ },
+
+ // @ts-ignore
+ render: ({ inline, warning, info, success, danger, message }: IAlertForTesting) => {
+ return html` `;
+ },
+};
+
+export const SuccessAlert = {
+ ...DefaultStory,
+ args: { ...DefaultStory, ...{ success: true, message: "He's a tribute to your genius!" } },
+};
+
+export const InfoAlert = {
+ ...DefaultStory,
+ args: { ...DefaultStory, ...{ info: true, message: "An octopus has tastebuds on its arms." } },
+};
+
+export const DangerAlert = {
+ ...DefaultStory,
+ args: { ...DefaultStory, ...{ danger: true, message: "Danger, Will Robinson! Danger!" } },
+};
diff --git a/web/src/elements/stories/Alert.test.ts b/web/src/elements/stories/Alert.test.ts
new file mode 100644
index 0000000000..e8eff1abe3
--- /dev/null
+++ b/web/src/elements/stories/Alert.test.ts
@@ -0,0 +1,36 @@
+import { $, expect } from "@wdio/globals";
+
+import { html, render } from "lit";
+
+import "../Alert.js";
+import { Level } from "../Alert.js";
+
+describe("ak-alert", () => {
+ it("should render an alert with the enum", async () => {
+ render(html`This is an alert`, document.body);
+
+ await expect(await $("ak-alert").$(">>>div")).not.toHaveElementClass("pf-m-inline");
+ await expect(await $("ak-alert").$(">>>div")).toHaveElementClass("pf-c-alert");
+ await expect(await $("ak-alert").$(">>>div")).toHaveElementClass("pf-m-info");
+ await expect(await $("ak-alert").$(">>>.pf-c-alert__title")).toHaveText("This is an alert");
+ });
+
+ it("should render an alert with the attribute", async () => {
+ render(html`This is an alert`, document.body);
+ await expect(await $("ak-alert").$(">>>div")).toHaveElementClass("pf-m-info");
+ await expect(await $("ak-alert").$(">>>.pf-c-alert__title")).toHaveText("This is an alert");
+ });
+
+ it("should render an alert with conflicting attributes in priority order", async () => {
+ render(html`This is an alert`, document.body);
+ await expect(await $("ak-alert").$(">>>div")).toHaveElementClass("pf-m-danger");
+ await expect(await $("ak-alert").$(">>>.pf-c-alert__title")).toHaveText("This is an alert");
+ });
+
+ it("should render an alert with an inline class and the default level", async () => {
+ render(html`This is an alert`, document.body);
+ await expect(await $("ak-alert").$(">>>div")).toHaveElementClass("pf-m-warning");
+ await expect(await $("ak-alert").$(">>>div")).toHaveElementClass("pf-m-inline");
+ await expect(await $("ak-alert").$(">>>.pf-c-alert__title")).toHaveText("This is an alert");
+ });
+});
diff --git a/web/src/elements/stories/Divider.docs.mdx b/web/src/elements/stories/Divider.docs.mdx
new file mode 100644
index 0000000000..7b534a4de3
--- /dev/null
+++ b/web/src/elements/stories/Divider.docs.mdx
@@ -0,0 +1,51 @@
+import { Canvas, Description, Meta, Story, Title } from "@storybook/blocks";
+
+import * as DividerStories from "./Divider.stories";
+
+
+
+# Divider
+
+Divider is a horizontal rule, an in-page element to separate displayed items.
+
+It has no configurable attributes. It does have a single unnamed slot, which is displayed in-line in
+the center of the rule. If the CSS Base in loaded into the parent context, icons defined in the base
+can be used here.
+
+## Usage
+
+```Typescript
+import "@goauthentik/elements/Divider.js";
+```
+
+```html
+
+```
+
+With content:
+
+```html
+Your content here
+```
+
+With an icon:
+
+```html
+
+```
+
+## Demo
+
+Note that the Divider inherits its background from its parent component.
+
+### Default Horizontal Rule
+
+
+
+### With A Message
+
+
+
+### With an Icon
+
+
diff --git a/web/src/elements/stories/Divider.stories.ts b/web/src/elements/stories/Divider.stories.ts
new file mode 100644
index 0000000000..1d424294c7
--- /dev/null
+++ b/web/src/elements/stories/Divider.stories.ts
@@ -0,0 +1,41 @@
+import type { Meta, StoryObj } from "@storybook/web-components";
+
+import { TemplateResult, html } from "lit";
+
+import { Divider } from "../Divider.js";
+import "../Divider.js";
+
+const metadata: Meta = {
+ title: "Elements/",
+ component: "ak-divider",
+ parameters: {
+ docs: {
+ description: "our most simple divider",
+ },
+ },
+};
+
+export default metadata;
+
+const container = (content: TemplateResult) =>
+ html`
+ ${content}
+
`;
+
+export const DefaultStory: StoryObj = {
+ render: () => container(html` `),
+};
+
+export const DividerWithSlottedContent: StoryObj = {
+ render: () => container(html` Time for bed!
`),
+};
+
+export const DividerWithSlottedIcon: StoryObj = {
+ render: () => container(html` `),
+};
diff --git a/web/src/elements/stories/Divider.test.ts b/web/src/elements/stories/Divider.test.ts
new file mode 100644
index 0000000000..3f87da04d9
--- /dev/null
+++ b/web/src/elements/stories/Divider.test.ts
@@ -0,0 +1,33 @@
+import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet.js";
+import { $, expect } from "@wdio/globals";
+
+import { TemplateResult, html, render as litRender } from "lit";
+
+import AKGlobal from "@goauthentik/common/styles/authentik.css";
+import PFBase from "@patternfly/patternfly/patternfly-base.css";
+
+import "../Divider.js";
+
+const render = (body: TemplateResult) => {
+ document.adoptedStyleSheets = [
+ ...document.adoptedStyleSheets,
+ ensureCSSStyleSheet(PFBase),
+ ensureCSSStyleSheet(AKGlobal),
+ ];
+ return litRender(body, document.body);
+};
+
+describe("ak-divider", () => {
+ it("should render the divider", async () => {
+ render(html``);
+ const empty = await $("ak-divider");
+ await expect(empty).toExist();
+ });
+
+ it("should render the divider with the specified text", async () => {
+ render(html`Your Message Here`);
+ const span = await $("ak-divider").$("span");
+ await expect(span).toExist();
+ await expect(span).toHaveText("Your Message Here");
+ });
+});
diff --git a/web/src/elements/stories/EmptyState.docs.mdx b/web/src/elements/stories/EmptyState.docs.mdx
new file mode 100644
index 0000000000..c5e770871d
--- /dev/null
+++ b/web/src/elements/stories/EmptyState.docs.mdx
@@ -0,0 +1,59 @@
+import { Canvas, Description, Meta, Story, Title } from "@storybook/blocks";
+
+import * as EmptyStateStories from "./EmptyState.stories";
+
+
+
+# EmptyState
+
+The EmptyState is an in-page element to indicate that something is either loading or unavailable.
+When "loading" is true it displays a spinner, otherwise it displays a static icon. The default
+icon is a question mark in a circle.
+
+It has two named slots, `body` and `primary`, to communicate further details about the current state
+this element is meant to display.
+
+## Usage
+
+```Typescript
+import "@goauthentik/elements/EmptyState.js";
+```
+
+Note that the content of an alert _must_ be a valid HTML component; plain text does not work here.
+
+```html
+This would display in the "primary" slot
+```
+
+## Demo
+
+### Default: Loading
+
+The default state is _loading_
+
+
+
+### Done
+
+
+
+### Alternative "Done" Icon
+
+This also shows the "header" attribute filled, which is rendered in a large, dark typeface.
+
+
+
+### The Body Slot Filled
+
+The body content slot is rendered in a lighter typeface at default size.
+
+
+
+### The Body and Primary Slot Filled
+
+The primary content is rendered in the normal dark typeface at default size. It is also spaced
+significantly below the spinner itself.
+
+
diff --git a/web/src/elements/stories/EmptyState.stories.ts b/web/src/elements/stories/EmptyState.stories.ts
new file mode 100644
index 0000000000..69d710f5bd
--- /dev/null
+++ b/web/src/elements/stories/EmptyState.stories.ts
@@ -0,0 +1,108 @@
+import type { Meta, StoryObj } from "@storybook/web-components";
+
+import { TemplateResult, html } from "lit";
+import { ifDefined } from "lit/directives/if-defined.js";
+
+import { EmptyState, type IEmptyState } from "../EmptyState.js";
+import "../EmptyState.js";
+
+const metadata: Meta = {
+ title: "Elements/",
+ component: "ak-empty-state",
+ parameters: {
+ docs: {
+ description: "Our empty state spinner",
+ },
+ },
+ argTypes: {
+ icon: { control: "text" },
+ loading: { control: "boolean" },
+ fullHeight: { control: "boolean" },
+ header: { control: "text" },
+ },
+};
+
+export default metadata;
+
+const container = (content: TemplateResult) =>
+ html`
+ ${content}
+
`;
+
+export const DefaultStory: StoryObj = {
+ args: {
+ icon: undefined,
+ loading: true,
+ fullHeight: false,
+ header: undefined,
+ },
+
+ render: ({ icon, loading, fullHeight, header }: IEmptyState) =>
+ container(
+ html`
+ `,
+ ),
+};
+
+export const DefaultAndLoadingDone = {
+ ...DefaultStory,
+ args: { ...DefaultStory, ...{ loading: false } },
+};
+
+export const DoneWithAlternativeIcon = {
+ ...DefaultStory,
+ args: {
+ ...DefaultStory,
+ ...{ loading: false, icon: "fa-space-shuttle", header: "The final frontier" },
+ },
+};
+
+export const WithBodySlotFilled = {
+ ...DefaultStory,
+ args: {
+ ...DefaultStory,
+ ...{ loading: false, icon: "fa-space-shuttle", header: "The final frontier" },
+ },
+ render: ({ icon, loading, fullHeight, header }: IEmptyState) =>
+ container(html`
+
+ This is the body content
+
+ `),
+};
+
+export const WithBodyAndPrimarySlotsFilled = {
+ ...DefaultStory,
+ args: {
+ ...DefaultStory,
+ ...{ loading: false, icon: "fa-space-shuttle", header: "The final frontier" },
+ },
+ render: ({ icon, loading, fullHeight, header }: IEmptyState) =>
+ container(
+ html`
+ This is the body content slot
+ This is the primary content slot
+ `,
+ ),
+};
diff --git a/web/src/elements/EmptyState.test.ts b/web/src/elements/stories/EmptyState.test.ts
similarity index 95%
rename from web/src/elements/EmptyState.test.ts
rename to web/src/elements/stories/EmptyState.test.ts
index ac0d68c586..7ef5f46038 100644
--- a/web/src/elements/EmptyState.test.ts
+++ b/web/src/elements/stories/EmptyState.test.ts
@@ -7,7 +7,7 @@ import { TemplateResult, html, render as litRender } from "lit";
import AKGlobal from "@goauthentik/common/styles/authentik.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
-import "./EmptyState.js";
+import "../EmptyState.js";
const render = (body: TemplateResult) => {
document.adoptedStyleSheets = [
@@ -58,6 +58,6 @@ describe("ak-empty-state", () => {
);
const message = await $("ak-empty-state").$(">>>.pf-c-empty-state__body").$(">>>p");
- await expect(message).toHaveText("Try again with a different filter");
+ await expect(message).toHaveText("Try again with a fucked filter");
});
});
diff --git a/web/src/elements/stories/Expand.docs.mdx b/web/src/elements/stories/Expand.docs.mdx
new file mode 100644
index 0000000000..c5ee9b455a
--- /dev/null
+++ b/web/src/elements/stories/Expand.docs.mdx
@@ -0,0 +1,38 @@
+import { Canvas, Description, Meta, Story, Title } from "@storybook/blocks";
+
+import * as ExpandStories from "./Expand.stories";
+
+
+
+# Expand
+
+Expand is an in-page element used to hid cluttering details that a user may wish to reveal, such as raw
+details of an alert or event.
+
+It has one unnamed slot for the content to be displayed.
+
+## Usage
+
+```Typescript
+import "@goauthentik/elements/Expand.js";
+```
+
+```html
+Your primary content goes here
+```
+
+To show the expanded content on initial render:
+
+```html
+Your primary content goes here
+```
+
+## Demo
+
+### Default: The content is hidden
+
+
+
+### Expanded
+
+
diff --git a/web/src/elements/stories/Expand.stories.ts b/web/src/elements/stories/Expand.stories.ts
new file mode 100644
index 0000000000..b7baa4eb69
--- /dev/null
+++ b/web/src/elements/stories/Expand.stories.ts
@@ -0,0 +1,60 @@
+import type { Meta, StoryObj } from "@storybook/web-components";
+
+import { TemplateResult, html } from "lit";
+import { ifDefined } from "lit/directives/if-defined.js";
+
+import { Expand, type IExpand } from "../Expand.js";
+import "../Expand.js";
+
+const metadata: Meta = {
+ title: "Elements/",
+ component: "ak-expand",
+ parameters: {
+ docs: {
+ description: "Our accordion component",
+ },
+ },
+ argTypes: {
+ expanded: { control: "boolean" },
+ textOpen: { control: "text" },
+ textClosed: { control: "text" },
+ },
+};
+
+export default metadata;
+
+const container = (content: TemplateResult) =>
+ html`
+ ${content}
+
`;
+
+export const DefaultStory: StoryObj = {
+ args: {
+ expanded: false,
+ textOpen: undefined,
+ textClosed: undefined,
+ },
+
+ render: ({ expanded, textOpen, textClosed }: IExpand) =>
+ container(
+ html`
+
Μήτ᾽ ἔμοι μέλι μήτε μέλισσα
+
"Neither the bee nor the honey for me." - Sappho, 600 BC
+
+ `,
+ ),
+};
+export const Expanded = {
+ ...DefaultStory,
+ args: { ...DefaultStory, ...{ expanded: true } },
+};
diff --git a/web/src/elements/stories/Expand.test.ts b/web/src/elements/stories/Expand.test.ts
new file mode 100644
index 0000000000..3fe64a911d
--- /dev/null
+++ b/web/src/elements/stories/Expand.test.ts
@@ -0,0 +1,52 @@
+import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet.js";
+import { $, expect } from "@wdio/globals";
+
+import { TemplateResult, html, render as litRender } from "lit";
+
+import AKGlobal from "@goauthentik/common/styles/authentik.css";
+import PFBase from "@patternfly/patternfly/patternfly-base.css";
+
+import "../Expand.js";
+
+const render = (body: TemplateResult) => {
+ document.adoptedStyleSheets = [
+ ...document.adoptedStyleSheets,
+ ensureCSSStyleSheet(PFBase),
+ ensureCSSStyleSheet(AKGlobal),
+ ];
+ return litRender(body, document.body);
+};
+
+describe("ak-expand", () => {
+ it("should render the expansion content hidden by default", async () => {
+ render(html`This is the expanded text
`);
+ const text = await $("ak-expand").$(">>>.pf-c-expandable-section__content");
+ await expect(text).not.toBeDisplayed();
+ });
+
+ it("should render the expansion content visible on demand", async () => {
+ render(html`This is the expanded text
`);
+ const paragraph = await $("ak-expand").$(">>>p");
+ await expect(paragraph).toExist();
+ await expect(paragraph).toBeDisplayed();
+ await expect(paragraph).toHaveText("This is the expanded text");
+ });
+
+ it("should respond to the click event", async () => {
+ render(html`This is the expanded text
`);
+ let content = await $("ak-expand").$(">>>.pf-c-expandable-section__content");
+ await expect(content).toExist();
+ await expect(content).not.toBeDisplayed();
+ const control = await $("ak-expand").$(">>>button");
+
+ await control.click();
+ content = await $("ak-expand").$(">>>.pf-c-expandable-section__content");
+ await expect(content).toExist();
+ await expect(content).toBeDisplayed();
+
+ await control.click();
+ content = await $("ak-expand").$(">>>.pf-c-expandable-section__content");
+ await expect(content).toExist();
+ await expect(content).not.toBeDisplayed();
+ });
+});
diff --git a/web/wdio.conf.ts b/web/wdio.conf.ts
index f1fd0c9f62..3c02e6a20c 100644
--- a/web/wdio.conf.ts
+++ b/web/wdio.conf.ts
@@ -87,7 +87,7 @@ export const config: Options.Testrunner = {
// and 30 processes will get spawned. The property handles how many capabilities
// from the same test should run tests.
//
- maxInstances: 10,
+ maxInstances: 1,
//
// If you have trouble getting all important capabilities together, check out the
// Sauce Labs platform configurator - a great tool to configure your capabilities: