diff options
Diffstat (limited to 'old-apps/web-shared')
129 files changed, 11370 insertions, 0 deletions
diff --git a/old-apps/web-shared/.typesafe-i18n.json b/old-apps/web-shared/.typesafe-i18n.json new file mode 100644 index 0000000..574db64 --- /dev/null +++ b/old-apps/web-shared/.typesafe-i18n.json @@ -0,0 +1,5 @@ +{ + "esmImports": true, + "outputPath": "./src/lib/i18n", + "$schema": "https://unpkg.com/typesafe-i18n@5.5.2/schema/typesafe-i18n.json" +}
\ No newline at end of file diff --git a/old-apps/web-shared/package.json b/old-apps/web-shared/package.json new file mode 100644 index 0000000..67b1336 --- /dev/null +++ b/old-apps/web-shared/package.json @@ -0,0 +1,20 @@ +{ + "name": "greatoffice-web-shared", + "version": "0.0.1", + "private": "true", + "scripts": { + "dev": "npm-run-all --parallel vite typesafe-i18n", + "vite": "vite", + "typesafe-i18n": "typesafe-i18n" + }, + "devDependencies": { + "@js-temporal/polyfill": "^0.4.2", + "fuzzysort": "^2.0.1", + "npm-run-all": "^4.1.5", + "svelte": "^3.49.0", + "svelte-feather-icons": "^4.0.0", + "svelte-spa-router": "^3.3.0", + "typesafe-i18n": "^5.11.0", + "typescript": "4.7.4" + } +} diff --git a/old-apps/web-shared/pnpm-lock.yaml b/old-apps/web-shared/pnpm-lock.yaml new file mode 100644 index 0000000..628f54b --- /dev/null +++ b/old-apps/web-shared/pnpm-lock.yaml @@ -0,0 +1,628 @@ +lockfileVersion: 5.4 + +specifiers: + '@js-temporal/polyfill': ^0.4.2 + fuzzysort: ^2.0.1 + npm-run-all: ^4.1.5 + svelte: ^3.49.0 + svelte-feather-icons: ^4.0.0 + svelte-spa-router: ^3.3.0 + typesafe-i18n: ^5.11.0 + typescript: 4.7.4 + +devDependencies: + '@js-temporal/polyfill': 0.4.2 + fuzzysort: 2.0.1 + npm-run-all: 4.1.5 + svelte: 3.49.0 + svelte-feather-icons: 4.0.0 + svelte-spa-router: 3.3.0 + typesafe-i18n: 5.11.0_typescript@4.7.4 + typescript: 4.7.4 + +packages: + + /@js-temporal/polyfill/0.4.2: + resolution: {integrity: sha512-c85vRxyqnJaXKyf4tvYij8jwiVIZhNLYDI9C4LLuOwVEHf4HUqGg07BBn70Le71W193QT/vmKg3jPUyQxJRHKQ==} + engines: {node: '>=12'} + dependencies: + jsbi: 4.3.0 + tslib: 2.4.0 + dev: true + + /ansi-styles/3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + dev: true + + /balanced-match/1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /brace-expansion/1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /call-bind/1.0.2: + resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} + dependencies: + function-bind: 1.1.1 + get-intrinsic: 1.1.2 + dev: true + + /chalk/2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + dev: true + + /color-convert/1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + dev: true + + /color-name/1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + dev: true + + /concat-map/0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true + + /cross-spawn/6.0.5: + resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==} + engines: {node: '>=4.8'} + dependencies: + nice-try: 1.0.5 + path-key: 2.0.1 + semver: 5.7.1 + shebang-command: 1.2.0 + which: 1.3.1 + dev: true + + /define-properties/1.1.4: + resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} + engines: {node: '>= 0.4'} + dependencies: + has-property-descriptors: 1.0.0 + object-keys: 1.1.1 + dev: true + + /error-ex/1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + dependencies: + is-arrayish: 0.2.1 + dev: true + + /es-abstract/1.20.1: + resolution: {integrity: sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + es-to-primitive: 1.2.1 + function-bind: 1.1.1 + function.prototype.name: 1.1.5 + get-intrinsic: 1.1.2 + get-symbol-description: 1.0.0 + has: 1.0.3 + has-property-descriptors: 1.0.0 + has-symbols: 1.0.3 + internal-slot: 1.0.3 + is-callable: 1.2.4 + is-negative-zero: 2.0.2 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + is-string: 1.0.7 + is-weakref: 1.0.2 + object-inspect: 1.12.2 + object-keys: 1.1.1 + object.assign: 4.1.3 + regexp.prototype.flags: 1.4.3 + string.prototype.trimend: 1.0.5 + string.prototype.trimstart: 1.0.5 + unbox-primitive: 1.0.2 + dev: true + + /es-to-primitive/1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + dependencies: + is-callable: 1.2.4 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + dev: true + + /escape-string-regexp/1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + dev: true + + /function-bind/1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + dev: true + + /function.prototype.name/1.1.5: + resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.1 + functions-have-names: 1.2.3 + dev: true + + /functions-have-names/1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + dev: true + + /fuzzysort/2.0.1: + resolution: {integrity: sha512-SlgbPAq0eQ6JQ1h3l4MNeGH/t9DHKH8GGM0RD/6RhmJrNnSoWt3oIVaiQm9g9BPB+wAhRMeMqlUTbhbd7+Ufcg==} + dev: true + + /get-intrinsic/1.1.2: + resolution: {integrity: sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==} + dependencies: + function-bind: 1.1.1 + has: 1.0.3 + has-symbols: 1.0.3 + dev: true + + /get-symbol-description/1.0.0: + resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.2 + dev: true + + /graceful-fs/4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + dev: true + + /has-bigints/1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + dev: true + + /has-flag/3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + dev: true + + /has-property-descriptors/1.0.0: + resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} + dependencies: + get-intrinsic: 1.1.2 + dev: true + + /has-symbols/1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + dev: true + + /has-tostringtag/1.0.0: + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + + /has/1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + dev: true + + /hosted-git-info/2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + dev: true + + /internal-slot/1.0.3: + resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.1.2 + has: 1.0.3 + side-channel: 1.0.4 + dev: true + + /is-arrayish/0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + dev: true + + /is-bigint/1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + dependencies: + has-bigints: 1.0.2 + dev: true + + /is-boolean-object/1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-callable/1.2.4: + resolution: {integrity: sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==} + engines: {node: '>= 0.4'} + dev: true + + /is-core-module/2.10.0: + resolution: {integrity: sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==} + dependencies: + has: 1.0.3 + dev: true + + /is-date-object/1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-negative-zero/2.0.2: + resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + engines: {node: '>= 0.4'} + dev: true + + /is-number-object/1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-regex/1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-shared-array-buffer/1.0.2: + resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + dependencies: + call-bind: 1.0.2 + dev: true + + /is-string/1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-symbol/1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + + /is-weakref/1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + dependencies: + call-bind: 1.0.2 + dev: true + + /isexe/2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true + + /jsbi/4.3.0: + resolution: {integrity: sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g==} + dev: true + + /json-parse-better-errors/1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + dev: true + + /load-json-file/4.0.0: + resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} + engines: {node: '>=4'} + dependencies: + graceful-fs: 4.2.10 + parse-json: 4.0.0 + pify: 3.0.0 + strip-bom: 3.0.0 + dev: true + + /memorystream/0.3.1: + resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} + engines: {node: '>= 0.10.0'} + dev: true + + /minimatch/3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /nice-try/1.0.5: + resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} + dev: true + + /normalize-package-data/2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.1 + semver: 5.7.1 + validate-npm-package-license: 3.0.4 + dev: true + + /npm-run-all/4.1.5: + resolution: {integrity: sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==} + engines: {node: '>= 4'} + hasBin: true + dependencies: + ansi-styles: 3.2.1 + chalk: 2.4.2 + cross-spawn: 6.0.5 + memorystream: 0.3.1 + minimatch: 3.1.2 + pidtree: 0.3.1 + read-pkg: 3.0.0 + shell-quote: 1.7.3 + string.prototype.padend: 3.1.3 + dev: true + + /object-inspect/1.12.2: + resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==} + dev: true + + /object-keys/1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + dev: true + + /object.assign/4.1.3: + resolution: {integrity: sha512-ZFJnX3zltyjcYJL0RoCJuzb+11zWGyaDbjgxZbdV7rFEcHQuYxrZqhow67aA7xpes6LhojyFDaBKAFfogQrikA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + has-symbols: 1.0.3 + object-keys: 1.1.1 + dev: true + + /parse-json/4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + dependencies: + error-ex: 1.3.2 + json-parse-better-errors: 1.0.2 + dev: true + + /path-key/2.0.1: + resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} + engines: {node: '>=4'} + dev: true + + /path-parse/1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: true + + /path-type/3.0.0: + resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} + engines: {node: '>=4'} + dependencies: + pify: 3.0.0 + dev: true + + /pidtree/0.3.1: + resolution: {integrity: sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==} + engines: {node: '>=0.10'} + hasBin: true + dev: true + + /pify/3.0.0: + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} + dev: true + + /read-pkg/3.0.0: + resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} + engines: {node: '>=4'} + dependencies: + load-json-file: 4.0.0 + normalize-package-data: 2.5.0 + path-type: 3.0.0 + dev: true + + /regexp.prototype.flags/1.4.3: + resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + functions-have-names: 1.2.3 + dev: true + + /regexparam/2.0.1: + resolution: {integrity: sha512-zRgSaYemnNYxUv+/5SeoHI0eJIgTL/A2pUtXUPLHQxUldagouJ9p+K6IbIZ/JiQuCEv2E2B1O11SjVQy3aMCkw==} + engines: {node: '>=8'} + dev: true + + /resolve/1.22.1: + resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} + hasBin: true + dependencies: + is-core-module: 2.10.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: true + + /semver/5.7.1: + resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} + hasBin: true + dev: true + + /shebang-command/1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + dependencies: + shebang-regex: 1.0.0 + dev: true + + /shebang-regex/1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + dev: true + + /shell-quote/1.7.3: + resolution: {integrity: sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==} + dev: true + + /side-channel/1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.2 + object-inspect: 1.12.2 + dev: true + + /spdx-correct/3.1.1: + resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==} + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.11 + dev: true + + /spdx-exceptions/2.3.0: + resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} + dev: true + + /spdx-expression-parse/3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + dependencies: + spdx-exceptions: 2.3.0 + spdx-license-ids: 3.0.11 + dev: true + + /spdx-license-ids/3.0.11: + resolution: {integrity: sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==} + dev: true + + /string.prototype.padend/3.1.3: + resolution: {integrity: sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.1 + dev: true + + /string.prototype.trimend/1.0.5: + resolution: {integrity: sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.1 + dev: true + + /string.prototype.trimstart/1.0.5: + resolution: {integrity: sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.1 + dev: true + + /strip-bom/3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + dev: true + + /supports-color/5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + dev: true + + /supports-preserve-symlinks-flag/1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: true + + /svelte-feather-icons/4.0.0: + resolution: {integrity: sha512-4ieUsjp+VYa1r6y80jDt9zRiRUZyJNbESpRdHdJJhiBubyuXX96A7f1UZSK4olxzP6Qsg5ZAuyZlnmvD+/swAA==} + dependencies: + svelte: 3.49.0 + dev: true + + /svelte-spa-router/3.3.0: + resolution: {integrity: sha512-cwRNe7cxD43sCvSfEeaKiNZg3FCizGxeMcf7CPiWRP3jKXjEma3vxyyuDtPOam6nWbVxl9TNM3hlE/i87ZlqcQ==} + dependencies: + regexparam: 2.0.1 + dev: true + + /svelte/3.49.0: + resolution: {integrity: sha512-+lmjic1pApJWDfPCpUUTc1m8azDqYCG1JN9YEngrx/hUyIcFJo6VZhj0A1Ai0wqoHcEIuQy+e9tk+4uDgdtsFA==} + engines: {node: '>= 8'} + dev: true + + /tslib/2.4.0: + resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} + dev: true + + /typesafe-i18n/5.11.0_typescript@4.7.4: + resolution: {integrity: sha512-OVX/6/F834XldHTMdmo3TcMPePcvLXwYrkDgqWYxmuVCTyCrk0aIdUOIWM0RPZEQ2D106+/LcWFCkJiBCuK2pA==} + hasBin: true + peerDependencies: + typescript: '>=3.5.1' + dependencies: + typescript: 4.7.4 + dev: true + + /typescript/4.7.4: + resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + + /unbox-primitive/1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + dependencies: + call-bind: 1.0.2 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + dev: true + + /validate-npm-package-license/3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + dependencies: + spdx-correct: 3.1.1 + spdx-expression-parse: 3.0.1 + dev: true + + /which-boxed-primitive/1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + dev: true + + /which/1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true diff --git a/old-apps/web-shared/src/assets/logos/projects.png b/old-apps/web-shared/src/assets/logos/projects.png Binary files differnew file mode 100644 index 0000000..e49191f --- /dev/null +++ b/old-apps/web-shared/src/assets/logos/projects.png diff --git a/old-apps/web-shared/src/assets/preload.css b/old-apps/web-shared/src/assets/preload.css new file mode 100644 index 0000000..3ee8bda --- /dev/null +++ b/old-apps/web-shared/src/assets/preload.css @@ -0,0 +1,127 @@ +:root { + --loader-primary: hsl(210, 92%, 44%); + --loader-accent: hsl(350, 47%, 42%); + --loader-contrast: hsl(230, 2%, 48%); + --loader-easing: cubic-bezier(0.645, 0.045, 0.355, 1); +} + +[data-theme="dark"] :root { + --loader-primary: hsl(250, 100%, 69%); + --loader-accent: hsl(342, 92%, 47%); + --loader-contrast: hsl(250, 100%, 69%); + --loader-easing: cubic-bezier(0.645, 0.045, 0.355, 1); +} + +[data-theme="dark"] { + background-color: hsl(232, 11%, 15%); +} + +.fill-loader { + position: relative; + overflow: hidden; + display: inline-block; + margin: 3rem; +} + +.fill-loader__fill { + position: absolute; +} + +@supports (-webkit-animation-name: this) or (animation-name: this) { + .fill-loader__label { + position: absolute; + clip: rect(1px, 1px, 1px, 1px); + -webkit-clip-path: inset(50%); + clip-path: inset(50%); + } +} + +@supports (-webkit-animation-name: this) or (animation-name: this) { + .fill-loader--v4 { + width: 90%; + max-width: 300px; + } + + .fill-loader--v4 .fill-loader__base { + height: 4px; + background-color: var(--loader-contrast); + } + + .fill-loader--v4 .fill-loader__fill { + top: 0; + left: 0; + right: 0; + height: 100%; + background-color: var(--loader-primary); + -webkit-animation: fill-loader-4 1.6s infinite var(--loader-easing); + animation: fill-loader-4 1.6s infinite var(--loader-easing); + will-change: left, right; + } +} + +@-webkit-keyframes fill-loader-4 { + 0% { + left: 0; + right: 100%; + background-color: var(--loader-primary); + } + + 10%, + 60% { + left: 0; + } + + 40%, + 90% { + right: 0; + } + + 50% { + left: 100%; + background-color: var(--loader-primary); + } + + 51% { + left: 0; + right: 100%; + background-color: var(--loader-accent); + } + + 100% { + left: 100%; + background-color: var(--loader-accent); + } +} +@keyframes fill-loader-4 { + 0% { + left: 0; + right: 100%; + background-color: var(--loader-primary); + } + + 10%, + 60% { + left: 0; + } + + 40%, + 90% { + right: 0; + } + + 50% { + left: 100%; + background-color: var(--loader-primary); + } + + 51% { + left: 0; + right: 100%; + background-color: var(--loader-accent); + } + + 100% { + left: 100%; + background-color: var(--loader-accent); + } +}
\ No newline at end of file diff --git a/old-apps/web-shared/src/assets/preload.js b/old-apps/web-shared/src/assets/preload.js new file mode 100644 index 0000000..379902f --- /dev/null +++ b/old-apps/web-shared/src/assets/preload.js @@ -0,0 +1,13 @@ +const value = `; ${document.cookie}`; +const parts = value.split(`; go_theme=`); +let currentTheme = "system"; +if (parts.length === 2) { + currentTheme = parts.pop().split(";").shift(); +} +if (currentTheme === "light") { + document.querySelector("html").dataset.theme = "light"; +} else if (currentTheme === "dark") { + document.querySelector("html").dataset.theme = "dark"; +} else { + document.querySelector("html").dataset.theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; +}
\ No newline at end of file diff --git a/old-apps/web-shared/src/components/alert.svelte b/old-apps/web-shared/src/components/alert.svelte new file mode 100644 index 0000000..4119edf --- /dev/null +++ b/old-apps/web-shared/src/components/alert.svelte @@ -0,0 +1,121 @@ +<script lang="ts"> + import {random_string} from "$shared/lib/helpers"; + import {afterUpdate, onMount} from "svelte"; + import {Temporal} from "@js-temporal/polyfill"; + + const noCooldownSetting = "no-cooldown"; + // if no unique id is supplied, cooldown will not work between page loads. + // Therefore we are disabling it with noCooldownSetting in the fallback id. + export let id = "alert--" + noCooldownSetting + "--" + random_string(4); + export let title = ""; + export let message = ""; + export let type = "info"; + export let closeable = false; + export let closeableCooldown = "-1"; + export let visible = true; + + const cooldownStorageKey = "lastseen--" + id; + $: cooldownEnabled = id.indexOf(noCooldownSetting) === -1 && closeable && (closeableCooldown === "~" || parseInt(closeableCooldown) > 0); + + function close() { + visible = false; + if (cooldownEnabled) { + console.log("Cooldown enabled for " + id + ", " + closeableCooldown === "~" ? "with an endless cooldown" : ""); + localStorage.setItem(cooldownStorageKey, String(Temporal.Now.instant().epochSeconds)); + } + } + + // Manages the state of the alert if cooldown is enabled + function run_cooldown() { + if (!cooldownEnabled) { + console.log("Alert cooldown is not enabled for " + id); + return; + } + if (!localStorage.getItem(cooldownStorageKey)) { + console.log("Alert " + id + " has not been seen yet, displaying"); + visible = true; + return; + } + if (!visible) { + console.log("Alert " + id + " is not visible, stopping cooldown change") + return; + } + if (closeableCooldown === "~") { + console.log("Alert " + id + " has an infinite cooldown, hiding"); + visible = false; + return; + } + + const lastSeen = Temporal.Instant.fromEpochSeconds(localStorage.getItem(cooldownStorageKey) as number); + if (Temporal.Instant.compare(Temporal.Now.instant(), lastSeen.add({seconds: parseInt(closeableCooldown)})) === 1) { + console.log("Alert " + id + " has a cooldown of " + closeableCooldown + " and was last seen " + lastSeen.toLocaleString() + " making it due for a showing"); + visible = true; + } else { + visible = false; + } + } + + onMount(() => { + if (cooldownEnabled) { + run_cooldown(); + } + }) + + afterUpdate(() => { + if (type === "default") { + type = "primary"; + } + }); +</script> + +<div class="alert alert--{type} padding-sm radius-md" + id="{id}" + class:alert--is-visible={visible} + role="alert"> + <div class="flex justify-between"> + <div class="flex flex-row items-center"> + <svg class="icon icon--sm alert__icon margin-right-xxs" + viewBox="0 0 24 24" + aria-hidden="true"> + <path d="M12,0C5.383,0,0,5.383,0,12s5.383,12,12,12s12-5.383,12-12S18.617,0,12,0z M14.658,18.284 c-0.661,0.26-2.952,1.354-4.272,0.191c-0.394-0.346-0.59-0.785-0.59-1.318c0-0.998,0.328-1.868,0.919-3.957 c0.104-0.395,0.231-0.907,0.231-1.313c0-0.701-0.266-0.887-0.987-0.887c-0.352,0-0.742,0.125-1.095,0.257l0.195-0.799 c0.787-0.32,1.775-0.71,2.621-0.71c1.269,0,2.203,0.633,2.203,1.837c0,0.347-0.06,0.955-0.186,1.375l-0.73,2.582 c-0.151,0.522-0.424,1.673-0.001,2.014c0.416,0.337,1.401,0.158,1.887-0.071L14.658,18.284z M13.452,8c-0.828,0-1.5-0.672-1.5-1.5 s0.672-1.5,1.5-1.5s1.5,0.672,1.5,1.5S14.28,8,13.452,8z"></path> + </svg> + {#if title} + <p class="text-sm"> + <strong class="error-title">{title}</strong> + </p> + {:else if message} + <div class="text-component text-sm break-word"> + {@html message} + </div> + {/if} + </div> + {#if closeable} + <button class="reset alert__close-btn" + on:click={close}> + <svg class="icon" + viewBox="0 0 20 20" + fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2"> + <title>Close alert</title> + <line x1="3" + y1="3" + x2="17" + y2="17"/> + <line x1="17" + y1="3" + x2="3" + y2="17"/> + </svg> + </button> + {/if} + </div> + + {#if message && title} + <div class="text-component text-sm break-word padding-top-xs"> + {@html message} + </div> + {/if} +</div> diff --git a/old-apps/web-shared/src/components/blowout-toolbelt.svelte b/old-apps/web-shared/src/components/blowout-toolbelt.svelte new file mode 100644 index 0000000..b83048c --- /dev/null +++ b/old-apps/web-shared/src/components/blowout-toolbelt.svelte @@ -0,0 +1,70 @@ +<script> + import { onMount } from "svelte"; + import ThemeSwitcher from "./theme-switcher.svelte"; + import ThemeSwitcherIcon from "./theme-switcher-icon.svelte"; + import LocaleSwitcher from "./locale-switcher.svelte"; + import LocaleSwitcherIcon from "./locale-switcher-icon.svelte"; + import { ChevronsRightIcon, ChevronsLeftIcon } from "svelte-feather-icons"; + + let expanded = false; + const localeSwitcher = { + show: false, + selection: "preffered" + }; + + const themeSwitcher = { + show: false, + selection: "system" + }; + + onMount(() => { + document.addEventListener("keydown", (e) => { + if (e.code === "Escape") { + expanded = false; + } + }); + document.addEventListener("click", (e) => { + if (e.target.closest("[data-blowout-toolbelt-element]") === null) { + expanded = false; + } + }); + }); +</script> +<style> + .blowout { + right: -80px; + } + + .expanded { + right: 0 !important; + } +</style> +<aside class="blowout position-fixed bg-light inner-glow shadow-xs padding-xxs bottom-50% right-0 z-index-popover {expanded ? 'expanded' : ''}" data-blowout-toolbelt-element> + <LocaleSwitcher bind:show="{localeSwitcher.show}" + glow="{false}" + data-blowout-toolbelt-element + bind:selection="{localeSwitcher.selection}"/> + <ThemeSwitcher bind:show="{themeSwitcher.show}" + glow="{false}" + data-blowout-toolbelt-element + bind:selection="{themeSwitcher.selection}"/> + <div class="flex flex-row gap-sm justify-end"> + {#if !expanded} + <div class="flex ld-switch-btn" + data-blowout-toolbelt-element + on:click={() => expanded = true}> + <ChevronsLeftIcon/> + </div> + {:else} + <div class="flex ld-switch-btn" + data-blowout-toolbelt-element + on:click={() => expanded = false}> + <ChevronsRightIcon/> + </div> + {/if} + <LocaleSwitcherIcon bind:show="{localeSwitcher.show}" + bind:selection="{localeSwitcher.selection}"/> + <ThemeSwitcherIcon bind:show="{themeSwitcher.show}" + bind:selection="{themeSwitcher.selection}"/> + </div> +</aside> diff --git a/old-apps/web-shared/src/components/breadcrumb/bread.svelte b/old-apps/web-shared/src/components/breadcrumb/bread.svelte new file mode 100644 index 0000000..244bb24 --- /dev/null +++ b/old-apps/web-shared/src/components/breadcrumb/bread.svelte @@ -0,0 +1,9 @@ +<script lang="ts"> + export type sizes = "big"|"small"; + export let size: sizes = "small"; +</script> +<nav class="breadcrumbs {size === 'small' ? 'text-sm' : 'text-lg'}"> + <ol class="flex flex-wrap items-center gap-xxs"> + <slot></slot> + </ol> +</nav> diff --git a/old-apps/web-shared/src/components/breadcrumb/crumb.svelte b/old-apps/web-shared/src/components/breadcrumb/crumb.svelte new file mode 100644 index 0000000..e540a44 --- /dev/null +++ b/old-apps/web-shared/src/components/breadcrumb/crumb.svelte @@ -0,0 +1,27 @@ +<script> + export let name; + export let withArrow = false; + export let isLink = false; +</script> +<li class="breadcrumbs__item"> + {#if isLink} + <a class="color-inherit {isLink ? 'cursor-pointer color-primary-light' : ''}" + on:click>{name}</a> + {:else} + <span class="color-inherit {isLink ? 'cursor-pointer color-primary-light' : ''}" + on:click>{name}</span> + {/if} + {#if withArrow} + <svg class="icon margin-left-xxxs color-contrast-low" + aria-hidden="true" + viewBox="0 0 16 16"> + <polyline fill="none" + stroke="currentColor" + stroke-width="1" + stroke-linecap="round" + stroke-linejoin="round" + stroke-miterlimit="10" + points="6.5,3.5 11,8 6.5,12.5 "></polyline> + </svg> + {/if} +</li> diff --git a/old-apps/web-shared/src/components/breadcrumb/index.ts b/old-apps/web-shared/src/components/breadcrumb/index.ts new file mode 100644 index 0000000..485ed7b --- /dev/null +++ b/old-apps/web-shared/src/components/breadcrumb/index.ts @@ -0,0 +1,7 @@ +import Bread from "./bread.svelte"; +import Crumb from "./crumb.svelte"; + +export { + Bread, + Crumb +}; diff --git a/old-apps/web-shared/src/components/button.svelte b/old-apps/web-shared/src/components/button.svelte new file mode 100644 index 0000000..5eaf19f --- /dev/null +++ b/old-apps/web-shared/src/components/button.svelte @@ -0,0 +1,116 @@ +<script lang="ts"> + import Icon from "$shared/components/icon.svelte"; + + export let text = ""; + export let title = ""; + export let href = ""; + export let variant: "primary"|"secondary"|"subtle" = "primary"; + export let type: "button"|"submit"|"reset" = "button"; + export let disabled = false; + export let loading = false; + export let icon = ""; + export let icon_right_aligned = false; + export let icon_width = false; + export let icon_height = false; + export let id; + export let tabindex; + export let style; + + $: shared_props = { + type: type, + id: id || null, + title: title || null, + disabled: disabled || null, + tabindex: tabindex || null, + style: style || null, + "aria-controls": ($$restProps["aria-controls"] ?? "") || null, + class: [variant === "reset" ? "reset" : `btn btn--${variant} btn--preserve-width ${loading ? "btn--state-b" : ""}`, $$restProps.class ?? ""].filter(Boolean).join(" "), + }; +</script> + +<template> + {#if href && !disabled} + <a {href} + {...shared_props} + on:click> + <span class="btn__content-a"> + {#if icon !== ""} + {#if icon_right_aligned} + {text} + <Icon class="{text ? 'margin-left-xxxs': ''}" + width={icon_width} + height={icon_height} + name={icon}/> + {:else} + <Icon class="{text ? 'margin-left-xxxs': ''}" + width={icon_width} + height={icon_height} + name={icon}/> + {text} + {/if} + {:else} + {text} + {/if} + </span> + {#if variant !== "reset" && loading} + <span class="btn__content-b"> + <svg class="icon icon--is-spinning" + aria-hidden="true" + viewBox="0 0 16 16"> + <title>Loading</title> + <g stroke-width="1" + fill="currentColor" + stroke="currentColor"> + <path d="M.5,8a7.5,7.5,0,1,1,1.91,5" + fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round"/> + </g> + </svg> + </span> + {/if} + </a> + {:else} + <button {...shared_props} + on:click> + <span class="btn__content-a"> + {#if icon !== ""} + {#if icon_right_aligned} + {text} + <Icon class="{text ? 'margin-left-xxxs': ''}" + width={icon_width} + height={icon_height} + name={icon}/> + {:else} + <Icon class="{text ? 'margin-left-xxxs': ''}" + width={icon_width} + height={icon_height} + name={icon}/> + {text} + {/if} + {:else} + {text} + {/if} + </span> + {#if variant !== "reset" && loading} + <span class="btn__content-b"> + <svg class="icon icon--is-spinning" + aria-hidden="true" + viewBox="0 0 16 16"> + <title>Loading</title> + <g stroke-width="1" + fill="currentColor" + stroke="currentColor"> + <path d="M.5,8a7.5,7.5,0,1,1,1.91,5" + fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round"/> + </g> + </svg> + </span> + {/if} + </button> + {/if} +</template> diff --git a/old-apps/web-shared/src/components/chip.svelte b/old-apps/web-shared/src/components/chip.svelte new file mode 100644 index 0000000..7fbb445 --- /dev/null +++ b/old-apps/web-shared/src/components/chip.svelte @@ -0,0 +1,50 @@ +<script> + import {IconNames} from "$shared/lib/configuration"; + import {createEventDispatcher} from "svelte"; + import Button from "./button.svelte"; + + const dispatch = createEventDispatcher(); + export let removable = false; + export let clickable = false; + export let text = ""; + export let id = ""; + export let color = ""; + export let tabindex = ""; + + function handle_remove() { + if (removable) { + dispatch("remove", { + id + }); + } + } + + function handle_click() { + if (clickable) { + dispatch("clicked", { + id + }); + } + } +</script> + +<div class="chip break-word text-sm justify-between justify-start@md {clickable ? 'chip--interactive' : ''}" + on:click={handle_click} + id={id} + style={color !== "" ? `background-color: ${color}15; border: 1px solid ${color}; color: ${color}` : ""} + {tabindex} +> + <span class="chip__label">{text}</span> + + {#if removable} + <Button class="chip__btn" + variant="reset" + style="{color !== '' ? `background-color: ${color}45;` : ''}" + {tabindex} + icon="{IconNames.x}" + icon_width="initial" + icon_height="initial" + on:click={handle_remove} + /> + {/if} +</div> diff --git a/old-apps/web-shared/src/components/details.svelte b/old-apps/web-shared/src/components/details.svelte new file mode 100644 index 0000000..6ccacb0 --- /dev/null +++ b/old-apps/web-shared/src/components/details.svelte @@ -0,0 +1,35 @@ +<script> + import {random_string} from "$shared/lib/helpers"; + + let open = false; + export let summary; + const id = "details-" + random_string(4); + + function on_toggle(event) { + open = event.target.open; + } +</script> + +<details class="details margin-bottom-sm" + on:toggle={on_toggle} + id={id}> + <summary class="details__summary" + aria-controls={id} + aria-expanded={open}> + <span class="flex items-center"> + <svg + class="icon icon--xxs margin-right-xxxs" + aria-hidden="true" + viewBox="0 0 12 12"> + <path + d="M2.783.088A.5.5,0,0,0,2,.5v11a.5.5,0,0,0,.268.442A.49.49,0,0,0,2.5,12a.5.5,0,0,0,.283-.088l8-5.5a.5.5,0,0,0,0-.824Z"/> + </svg> + <span>{summary}</span> + </span> + </summary> + <div + class="details__content text-component margin-top-xs" + aria-hidden={!open}> + <slot/> + </div> +</details> diff --git a/old-apps/web-shared/src/components/dropdown.svelte b/old-apps/web-shared/src/components/dropdown.svelte new file mode 100644 index 0000000..a28bcd3 --- /dev/null +++ b/old-apps/web-shared/src/components/dropdown.svelte @@ -0,0 +1,389 @@ +<script lang="ts"> + // @ts-ignore + import { go, highlight } from "fuzzysort"; + import { element_has_focus, random_string } from "$shared/lib/helpers"; + import Button from "$shared/components/button.svelte"; + import Chip from "$shared/components/chip.svelte"; + + export let name; + export let id; + export let maxlength; + export let placeholder = "Search"; + export let entries = []; + export let createable = false; + export let loading = false; + export let multiple = false; + export let noResultsText; + export let errorText; + export let label; + export let on_create_async = ({name: string}) => { + }; + + export const reset = () => methods.reset(); + export const select = (id: string) => methods.select_entry(id); + export const deselect = (id: string) => methods.deselect_entry(id); + + const INTERNAL_ID = "__dropdown-" + random_string(5); + + let searchInputNode; + let searchResults = []; + let searchValue = ""; + let showCreationHint = false; + let showDropdown = false; + let lastKeydownCode = ""; + let mouseIsOverDropdown = false; + let mouseIsOverComponent = false; + + $: hasSelection = entries.some((c) => c.selected === true); + $: if (searchValue.trim()) { + showCreationHint = createable && entries.every((c) => search.normalise_value(c.name) !== search.normalise_value(searchValue)); + } else { + showCreationHint = false; + entries = methods.get_sorted_array(entries); + } + + const search = { + normalise_value(value: string): string { + if (!value) { + return ""; + } + return value.toString().trim().toLowerCase(); + }, + do() { + const query = search.normalise_value(searchValue); + if (!query.trim()) { + searchResults = []; + return; + } + + const options = { + limit: 10, + allowTypo: true, + threshold: -10000, + key: "name", + }; + searchResults = go(query, entries, options); + showDropdown = true; + }, + on_input_focusout() { + if (lastKeydownCode !== "Tab" && (mouseIsOverDropdown || lastKeydownCode === "ArrowDown")) { + return; + } + const selected = entries.find((c) => c.selected === true); + if (selected && !multiple) { + searchValue = selected.name; + } + document.querySelector("#" + INTERNAL_ID + " ul li.focus")?.classList.remove("focus"); + showDropdown = false; + } + }; + + const methods = { + reset(focus_input = false) { + searchValue = ""; + const copy = entries; + for (const entry of copy) { + entry.selected = false; + } + entries = methods.get_sorted_array(copy); + if (focus_input) { + searchInputNode?.focus(); + showDropdown = true; + } else { + showDropdown = false; + } + }, + async create_entry(name) { + if (!name || !createable || loading) { + console.log("Not sending creation event due to failed preconditions", {name, createable, loading}); + return; + } + try { + await on_create_async({name}); + searchValue = ""; + loading = false; + } catch (e) { + console.error(e); + } + }, + select_entry(entry_id) { + if (!entry_id || loading) { + console.log("Not selecting entry due to failed preconditions", { + entry_id, + loading, + }); + return; + } + + const copy = entries; + let selected; + for (const entry of entries) { + if (entry.id === entry_id) { + entry.selected = true; + selected = entry; + if (multiple) { + searchValue = ""; + } else { + searchValue = entry.name; + } + } else if (!multiple) { + entry.selected = false; + } + } + entries = methods.get_sorted_array(copy); + searchInputNode?.focus(); + searchResults = []; + }, + deselect_entry(entry_id) { + if (!entry_id || loading) { + console.log("Not deselecting entry due to failed preconditions", { + entry_id, + loading, + }); + return; + } + console.log("Deselecting entry", entry_id); + + const copy = entries; + let deselected; + + for (const entry of copy) { + if (entry.id === entry_id) { + entry.selected = false; + deselected = entry; + } + } + + entries = methods.get_sorted_array(copy); + searchInputNode?.focus(); + }, + get_sorted_array(entries: Array<DropdownEntry>): Array<DropdownEntry> { + if (!entries) { + return; + } + if (entries.length < 1) { + return []; + } + if (searchValue) { + return entries; + } + return (entries as any).sort((a, b) => { + search.normalise_value(a.name).localeCompare(search.normalise_value(b.name)); + }); + }, + }; + + const windowEvents = { + on_mousemove(event) { + if (!event.target) return; + mouseIsOverDropdown = (event.target?.closest("#" + INTERNAL_ID + " .autocomplete__results") != null ?? false); + mouseIsOverComponent = (event.target?.closest("#" + INTERNAL_ID) != null ?? false); + }, + on_click(event) { + if (showDropdown && !mouseIsOverDropdown && !mouseIsOverComponent && event.target.id !== id && event.target?.htmlFor !== id) { + showDropdown = false; + } + }, + on_keydown(event) { + lastKeydownCode = event.code; + const enterPressed = event.code === "Enter"; + const backspacePressed = event.code === "Backspace"; + const arrowUpPressed = event.code === "ArrowUp"; + const spacePressed = event.code === "Space"; + const arrowDownPressed = event.code === "ArrowDown"; + const searchInputHasFocus = element_has_focus(searchInputNode); + const focusedEntry = document.querySelector("#" + INTERNAL_ID + " ul .focus"); + + if (showDropdown && (enterPressed || arrowDownPressed)) { + event.preventDefault(); + event.stopPropagation(); + } + + if (searchInputHasFocus && backspacePressed && !searchValue && entries.length > 0) { + if (entries.filter(c => c.selected === true).at(-1)?.id ?? false) { + methods.deselect_entry(entries.filter(c => c.selected === true).at(-1)?.id ?? ""); + } + return; + } + if (searchInputHasFocus && enterPressed && showCreationHint) { + methods.create_entry(searchValue.trim()); + return; + } + + if (searchInputHasFocus && !focusedEntry && arrowDownPressed) { + const firstEntry = document.querySelector("#" + INTERNAL_ID + " ul li:first-of-type"); + if (firstEntry) { + firstEntry.classList.add("focus"); + return; + } + } + + if (focusedEntry && (arrowUpPressed || arrowDownPressed)) { + if (arrowDownPressed) { + if (focusedEntry.nextElementSibling) { + focusedEntry.nextElementSibling.classList.add("focus"); + focusedEntry.nextElementSibling.scrollIntoView(); + } else { + document.querySelector("#" + INTERNAL_ID + " ul li:first-of-type").classList.add("focus"); + document.querySelector("#" + INTERNAL_ID + " ul li:first-of-type").scrollIntoView(); + } + } else if (arrowUpPressed) { + if (focusedEntry.previousElementSibling) { + focusedEntry.previousElementSibling.classList.add("focus"); + focusedEntry.previousElementSibling.scrollIntoView(); + } else { + document.querySelector("#" + INTERNAL_ID + " ul li:last-of-type").classList.add("focus"); + document.querySelector("#" + INTERNAL_ID + " ul li:last-of-type").scrollIntoView(); + } + } + focusedEntry.classList.remove("focus"); + return; + } + + if (focusedEntry && (spacePressed || enterPressed)) { + methods.select_entry(focusedEntry.dataset.id); + return; + } + + if (lastKeydownCode === "Tab" && !searchInputHasFocus) { + showDropdown = false; + } + }, + on_touchend(event) { + windowEvents.on_mousemove(event); + } + }; + + interface DropdownEntry { + name: string, + id: string, + } +</script> + +<svelte:window + on:keydown={windowEvents.on_keydown} + on:mousemove={windowEvents.on_mousemove} + on:touchend={windowEvents.on_touchend} + on:click={windowEvents.on_click} +/> + +{#if label} + <label for="{id}" + class="form-label margin-bottom-xxs">{label}</label> +{/if} + +<div class="autocomplete position-relative select-auto" + class:cursor-wait={loading} + class:autocomplete--results-visible={showDropdown} + class:select-auto--selection-done={searchValue} + id={INTERNAL_ID} +> + <!-- input --> + <div class="select-auto__input-wrapper form-control" + style="cursor: text" + on:click={() => { + if (!element_has_focus(searchInputNode)) searchInputNode.focus(); + showDropdown = true; + }} + class:multiple={multiple === true} + class:has-selection={hasSelection}> + {#if multiple === true && hasSelection} + {#each entries.filter((c) => c.selected === true) as entry} + <Chip id={entry.id} + removable={true} + tabindex="-1" + on:remove={() => methods.deselect_entry(entry.id)} + text={entry.name}/> + {/each} + {/if} + <input + class="reset width-100%" + style="outline:none;" + type="text" + {name} + {id} + {maxlength} + {placeholder} + bind:value={searchValue} + bind:this={searchInputNode} + on:input={() => search.do()} + on:click={() => (showDropdown = true)} + on:focus={() => (showDropdown = true)} + on:blur={search.on_input_focusout} + autocomplete="off" + /> + <div class="select-auto__input-icon-wrapper"> + <!-- arrow icon --> + <svg class="icon" + viewBox="0 0 16 16"> + <title>Open selection</title> + <polyline points="1 5 8 12 15 5" + fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2"/> + </svg> + + <!-- close X icon --> + <button class="reset select-auto__input-btn" + type="button" + on:click={() => reset(true)}> + <svg class="icon" + viewBox="0 0 16 16"> + <title>Reset selection</title> + <path + d="M8,0a8,8,0,1,0,8,8A8,8,0,0,0,8,0Zm3.707,10.293a1,1,0,1,1-1.414,1.414L8,9.414,5.707,11.707a1,1,0,0,1-1.414-1.414L6.586,8,4.293,5.707A1,1,0,0,1,5.707,4.293L8,6.586l2.293-2.293a1,1,0,1,1,1.414,1.414L9.414,8Z" + /> + </svg> + </button> + </div> + </div> + + {#if errorText} + <small class="color-error">{errorText}</small> + {/if} + + <!-- dropdown --> + <div class="autocomplete__results select-auto__results shadow-xs inner-glow"> + <ul on:keydown={(event) => event.code.startsWith("Arrow") && event.preventDefault()} + tabindex="-1" + class="autocomplete__list"> + {#if searchResults.length > 0} + {#each searchResults.filter((c) => !c.selected) as result} + <li class="select-auto__option padding-y-xs padding-x-sm" + data-id={result.obj.id} + on:click={(e) => methods.select_entry(e.target.dataset.id)} + tabindex="-1"> + {@html highlight(result, (open = '<span class="font-semibold">'), (close = "</span>"))} + </li> + {/each} + {:else if entries.length > 0} + {#each entries.filter((c) => !c.selected) as entry} + <li class="select-auto__option padding-y-xs padding-x-sm" + data-id={entry.id} + on:click={(e) => methods.select_entry(e.target.dataset.id)} + tabindex="-1"> + {entry.name} + </li> + {/each} + {:else} + <li class="select-auto__option text-center padding-y-xs padding-x-sm pointer-events-none" + tabindex="-1"> + {noResultsText} + </li> + {/if} + </ul> + {#if showCreationHint} + <div class="width-100% border-top border-bg-lighter padding-xxxs"> + <Button variant="reset" + type="button" + class="width-100%" + text="Press enter or click to create {searchValue.trim()}" + title="Press enter or click here to create {searchValue.trim()}" + loading={loading} + on:click={() => methods.create_entry(searchValue.trim())}/> + </div> + {/if} + </div> +</div> diff --git a/old-apps/web-shared/src/components/form/index.ts b/old-apps/web-shared/src/components/form/index.ts new file mode 100644 index 0000000..08769bd --- /dev/null +++ b/old-apps/web-shared/src/components/form/index.ts @@ -0,0 +1,5 @@ +import Textarea from "./textarea.svelte"; + +export { + Textarea +}; diff --git a/old-apps/web-shared/src/components/form/textarea.svelte b/old-apps/web-shared/src/components/form/textarea.svelte new file mode 100644 index 0000000..b313d2e --- /dev/null +++ b/old-apps/web-shared/src/components/form/textarea.svelte @@ -0,0 +1,48 @@ +<script lang="ts"> + export let id; + export let disabled = false; + export let loading = false; + export let rows = 2; + export let cols = 0; + export let name; + export let placeholder; + export let value; + export let label; + export let errorText; + + $: shared_props = { + rows: rows || null, + cols: cols || null, + name: name || null, + id: id || null, + disabled: disabled || null, + class: [`form-control ${loading ? "c-disabled loading" : ""}`, $$restProps.class ?? ""].filter(Boolean).join(" "), + }; + + let textarea; + let scrollHeight = 0; + + $:if (textarea) { + scrollHeight = textarea.scrollHeight; + } + + function on_input(event) { + event.target.style.height = "auto"; + event.target.style.height = (this.scrollHeight) + "px"; + } +</script> + +{#if label} + <label for="{id}" + class="form-label margin-bottom-xxs">{label}</label> +{/if} +<textarea {...shared_props} + {placeholder} + style="overflow-y:hidden;min-height:calc(1.5em + .75rem + 2px);{scrollHeight ? 'height:{scrollHeight}px' : ''};" + bind:value={value} + bind:this={textarea} + on:input={on_input} +></textarea> +{#if errorText} + <small class="color-error">{errorText}</small> +{/if} diff --git a/old-apps/web-shared/src/components/icon.svelte b/old-apps/web-shared/src/components/icon.svelte new file mode 100644 index 0000000..144b45d --- /dev/null +++ b/old-apps/web-shared/src/components/icon.svelte @@ -0,0 +1,87 @@ +<script> + import {IconNames} from "$shared/lib/configuration"; + + const icons = [ + { + box: 16, + name: IconNames.verticalDots, + svg: `<path d="M9.5 13a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z"/>`, + }, + { + box: 16, + name: IconNames.clock, + svg: `<path d="M8 3.5a.5.5 0 0 0-1 0V9a.5.5 0 0 0 .252.434l3.5 2a.5.5 0 0 0 .496-.868L8 8.71V3.5z"/><path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm7-8A7 7 0 1 1 1 8a7 7 0 0 1 14 0z"/>`, + }, + { + box: 21, + name: IconNames.trash, + svg: `<g fill="none" fill-rule="evenodd" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" transform="translate(3 2)"><path d="m2.5 2.5h10v12c0 1.1045695-.8954305 2-2 2h-6c-1.1045695 0-2-.8954305-2-2zm5-2c1.0543618 0 1.91816512.81587779 1.99451426 1.85073766l.00548574.14926234h-4c0-1.1045695.8954305-2 2-2z"/><path d="m.5 2.5h14"/><path d="m5.5 5.5v8"/><path d="m9.5 5.5v8"/></g>`, + }, + { + box: 21, + name: IconNames.pencilSquare, + svg: ` + <g fill="none" fill-rule="evenodd" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" transform="translate(3 3)"><path d="m14 1c.8284271.82842712.8284271 2.17157288 0 3l-9.5 9.5-4 1 1-3.9436508 9.5038371-9.55252193c.7829896-.78700064 2.0312313-.82943964 2.864366-.12506788z"/><path d="m6.5 14.5h8"/><path d="m12.5 3.5 1 1"/></g> + `, + }, + { + box: 16, + name: IconNames.x, + svg: `<path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/>`, + }, + { + box: 16, + name: IconNames.funnel, + svg: `<path d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5v-2zm1 .5v1.308l4.372 4.858A.5.5 0 0 1 7 8.5v5.306l2-.666V8.5a.5.5 0 0 1 .128-.334L13.5 3.308V2h-11z"/>`, + }, + { + box: 16, + name: IconNames.funnelFilled, + svg: `<path d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5v-2z"/>`, + }, + { + box: 16, + name: IconNames.github, + svg: ` + <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"/> + ` + }, + { + box: 21, + name: IconNames.refresh, + svg: `<g fill="none" fill-rule="evenodd" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" transform="translate(2 2)"><path d="m4.5 1.5c-2.4138473 1.37729434-4 4.02194088-4 7 0 4.418278 3.581722 8 8 8s8-3.581722 8-8-3.581722-8-8-8"/><path d="m4.5 5.5v-4h-4"/></g> ` + }, + { + box: 21, + name: IconNames.resetHard, + svg: `<g fill="none" fill-rule="evenodd" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" transform="matrix(0 1 1 0 2.5 2.5)"><path d="m13 11 3 3v-6c0-3.36502327-2.0776-6.24479706-5.0200433-7.42656457-.9209869-.36989409-1.92670197-.57343543-2.9799567-.57343543-4.418278 0-8 3.581722-8 8s3.581722 8 8 8c1.48966767 0 3.4724708-.3698516 5.0913668-1.5380762" transform="matrix(-1 0 0 -1 16 16)"/><path d="m5 5 6 6"/><path d="m11 5-6 6"/></g>` + }, + { + box: 21, + name: IconNames.arrowUp, + svg: `<g fill="none" fill-rule="evenodd" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" transform="translate(6 3)"><path d="m8.5 4.5-4-4-4.029 4"/><path d="m4.5.5v13"/></g>` + }, + { + box: 21, + name: IconNames.arrowDown, + svg: `<g fill="none" fill-rule="evenodd" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" transform="translate(6 4)"><path d="m.5 9.499 4 4.001 4-4.001"/><path d="m4.5.5v13" transform="matrix(-1 0 0 -1 9 14)"/></g>` + }, + { + box: 21, + name: IconNames.chevronDown, + svg: `<path d="m8.5.5-4 4-4-4" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" transform="translate(6 8)"/>` + } + ]; + + export let name; + export let fill = false; + export let width = "1rem"; + export let height = "1rem"; + const displayIcon = icons.find((e) => e.name === name); +</script> + +<svg class="icon {$$restProps.class ?? ''}" + style="width: {width}; height:{height}; fill: currentColor;" + viewBox="0 0 {displayIcon.box} {displayIcon.box}"> + {@html displayIcon.svg} +</svg> diff --git a/old-apps/web-shared/src/components/link-card.svelte b/old-apps/web-shared/src/components/link-card.svelte new file mode 100644 index 0000000..85738c7 --- /dev/null +++ b/old-apps/web-shared/src/components/link-card.svelte @@ -0,0 +1,47 @@ +<script> + export let text = "View"; + export let name; + export let description = null; + export let href = null; + export let target; + export let title = null; +</script> + +<a class="link-card flex flex-column bg-light cursor-pointer radius-sm inner-glow {$$restProps.class??''}" + {href} + {target} + {title} + on:click + aria-label="Link label"> + <div class="padding-sm"> + <div class="flex flex-wrap gap-xs items-center"> + <slot name="icon"></slot> + <div class="line-height-xs"> + <p class="text-lg font-semibold color-contrast-higher">{name}</p> + {#if description} + <p class="color-contrast-low margin-top-xxxs">{description}</p> + {/if} + </div> + </div> + </div> + <div class="link-card__footer margin-top-auto border-top border-contrast-lower"> + <p class="text-sm">{text}</p> + <div> + <svg class="icon icon--sm" + viewBox="0 0 24 24"> + <g fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2"> + <line x1="3" + y1="12" + x2="21" + y2="12"/> + <polyline points="15 6 21 12 15 18"/> + </g> + </svg> + </div> + </div> +</a> + diff --git a/old-apps/web-shared/src/components/locale-switcher-icon.svelte b/old-apps/web-shared/src/components/locale-switcher-icon.svelte new file mode 100644 index 0000000..d2776a1 --- /dev/null +++ b/old-apps/web-shared/src/components/locale-switcher-icon.svelte @@ -0,0 +1,16 @@ +<script> + import {GlobeIcon} from "svelte-feather-icons"; + + export let show = false; + export let selection = "preffered"; +</script> +<div data-locale-switcher-element + class="ld-switch flex"> + <button class="reset ld-switch-btn" + on:click={() => (show = !show)}> + <div class="ld-switch-btn__icon-wrapper ld-switch-btn__icon-wrapper--in" + aria-hidden="true"> + <GlobeIcon/> + </div> + </button> +</div> diff --git a/old-apps/web-shared/src/components/locale-switcher.svelte b/old-apps/web-shared/src/components/locale-switcher.svelte new file mode 100644 index 0000000..5399247 --- /dev/null +++ b/old-apps/web-shared/src/components/locale-switcher.svelte @@ -0,0 +1,62 @@ +<script> + import { CookieNames } from "$shared/lib/configuration"; + import { get_cookie } from "$shared/lib/helpers"; + import { currentLocale } from "$shared/lib/locale"; + import { onMount } from "svelte"; + + export let glow = false; + export let show = false; + export let selection = "preffered"; + export let size; + + function change(to) { + selection = to; + currentLocale.set(to); + } + + onMount(() => { + selection = get_cookie(CookieNames.locale); + document.addEventListener("keydown", (e) => { + if (e.code === "Escape") { + show = false; + } + }); + document.addEventListener("click", (e) => { + if (e.target.closest("[data-locale-switcher-element]") === null) { + show = false; + } + }); + }); +</script> + +<div class="bg-light padding-x-xs padding-bottom-xs padding-top-xxxs radius-md {glow ? 'inner-glow shadow-xs':''}" + data-locale-switcher-element + class:is-hidden={!show} + role="listbox"> + <div class="flex flex-wrap flex-column" + role="group"> + <div class="margin-bottom-xs flex-grow"> + <span class="text-xs color-contrast-medium">Language</span> + </div> + <div class="flex gap-xs flex-row"> + <div class="ld-switch-popover__option" + aria-selected="{selection === 'en' ? 'true' : 'false'}" + on:click={() => change("en")} + role="option"> + <div class="text-xs margin-top-xxxs padding-x-xxxxs">English</div> + </div> + <div class="ld-switch-popover__option" + aria-selected="{selection === 'nb' ? 'true' : 'false'}" + on:click={() => change("nb")} + role="option"> + <div class="text-xs margin-top-xxxs padding-x-xxxxs">Norsk</div> + </div> + <div class="ld-switch-popover__option" + aria-selected="{selection === 'preffered' ? 'true' : 'false'}" + on:click={() => change("preffered")} + role="option"> + <div class="text-xs margin-top-xxxs padding-x-xxxxs">Default</div> + </div> + </div> + </div> +</div> diff --git a/old-apps/web-shared/src/components/menu/index.ts b/old-apps/web-shared/src/components/menu/index.ts new file mode 100644 index 0000000..8eb7938 --- /dev/null +++ b/old-apps/web-shared/src/components/menu/index.ts @@ -0,0 +1,9 @@ +import Menu from "./menu.svelte"; +import MenuItem from "./item.svelte"; +import MenuItemSeparator from "./separator.svelte"; + +export { + Menu, + MenuItem, + MenuItemSeparator +}; diff --git a/old-apps/web-shared/src/components/menu/item.svelte b/old-apps/web-shared/src/components/menu/item.svelte new file mode 100644 index 0000000..aeb0f99 --- /dev/null +++ b/old-apps/web-shared/src/components/menu/item.svelte @@ -0,0 +1,8 @@ +<script lang="ts"> + export let danger = false; +</script> +<li role="menuitem" on:click> + <span class="menu__content {danger ? 'bg-error-lighter@hover color-white@hover' : ''}"> + <slot/> + </span> +</li> diff --git a/old-apps/web-shared/src/components/menu/menu.svelte b/old-apps/web-shared/src/components/menu/menu.svelte new file mode 100644 index 0000000..33517ab --- /dev/null +++ b/old-apps/web-shared/src/components/menu/menu.svelte @@ -0,0 +1,54 @@ +<script lang="ts"> + import {random_string} from "$shared/lib/helpers"; + + export const id = "__menu_" + random_string(3); + export let trigger: HTMLElement; + export let show = false; + + let windowInnerWidth = 0; + let windowInnerHeight = 0; + let menu: HTMLMenuElement; + + $: if (show && menu && trigger) { + const + selectedTriggerPosition = trigger.getBoundingClientRect(), + menuOnTop = (windowInnerHeight - selectedTriggerPosition.bottom) < selectedTriggerPosition.top, + left = selectedTriggerPosition.left, + right = (windowInnerWidth - selectedTriggerPosition.right), + isRight = (windowInnerWidth < selectedTriggerPosition.left + menu.offsetWidth), + vertical = menuOnTop + ? "bottom: " + (windowInnerHeight - selectedTriggerPosition.top) + "px;" + : "top: " + selectedTriggerPosition.bottom + "px;"; + + let horizontal = isRight ? "right: " + right + "px;" : "left: " + left + "px;"; + + // check right position is correct -> otherwise set left to 0 + if (isRight && (right + menu.offsetWidth) > windowInnerWidth) horizontal = ("left: " + (windowInnerWidth - menu.offsetWidth) / 2 + "px;"); + const maxHeight = menuOnTop ? selectedTriggerPosition.top - 20 : windowInnerHeight - selectedTriggerPosition.bottom - 20; + menu.setAttribute("style", horizontal + vertical + "max-height:" + Math.floor(maxHeight) + "px;"); + } + + function on_window_click(event) { + if (!event.target.closest("#" + id) && !event.target.closest("[aria-controls='" + id + "']")) show = false; + } + + function on_window_touchend(event) { + if (!event.target.closest("#" + id) && !event.target.closest("[aria-controls='" + id + "']")) show = false; + } +</script> + +<svelte:window + on:click={on_window_click} + on:touchend={on_window_touchend} + bind:innerWidth={windowInnerWidth} + bind:innerHeight={windowInnerHeight} +/> + +<menu class="menu" + id="{id}" + bind:this={menu} + class:menu--is-visible={show} + aria-expanded="{show}" + aria-haspopup="true"> + <slot name="options"/> +</menu> diff --git a/old-apps/web-shared/src/components/menu/separator.svelte b/old-apps/web-shared/src/components/menu/separator.svelte new file mode 100644 index 0000000..798dce0 --- /dev/null +++ b/old-apps/web-shared/src/components/menu/separator.svelte @@ -0,0 +1,2 @@ +<li class="menu__separator" + role="separator"></li> diff --git a/old-apps/web-shared/src/components/modal.svelte b/old-apps/web-shared/src/components/modal.svelte new file mode 100644 index 0000000..f3b633c --- /dev/null +++ b/old-apps/web-shared/src/components/modal.svelte @@ -0,0 +1,66 @@ +<script> + import {random_string} from "$shared/lib/helpers"; + + export let title = ""; + let isVisible = false; + const modal_id = "modal_" + random_string(5); + + function handle_keyup(e) { + if (e.key === "Escape") { + isVisible = false; + } + } + + export const functions = { + open() { + isVisible = true; + window.addEventListener("keyup", handle_keyup); + }, + close() { + isVisible = false; + window.removeEventListener("keyup", handle_keyup); + }, + }; +</script> + +<div class="modal modal--animate-scale flex flex-center padding-md bg-dark bg-opacity-40% {isVisible ? 'modal--is-visible' : ''}" + id={modal_id} +> + <div class="modal__content width-100% max-width-xs max-height-100% overflow-auto radius-md shadow-md bg" + role="alertdialog" + > + <header class="padding-y-sm padding-x-md flex items-center justify-between" + > + <h4 class="text-truncate">{title}</h4> + + <button class="reset modal__close-btn modal__close-btn--inner" + on:click={functions.close} + > + <svg class="icon" + viewBox="0 0 20 20"> + <title>Close modal window</title> + <g fill="none" + stroke="currentColor" + stroke-miterlimit="10" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + > + <line x1="3" + y1="3" + x2="17" + y2="17"/> + <line x1="17" + y1="3" + x2="3" + y2="17"/> + </g> + </svg> + </button> + </header> + + <div class="padding-bottom-md padding-x-md"> + <slot/> + </div> + </div> +</div> diff --git a/old-apps/web-shared/src/components/pre-header.svelte b/old-apps/web-shared/src/components/pre-header.svelte new file mode 100644 index 0000000..87a19b1 --- /dev/null +++ b/old-apps/web-shared/src/components/pre-header.svelte @@ -0,0 +1,37 @@ +<script> + export let closable = true; + export let show = false; +</script> + +<div class="pre-header padding-y-xs" style="{show ? '' : 'display:none'}"> + <div class="container max-width-lg position-relative"> + <div class="text-component text-sm padding-right-lg"> + <p> + <slot/> + </p> + </div> + {#if closable} + <button class="reset pre-header__close-btn" + on:click={(event) => event.target.closest(".pre-header")?.remove()}> + <svg class="icon" + viewBox="0 0 20 20"> + <title>Close header banner</title> + <g fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2"> + <line x1="4" + y1="4" + x2="16" + y2="16"/> + <line x1="16" + y1="4" + x2="4" + y2="16"/> + </g> + </svg> + </button> + {/if} + </div> +</div> diff --git a/old-apps/web-shared/src/components/screens/GeneralErrorScreen.svelte b/old-apps/web-shared/src/components/screens/GeneralErrorScreen.svelte new file mode 100644 index 0000000..dd4b5bd --- /dev/null +++ b/old-apps/web-shared/src/components/screens/GeneralErrorScreen.svelte @@ -0,0 +1,7 @@ +<script> + export let message; + export let status; +</script> + +<h1>{status}</h1> +<p>{message}</p> diff --git a/old-apps/web-shared/src/components/screens/NotFoundScreen.svelte b/old-apps/web-shared/src/components/screens/NotFoundScreen.svelte new file mode 100644 index 0000000..69d55af --- /dev/null +++ b/old-apps/web-shared/src/components/screens/NotFoundScreen.svelte @@ -0,0 +1,161 @@ +<style> + .fof__animation svg { + display: block; + max-width: 520px; + margin-left: auto; + margin-right: auto; + } + + #i-fof-browser { + -webkit-transform-origin: 260px 304px; + transform-origin: 260px 304px; + -webkit-animation: i-fof-browser 4s infinite; + animation: i-fof-browser 4s infinite; + } + + #i-fof-shadow { + -webkit-transform-origin: 282px 410px; + transform-origin: 282px 410px; + -webkit-animation: i-fof-shadow 4s infinite; + animation: i-fof-shadow 4s infinite; + } + + @-webkit-keyframes i-fof-browser { + 0%, 100% { + -webkit-transform: translateY(0) scale(1); + transform: translateY(0) scale(1); + } + 50% { + -webkit-transform: translateY(-10%) scale(0.9); + transform: translateY(-10%) scale(0.9); + } + } + + @keyframes i-fof-browser { + 0%, 100% { + -webkit-transform: translateY(0) scale(1); + transform: translateY(0) scale(1); + } + 50% { + -webkit-transform: translateY(-10%) scale(0.9); + transform: translateY(-10%) scale(0.9); + } + } + + @-webkit-keyframes i-fof-shadow { + 0%, 100% { + -webkit-transform: scale(1); + transform: scale(1); + } + 50% { + -webkit-transform: scale(0.8); + transform: scale(0.8); + } + } + + @keyframes i-fof-shadow { + 0%, 100% { + -webkit-transform: scale(1); + transform: scale(1); + } + 50% { + -webkit-transform: scale(0.8); + transform: scale(0.8); + } + } +</style> + +<section class="fof"> + <div class="container max-width-sm"> + <div class="text-component text-center margin-bottom-lg"> + <h1>Page not found</h1> + <p>Sorry, but the page you were looking for could not be found.</p> + <p><a href="/">Go to homepage</a>.</p> + </div> + + <div class="fof__animation" + aria-lable="404 animation"> + <svg id="i-fof" + viewBox="0 0 520 450"> + <g id="i-fof-ship"> + <path id="i-fof-capsule" + d="M260,9a53,53,0,0,0-53,53H313A53,53,0,0,0,260,9Z" + fill="var(--color-contrast-lower)"/> + <path id="i-fof-ship-top" + d="M448,73H72s78-37,188-37S448,73,448,73Z" + fill="var(--color-contrast-low)"/> + <path id="i-fof-ship-bottom" + d="M448,73A1185.633,1185.633,0,0,0,72,73c80.173,34.484,144.2,37,188,37C370,110,448,73,448,73Z" + fill="var(--color-contrast-high)"/> + </g> + <g id="i-fof-browser"> + <rect id="i-fof-browser-canvas" + x="179" + y="243" + width="160" + height="109" + rx="6" + transform="translate(131.361 -75.723) rotate(22.162)" + fill="var(--color-contrast-higher)"/> + <g id="i-fof-browser-dots" + fill="var(--color-bg)"> + <circle cx="211.713" + cy="228.029" + r="3"/> + <circle cx="221.9" + cy="232.179" + r="3"/> + <circle cx="232.088" + cy="236.328" + r="3"/> + </g> + <rect id="i-fof-browser-body" + x="180.737" + y="258.557" + width="152" + height="89" + rx="2" + transform="translate(133.29 -74.459) rotate(22.162)" + fill="var(--color-bg)"/> + <g id="i-fof-emoji"> + <path d="M248.626,322.968A22,22,0,1,1,277.3,310.893,22,22,0,0,1,248.626,322.968Z" + fill="#ffd764"/> + <path d="M264.3,311a7,7,0,1,1,9.124-3.843A7,7,0,0,1,264.3,311Z" + fill="#fff"/> + <path d="M245.778,303.452a7,7,0,1,1,9.123-3.842A7,7,0,0,1,245.778,303.452Z" + fill="#fff"/> + <path d="M258.5,317.274l-12.966-5.281a1,1,0,0,1,.755-1.853l12.966,5.282a1,1,0,0,1-.755,1.852Z" + fill="#444"/> + <path d="M247.287,299.747a3,3,0,1,1,3.91-1.646A3,3,0,0,1,247.287,299.747Z" + fill="#444"/> + <path d="M265.809,307.292a3,3,0,1,1,3.91-1.647A3,3,0,0,1,265.809,307.292Z" + fill="#444"/> + </g> + </g> + <polygon id="i-fof-radar-body" + points="415 410 105 410 196 106 324 106 415 410" + fill="#4ad36b" + opacity="0.5"/> + <ellipse id="i-fof-radar-bottom" + cx="260" + cy="410" + rx="155" + ry="28" + fill="#4ad36b"/> + <ellipse id="i-fof-shadow" + cx="282" + cy="410" + rx="72" + ry="10" + opacity="0.4" + fill="var(--color-black)"/> + <ellipse id="i-fof-radar-top" + cx="260" + cy="106" + rx="64" + ry="6" + fill="#4ad36b"/> + </svg> + </div> + </div> +</section> diff --git a/old-apps/web-shared/src/components/stopwatch.svelte b/old-apps/web-shared/src/components/stopwatch.svelte new file mode 100644 index 0000000..0e641e8 --- /dev/null +++ b/old-apps/web-shared/src/components/stopwatch.svelte @@ -0,0 +1,196 @@ +<script lang="ts"> + import Button from "$shared/components/button.svelte"; + import { Textarea } from "$shared/components/form"; + import { StorageKeys } from "$shared/lib/configuration"; + import { loadLocaleAsync } from "$shared/lib/i18n/i18n-util.async"; + import { i18nObject } from "$shared/lib/i18n/i18n-util"; + import { currentLocale, preffered_or_default } from "$shared/lib/locale"; + import { StoreType, writable_persistent } from "$shared/lib/persistent-store"; + import { Temporal } from "@js-temporal/polyfill"; + import { createEventDispatcher, onMount } from "svelte"; + + const state = writable_persistent({ + initialState: { + hours: 0, + minutes: 0, + seconds: 0, + startTime: null as Temporal.PlainDateTime, + lastStep: null as Temporal.PlainDateTime, + isRunning: false, + intervalId: 0, + note: "", + }, + options: { + store: StoreType.LOCAL + }, + name: StorageKeys.stopwatch, + }); + + let timeString; + let LL = i18nObject($currentLocale); + + $: if ($state.hours || $state.minutes || $state.seconds) { + timeString = $state.hours.toLocaleString(undefined, {minimumIntegerDigits: 2}) + + ":" + $state.minutes.toLocaleString(undefined, {minimumIntegerDigits: 2}) + + ":" + $state.seconds.toLocaleString(undefined, {minimumIntegerDigits: 2}); + } else { + timeString = "--:--:--"; + } + + currentLocale.subscribe(async locale => { + if (locale === "preffered") locale = preffered_or_default(); + await loadLocaleAsync(locale); + LL = i18nObject(locale); + }); + + onMount(async () => { + start_if_running(); + await loadLocaleAsync($currentLocale); + LL = i18nObject($currentLocale); + }); + + function start_if_running() { + if ($state.isRunning) { + if (Temporal.PlainDateTime.compare($state.lastStep, Temporal.Now.plainDateTimeISO()) == -1) { + const duration = Temporal.Now.plainDateTimeISO().since($state.lastStep, {smallestUnit: "second"}); + console.log("lastStep", $state.lastStep.toString()); + console.log("duration", duration.toString()); + console.log(duration.seconds); + // for (let i = 0; i < steps; i++) { + // step(); + // } + } + clearInterval($state.intervalId); + $state.intervalId = setInterval(step, 1000); + } + } + + window.addEventListener("focus", () => { + start_if_running(); + }); + + const dispatch = createEventDispatcher(); + + function step() { + $state.seconds = $state.seconds + 1; + + if ($state.seconds == 60) { + $state.minutes = $state.minutes + 1; + $state.seconds = 0; + } + + if ($state.minutes == 60) { + $state.hours = $state.hours + 1; + $state.minutes = 0; + $state.seconds = 0; + } + + if (!$state.startTime) $state.startTime = Temporal.Now.plainDateTimeISO(); + $state.lastStep = Temporal.Now.plainDateTimeISO(); + } + + function reset() { + clearInterval($state.intervalId); + $state.isRunning = false; + $state.hours = 0; + $state.minutes = 0; + $state.seconds = 0; + $state.startTime = null; + $state.intervalId = 0; + $state.note = ""; + } + + let roundUpToNearest = 30; + let roundDownToNearest = 30; + + function on_round_up() { + const newTime = Temporal.PlainTime + .from({hour: $state.hours, minute: $state.minutes, second: $state.seconds}) + .round({ + roundingIncrement: roundUpToNearest, + smallestUnit: "minute", + roundingMode: "ceil" + }); + $state.hours = newTime.hour; + $state.minutes = newTime.minute; + $state.seconds = newTime.second; + } + + function on_round_down() { + const newTime = Temporal.PlainTime + .from({hour: $state.hours, minute: $state.minutes, second: $state.seconds,}) + .round({ + roundingIncrement: roundDownToNearest, + smallestUnit: "minute", + roundingMode: "trunc" + }); + $state.hours = newTime.hour; + $state.minutes = newTime.minute; + $state.seconds = newTime.second; + } + + function on_start_stop() { + if ($state.isRunning) { + clearInterval($state.intervalId); + $state.isRunning = false; + return; + } + step(); + $state.intervalId = setInterval(step, 1000); + $state.isRunning = true; + } + + function on_create_entry() { + if (!$state.startTime) return; + const plainStartTime = Temporal.PlainDateTime.from($state.startTime); + dispatch("create", { + from: plainStartTime, + to: plainStartTime.add({hours: $state.hours, minutes: $state.minutes, seconds: $state.seconds}), + description: $state.note + }); + reset(); + } +</script> + +<div class="grid"> + <div class="col-6"> + <slot name="header"></slot> + <pre class="text-xxl padding-y-sm">{timeString}</pre> + </div> + <div class="col-6 flex align-bottom flex-column text-xs"> + <Button title="{$state.isRunning ? LL.stopwatch.stop() : LL.stopwatch.start()}" + text="{$state.isRunning ? LL.stopwatch.stop() : LL.stopwatch.start()}" + variant="link" + on:click={on_start_stop}/> + + {#if $state.startTime} + <Button title="{LL.stopwatch.reset()}" + text="{LL.stopwatch.reset()}" + variant="link" + class="bg-error-lighter@hover color-white@hover" + on:click={reset}/> + {#if !$state.isRunning} + <Button title="{LL.stopwatch.roundUp()}" + text="{LL.stopwatch.roundUp()}" + variant="link" + on:click={on_round_up}/> + <Button title="{LL.stopwatch.roundDown()}" + text="{LL.stopwatch.roundDown()}" + variant="link" + on:click={on_round_down}/> + {#if $state.minutes > 0 || $state.hours > 0} + <Button title="{LL.stopwatch.createEntry()}" + text="{LL.stopwatch.createEntry()}" + variant="link" + on:click={on_create_entry}/> + {/if} + {/if} + {/if} + </div> +</div> + +<Textarea class="width-100% margin-top-xs" + placeholder="{LL.stopwatch.whatsYourFocus()}" + rows="1" + bind:value={$state.note} +/> diff --git a/old-apps/web-shared/src/components/table/index.ts b/old-apps/web-shared/src/components/table/index.ts new file mode 100644 index 0000000..8390c0e --- /dev/null +++ b/old-apps/web-shared/src/components/table/index.ts @@ -0,0 +1,15 @@ +import TablePaginator from "./paginator.svelte"; +import Table from "./table.svelte"; +import THead from "./thead.svelte"; +import TBody from "./tbody.svelte"; +import TCell from "./tcell.svelte"; +import TRow from "./trow.svelte"; + +export { + TablePaginator, + Table, + THead, + TBody, + TCell, + TRow +}; diff --git a/old-apps/web-shared/src/components/table/paginator.svelte b/old-apps/web-shared/src/components/table/paginator.svelte new file mode 100644 index 0000000..53c6392 --- /dev/null +++ b/old-apps/web-shared/src/components/table/paginator.svelte @@ -0,0 +1,101 @@ +<script> + import {createEventDispatcher, onMount} from "svelte"; + import {restrict_input_to_numbers} from "$shared/lib/helpers"; + + const dispatch = createEventDispatcher(); + export let page = 1; + export let pageCount = 1; + let prevCount = page; + let canIncrement = false; + let canDecrement = false; + $: canIncrement = page < pageCount; + $: canDecrement = page > 1; + + onMount(() => { + restrict_input_to_numbers(document.querySelector("#curr-page")); + }); + + function increment() { + if (canIncrement) { + page++; + } + } + + function decrement() { + if (canDecrement) { + page--; + } + } + + $: if (page) { + handle_change(); + } + + function handle_change() { + if (page === prevCount) { + return; + } + prevCount = page; + if (page > pageCount) { + page = pageCount; + } + dispatch("value_change", { + newValue: page, + }); + } +</script> + +<nav class="pagination" + aria-label="Pagination"> + <ul class="pagination__list flex flex-wrap gap-xxxs justify-center justify-end@md"> + <li> + <button on:click={decrement} + class="reset pagination__item {canDecrement ? '' : 'c-disabled'}"> + <svg class="icon icon--xs flip-x" + viewBox="0 0 16 16" + ><title>Go to previous page</title> + <polyline + points="6 2 12 8 6 14" + fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + /> + </svg> + </button> + </li> + + <li> + <span class="pagination__jumper flex items-center"> + <input + aria-label="Page number" + class="form-control" + id="curr-page" + type="text" + on:change={handle_change} + value={page} + /> + <em>of {pageCount}</em> + </span> + </li> + + <li> + <button on:click={increment} + class="reset pagination__item {canIncrement ? '' : 'c-disabled'}"> + <svg class="icon icon--xs" + viewBox="0 0 16 16" + ><title>Go to next page</title> + <polyline + points="6 2 12 8 6 14" + fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + /> + </svg> + </button> + </li> + </ul> +</nav> diff --git a/old-apps/web-shared/src/components/table/table.svelte b/old-apps/web-shared/src/components/table/table.svelte new file mode 100644 index 0000000..4acbf37 --- /dev/null +++ b/old-apps/web-shared/src/components/table/table.svelte @@ -0,0 +1,3 @@ +<table class="int-table {$$restProps.class ?? ''}"> + <slot/> +</table> diff --git a/old-apps/web-shared/src/components/table/tbody.svelte b/old-apps/web-shared/src/components/table/tbody.svelte new file mode 100644 index 0000000..f0617fa --- /dev/null +++ b/old-apps/web-shared/src/components/table/tbody.svelte @@ -0,0 +1,3 @@ +<tbody class="int-table__body {$$restProps.class ?? ''}"> +<slot/> +</tbody> diff --git a/old-apps/web-shared/src/components/table/tcell.svelte b/old-apps/web-shared/src/components/table/tcell.svelte new file mode 100644 index 0000000..76f500f --- /dev/null +++ b/old-apps/web-shared/src/components/table/tcell.svelte @@ -0,0 +1,23 @@ +<script lang="ts"> + export let thScope: "row"|"col"|"rowgroup"|"colgroup"|""; + export let colspan = ""; + export let type: "th"|"td" = "td"; + export let style; + + $: shared_props = { + colspan: colspan || null, + style: style || null, + class: [type === "th" ? "int-table__cell--th" : "", "int-table__cell", $$restProps.class ?? ""].filter(Boolean).join(" "), + }; +</script> +{#if type === "th"} + <th {thScope} + {...shared_props}> + <slot/> + </th> +{/if} +{#if type === "td"} + <td {...shared_props}> + <slot/> + </td> +{/if} diff --git a/old-apps/web-shared/src/components/table/thead.svelte b/old-apps/web-shared/src/components/table/thead.svelte new file mode 100644 index 0000000..aa20bf0 --- /dev/null +++ b/old-apps/web-shared/src/components/table/thead.svelte @@ -0,0 +1,10 @@ +<script lang="ts"> + import TRow from "./trow.svelte"; +</script> + + +<thead class="int-table__header {$$restProps.class ?? ''}"> +<TRow> + <slot/> +</TRow> +</thead> diff --git a/old-apps/web-shared/src/components/table/trow.svelte b/old-apps/web-shared/src/components/table/trow.svelte new file mode 100644 index 0000000..35b34bb --- /dev/null +++ b/old-apps/web-shared/src/components/table/trow.svelte @@ -0,0 +1,6 @@ +<script> + export let dataId; +</script> +<tr class="int-table__row {$$restProps.class ?? ''}" data-id={dataId}> + <slot/> +</tr> diff --git a/old-apps/web-shared/src/components/theme-switcher-icon.svelte b/old-apps/web-shared/src/components/theme-switcher-icon.svelte new file mode 100644 index 0000000..1531ab2 --- /dev/null +++ b/old-apps/web-shared/src/components/theme-switcher-icon.svelte @@ -0,0 +1,248 @@ +<script> + export let show = false; + export let selection = ""; +</script> + +<div class="ld-switch flex" + data-theme-switcher-element> + <button class="reset ld-switch-btn" + on:click={() => show =!show}> + <span class="sr-only">{selection}</span> + <div class="ld-switch-btn__icon-wrapper ld-switch-btn__icon-wrapper--in" + aria-hidden="true"> + {#if selection === "dark"} + <svg class="icon ld-switch-btn__icon" + viewBox="0 0 20 20"> + <title>dark</title> + <g fill="currentColor"> + <path d="M11.964 3.284c.021.237.036.474.036.716a8 8 0 0 1-8 8c-.242 0-.479-.015-.716-.036a7 7 0 1 0 8.68-8.68z" + fill-opacity=".2" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2"></path> + <path d="M7 4a.979.979 0 0 1-1-1 1 1 0 0 0-2 0 .979.979 0 0 1-1 1 1 1 0 0 0 0 2 .979.979 0 0 1 1 1 1 1 0 0 0 2 0 .979.979 0 0 1 1-1 1 1 0 0 0 0-2z"></path> + </g> + </svg> + {:else if selection === "light"} + <svg class="icon ld-switch-btn__icon" + viewBox="0 0 20 20"><title>light</title> + <g fill="currentColor"> + <circle cx="10" + cy="10" + r="4" + fill-opacity=".2" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2"></circle> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M10 1v1.5"></path> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M16.364 3.636l-1.061 1.061"></path> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M19 10h-1.5"></path> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M16.364 16.364l-1.061-1.061"></path> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M10 19v-1.5"></path> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M3.636 16.364l1.061-1.061"></path> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M1 10h1.5"></path> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M3.636 3.636l1.061 1.061"></path> + </g> + </svg> + {:else } + <svg class="icon ld-switch-btn__icon" + viewBox="0 0 20 20"><title>light-auto</title> + <g fill="currentColor"> + <path d="M10 14a4 4 0 1 1 3.465-6" + fill-opacity=".2" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2"></path> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M12 18l2.5-7h1l2.5 7"></path> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M12.714 16h4.572"></path> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M10 1v1.5"></path> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M16.364 3.636l-1.061 1.061"></path> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M3.636 16.364l1.061-1.061"></path> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M1 10h1.5"></path> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M3.636 3.636l1.061 1.061"></path> + </g> + </svg> + {/if} + </div> + + <div class="ld-switch-btn__icon-wrapper js-ld-switch-icon" + aria-hidden="true"> + {#if selection === "dark"} + <svg class="icon ld-switch-btn__icon" + viewBox="0 0 20 20"> + <title>dark</title> + <g fill="currentColor"> + <path d="M11.964 3.284c.021.237.036.474.036.716a8 8 0 0 1-8 8c-.242 0-.479-.015-.716-.036a7 7 0 1 0 8.68-8.68z" + fill-opacity=".2" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2"></path> + <path d="M7 4a.979.979 0 0 1-1-1 1 1 0 0 0-2 0 .979.979 0 0 1-1 1 1 1 0 0 0 0 2 .979.979 0 0 1 1 1 1 1 0 0 0 2 0 .979.979 0 0 1 1-1 1 1 0 0 0 0-2z"></path> + </g> + </svg> + {:else if selection === "light"} + <svg class="icon ld-switch-btn__icon" + viewBox="0 0 20 20"> + <title>light-auto</title> + <g fill="currentColor"> + <path d="M10 14a4 4 0 1 1 3.465-6" + fill-opacity=".2" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2"></path> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M12 18l2.5-7h1l2.5 7"></path> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M12.714 16h4.572"></path> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M10 1v1.5"></path> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M16.364 3.636l-1.061 1.061"></path> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M3.636 16.364l1.061-1.061"></path> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M1 10h1.5"></path> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M3.636 3.636l1.061 1.061"></path> + </g> + </svg> + {:else } + <svg class="icon ld-switch-btn__icon" + viewBox="0 0 20 20"> + <title>dark-auto</title> + <g fill="currentColor"> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M12 18l2.5-7h1l2.5 7"></path> + <path fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M12.714 16h4.572"></path> + <path d="M12.146 10.159A2.5 2.5 0 0 1 14.5 8.5h1a2.5 2.5 0 0 1 1.412.441 7 7 0 0 0-4.948-5.657c.021.237.036.474.036.716a8 8 0 0 1-8 8c-.242 0-.479-.015-.716-.036a6.99 6.99 0 0 0 6.427 5.012z" + fill-opacity=".2"></path> + <path d="M16.71 8a7.015 7.015 0 0 0-4.746-4.716c.021.237.036.474.036.716a8 8 0 0 1-8 8c-.242 0-.479-.015-.716-.036A7.006 7.006 0 0 0 9 16.929" + fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2"></path> + <path d="M7 4a.979.979 0 0 1-1-1 1 1 0 0 0-2 0 .979.979 0 0 1-1 1 1 1 0 0 0 0 2 .979.979 0 0 1 1 1 1 1 0 0 0 2 0 .979.979 0 0 1 1-1 1 1 0 0 0 0-2z"></path> + </g> + </svg> + {/if} + </div> + </button> +</div> diff --git a/old-apps/web-shared/src/components/theme-switcher.svelte b/old-apps/web-shared/src/components/theme-switcher.svelte new file mode 100644 index 0000000..6f86875 --- /dev/null +++ b/old-apps/web-shared/src/components/theme-switcher.svelte @@ -0,0 +1,215 @@ +<script lang="ts"> + import {base_domain, CookieNames} from "$shared/lib/configuration"; + import {get_cookie, set_cookie} from "$shared/lib/helpers"; + import {onMount} from "svelte"; + + type theme = "system"|"dark"|"light"; + + export let show = false; + export let glow = false; + export let selection: theme = "system"; + export let size; + let prefers = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; + onMount(() => { + selection = get_cookie(CookieNames.theme) as theme; + document.addEventListener("keydown", (e) => { + if (e.code === "Escape") show = false; + }); + document.addEventListener("click", (e: any) => { + if (e.target.closest("[data-theme-switcher-element]") === null) show = false; + }); + }); + let html = document.querySelector("html"); + + window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (event) => { + prefers = event.matches ? "dark" : "light"; + }); + + $: switch (selection || prefers) { + case "system": + html.dataset.theme = prefers; + break; + case "light": + html.dataset.theme = "light"; + break; + case "dark": + html.dataset.theme = "dark"; + break; + } + + function change(to: theme) { + selection = to; + set_cookie(CookieNames.theme, selection, base_domain()); + } +</script> + +<div class="bg-light padding-x-xs padding-bottom-xs padding-top-xxxs radius-md {glow ? 'inner-glow shadow-xs' : ''}" + class:is-hidden={!show} + data-theme-switcher-element + role="listbox"> + <div class="flex flex-wrap flex-column" + role="group"> + <div class="margin-bottom-xs flex-grow"> + <span class="text-xs color-contrast-medium">Appearance</span> + </div> + <div class="flex gap-xs flex-row"> + <div class="ld-switch-popover__option" + aria-selected="{selection === 'dark' ? 'true' : 'false'}" + role="option"> + <figure class="radius-md inner-glow" + on:click={() => change("dark")}> + <svg id="Layer_1" + class="block radius-inherit" + data-name="Layer 1" + xmlns="http://www.w3.org/2000/svg" + width="70" + height="50" + viewBox="0 0 70 50"> + <rect width="70" + height="50" + fill="#22232a"/> + <path d="M14,10H70V50H10V14A4,4,0,0,1,14,10Z" + fill="#5a5c63"/> + <circle cx="18" + cy="18" + r="3" + fill="#22232a"/> + <circle cx="27" + cy="18" + r="3" + fill="#22232a"/> + <circle cx="36" + cy="18" + r="3" + fill="#22232a"/> + <rect x="17" + y="28" + width="46" + height="3" + rx="1" + fill="#fafaff"/> + <rect x="17" + y="34" + width="46" + height="3" + rx="1" + fill="#fafaff"/> + <rect x="17" + y="40" + width="30" + height="3" + rx="1" + fill="#fafaff"/> + </svg> + </figure> + <div class="text-xs margin-top-xxxs padding-x-xxxxs">Dark</div> + </div> + <div class="ld-switch-popover__option" + aria-selected="{selection === 'light' ? 'true' : 'false'}" + role="option"> + <figure class="radius-md inner-glow" + on:click={() => change("light")}> + <svg id="Layer_1" + class="block radius-inherit" + data-name="Layer 1" + xmlns="http://www.w3.org/2000/svg" + width="70" + height="50" + viewBox="0 0 70 50"> + <rect width="70" + height="50" + fill="#e5e5e6"/> + <path d="M14,10H70a0,0,0,0,1,0,0V50a0,0,0,0,1,0,0H10a0,0,0,0,1,0,0V14A4,4,0,0,1,14,10Z" + fill="#fff"/> + <circle cx="18" + cy="18" + r="3" + fill="#e5e5e6"/> + <circle cx="27" + cy="18" + r="3" + fill="#e5e5e6"/> + <circle cx="36" + cy="18" + r="3" + fill="#e5e5e6"/> + <rect x="17" + y="28" + width="46" + height="3" + rx="1" + fill="#38393e"/> + <rect x="17" + y="34" + width="46" + height="3" + rx="1" + fill="#38393e"/> + <rect x="17" + y="40" + width="30" + height="3" + rx="1" + fill="#38393e"/> + </svg> + </figure> + <div class="text-xs margin-top-xxxs padding-x-xxxxs">Light</div> + </div> + <div class="ld-switch-popover__option" + aria-selected="{selection === 'system' ? 'true' : 'false'}" + role="option"> + <figure class="radius-md inner-glow" + on:click={() => change("system")}> + <svg id="Layer_1" + data-name="Layer 1" + xmlns="http://www.w3.org/2000/svg" + class="block radius-inherit" + width="70" + height="50" + viewBox="0 0 70 50"> + <rect width="35" + height="50" + fill="#e5e5e6"/> + <path d="M14,10H35a0,0,0,0,1,0,0V50a0,0,0,0,1,0,0H10a0,0,0,0,1,0,0V14A4,4,0,0,1,14,10Z" + fill="#fff"/> + <circle cx="18" + cy="18" + r="3" + fill="#e5e5e6"/> + <circle cx="27" + cy="18" + r="3" + fill="#e5e5e6"/> + <path d="M18,28H35a0,0,0,0,1,0,0v3a0,0,0,0,1,0,0H18a1,1,0,0,1-1-1V29A1,1,0,0,1,18,28Z" + fill="#38393e"/> + <path d="M18,34H35a0,0,0,0,1,0,0v3a0,0,0,0,1,0,0H18a1,1,0,0,1-1-1V35A1,1,0,0,1,18,34Z" + fill="#38393e"/> + <path d="M18,40H35a0,0,0,0,1,0,0v3a0,0,0,0,1,0,0H18a1,1,0,0,1-1-1V41A1,1,0,0,1,18,40Z" + fill="#38393e"/> + <rect x="35" + width="35" + height="50" + fill="#22232a"/> + <path d="M49,10H70V50H45V14A4,4,0,0,1,49,10Z" + fill="#5a5c63"/> + <circle cx="53" + cy="18" + r="3" + fill="#22232a"/> + <circle cx="62" + cy="18" + r="3" + fill="#22232a"/> + <path d="M53,28H70a0,0,0,0,1,0,0v3a0,0,0,0,1,0,0H53a1,1,0,0,1-1-1V29A1,1,0,0,1,53,28Z" + fill="#fafaff"/> + <path d="M53,34H70a0,0,0,0,1,0,0v3a0,0,0,0,1,0,0H53a1,1,0,0,1-1-1V35A1,1,0,0,1,53,34Z" + fill="#fafaff"/> + <path d="M53,40H70a0,0,0,0,1,0,0v3a0,0,0,0,1,0,0H53a1,1,0,0,1-1-1V41A1,1,0,0,1,53,40Z" + fill="#fafaff"/> + </svg> + </figure> + <div class="text-xs margin-top-xxxs padding-x-xxxxs">System</div> + </div> + </div> + </div> +</div> diff --git a/old-apps/web-shared/src/components/tile.svelte b/old-apps/web-shared/src/components/tile.svelte new file mode 100644 index 0000000..b8e9cdf --- /dev/null +++ b/old-apps/web-shared/src/components/tile.svelte @@ -0,0 +1,4 @@ +<section class="bg-light radius-sm padding-sm inner-glow shadow-xs {$$restProps.class??''}" + style="height: fit-content;"> + <slot/> +</section> diff --git a/old-apps/web-shared/src/components/user-menu.svelte b/old-apps/web-shared/src/components/user-menu.svelte new file mode 100644 index 0000000..c0195f4 --- /dev/null +++ b/old-apps/web-shared/src/components/user-menu.svelte @@ -0,0 +1,99 @@ +<script> + import {onMount} from "svelte"; + import {Menu, MenuItem, MenuItemSeparator} from "./menu"; + + let userMenuTrigger; + let showUserMenu = false; + + export let avatar = ""; + export let name; + export let secondary = ""; + let userMenuId; + + onMount(() => { + userMenuTrigger = document.getElementById("open-user-menu"); + }); +</script> + +<button class="reset user-menu-control" + id="open-user-menu" + aria-controls="{userMenuId}" + on:click={() => showUserMenu = true}> + {#if avatar} + <figure class="user-menu-control__img-wrapper radius-50%"> + <img class="user-menu-control__img" + src="{avatar}" + alt="User picture"> + </figure> + {/if} + + <div class="margin-x-xs user-menu__meta"> + <p class="user-menu__meta-title text-sm line-height-1 padding-y-xxxxs font-semibold color-contrast-higher text-truncate">{name}</p> + {#if secondary} + <p class="text-xs color-contrast-medium line-height-1 padding-bottom-xxxxs">{secondary}</p> + {/if} + </div> + + <svg class="icon icon--xxs" + aria-hidden="true" + viewBox="0 0 12 12"> + <polyline points="1 4 6 9 11 4" + fill="none" + stroke="currentColor" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2"/> + </svg> +</button> + +<Menu trigger={userMenuTrigger} + bind:id={userMenuId} + bind:show="{showUserMenu}"> + <div slot="options"> + <MenuItem> + <svg class="icon menu__icon" + aria-hidden="true" + viewBox="0 0 16 16"> + <circle cx="8" + cy="3.5" + r="3.5"/> + <path d="M14.747,14.15a6.995,6.995,0,0,0-13.494,0A1.428,1.428,0,0,0,1.5,15.4a1.531,1.531,0,0,0,1.209.6H13.288a1.531,1.531,0,0,0,1.209-.6A1.428,1.428,0,0,0,14.747,14.15Z"/> + </svg> + <span>Profile</span> + + </MenuItem> + + <MenuItem> + <svg class="icon menu__icon" + aria-hidden="true" + viewBox="0 0 16 16"> + <g fill="currentColor"> + <path d="M15.135,6.784c-1.303-0.326-1.921-1.818-1.23-2.969c0.322-0.536,0.225-0.998-0.094-1.316l-0.31-0.31 c-0.318-0.318-0.78-0.415-1.316-0.094c-1.152,0.691-2.644,0.073-2.969-1.23C9.065,0.258,8.669,0,8.219,0H7.781 c-0.45,0-0.845,0.258-0.997,0.865c-0.326,1.303-1.818,1.921-2.969,1.23C3.279,1.773,2.816,1.87,2.498,2.188l-0.31,0.31 C1.87,2.816,1.773,3.279,2.095,3.815c0.691,1.152,0.073,2.644-1.23,2.969C0.26,6.935,0,7.33,0,7.781v0.438 c0,0.45,0.258,0.845,0.865,0.997c1.303,0.326,1.921,1.818,1.23,2.969c-0.322,0.536-0.225,0.998,0.094,1.316l0.31,0.31 c0.319,0.319,0.782,0.415,1.316,0.094c1.152-0.691,2.644-0.073,2.969,1.23C6.935,15.742,7.331,16,7.781,16h0.438 c0.45,0,0.845-0.258,0.997-0.865c0.326-1.303,1.818-1.921,2.969-1.23c0.535,0.321,0.997,0.225,1.316-0.094l0.31-0.31 c0.318-0.318,0.415-0.78,0.094-1.316c-0.691-1.152-0.073-2.644,1.23-2.969C15.742,9.065,16,8.669,16,8.219V7.781 C16,7.33,15.74,6.935,15.135,6.784z M8,11c-1.657,0-3-1.343-3-3s1.343-3,3-3s3,1.343,3,3S9.657,11,8,11z"></path> + </g> + </svg> + <span>Settings</span> + </MenuItem> + + <MenuItem> + <svg class="icon menu__icon" + aria-hidden="true" + viewBox="0 0 16 16"> + <circle cx="10.5" + cy="2.5" + r="2.5"/> + <circle cx="5.5" + cy="6.5" + r="2.5"/> + <path d="M15.826,10.124A5.455,5.455,0,0,0,9.46,6.107,3.932,3.932,0,0,1,9.5,6.5,3.97,3.97,0,0,1,8.452,9.176,6.963,6.963,0,0,1,11.539,12h2.829a1.5,1.5,0,0,0,1.458-1.876Z"/> + <path d="M10.826,14.124a5.5,5.5,0,0,0-10.652,0A1.5,1.5,0,0,0,1.632,16H9.368a1.5,1.5,0,0,0,1.458-1.876Z"/> + </svg> + <span>Team</span> + </MenuItem> + + <MenuItemSeparator/> + + <MenuItem danger="true" on:click={() => logout_user()}> + Logout + </MenuItem> + </div> +</Menu> diff --git a/old-apps/web-shared/src/lib/api/internal-fetch.ts b/old-apps/web-shared/src/lib/api/internal-fetch.ts new file mode 100644 index 0000000..8659ccb --- /dev/null +++ b/old-apps/web-shared/src/lib/api/internal-fetch.ts @@ -0,0 +1,170 @@ +import {Temporal} from "@js-temporal/polyfill"; +import {clear_session_data} from "$shared/lib/session"; +import {resolve_references} from "$shared/lib/helpers"; +import {replace} from "svelte-spa-router"; +import type {IInternalFetchResponse} from "$shared/lib/models/IInternalFetchResponse"; +import type {IInternalFetchRequest} from "$shared/lib/models/IInternalFetchRequest"; + +export async function http_post(url: string, body?: object|string, timeout = -1, skip_401_check = false, abort_signal: AbortSignal = undefined): Promise<IInternalFetchResponse> { + const init = { + method: "post", + } as RequestInit; + + if (abort_signal) { + init.signal = abort_signal; + } + + if (body) { + init.headers = { + "Content-Type": "application/json;charset=UTF-8", + }; + init.body = JSON.stringify(body); + } + + const response = await internal_fetch({url, init, timeout}); + const result = {} as IInternalFetchResponse; + + if (!skip_401_check && await is_401(response)) return result; + + result.ok = response.ok; + result.status = response.status; + result.http_response = response; + + if (response.status !== 204) { + try { + const ct = response.headers.get("Content-Type")?.toString() ?? ""; + if (ct.startsWith("application/json")) { + const data = await response.json(); + result.data = resolve_references(data); + } else if (ct.startsWith("text/plain")) { + const text = await response.text(); + result.data = text as string; + } + } catch { + // Ignored + } + } + + return result; +} + +export async function http_get(url: string, timeout = -1, skip_401_check = false, abort_signal: AbortSignal = undefined): Promise<IInternalFetchResponse> { + const init = { + method: "get", + } as RequestInit; + + if (abort_signal) { + init.signal = abort_signal; + } + + const response = await internal_fetch({url, init, timeout}); + const result = {} as IInternalFetchResponse; + + if (!skip_401_check && await is_401(response)) return result; + + result.ok = response.ok; + result.status = response.status; + result.http_response = response; + + if (response.status !== 204) { + try { + const ct = response.headers.get("Content-Type")?.toString() ?? ""; + if (ct.startsWith("application/json")) { + const data = await response.json(); + result.data = resolve_references(data); + } else if (ct.startsWith("text/plain")) { + const text = await response.text(); + result.data = text as string; + } + } catch { + // Ignored + } + } + + return result; +} + +export async function http_delete(url: string, body?: object|string, timeout = -1, skip_401_check = false, abort_signal: AbortSignal = undefined): Promise<IInternalFetchResponse> { + const init = { + method: "delete", + } as RequestInit; + + if (abort_signal) { + init.signal = abort_signal; + } + + if (body) { + init.headers = { + "Content-Type": "application/json;charset=UTF-8", + }; + init.body = JSON.stringify(body); + } + + const response = await internal_fetch({url, init, timeout}); + const result = {} as IInternalFetchResponse; + + if (!skip_401_check && await is_401(response)) return result; + + result.ok = response.ok; + result.status = response.status; + result.http_response = response; + + if (response.status !== 204) { + try { + const ct = response.headers.get("Content-Type")?.toString() ?? ""; + if (ct.startsWith("application/json")) { + const data = await response.json(); + result.data = resolve_references(data); + } else if (ct.startsWith("text/plain")) { + const text = await response.text(); + result.data = text as string; + } + } catch (error) { + // ignored + } + } + + return result; +} + +async function internal_fetch(request: IInternalFetchRequest): Promise<Response> { + if (!request.init) request.init = {}; + request.init.credentials = "include"; + request.init.headers = { + "X-TimeZone": Temporal.Now.timeZone().id, + ...request.init.headers + }; + + const fetch_request = new Request(request.url, request.init); + let response: any; + + try { + if (request.timeout > 500) { + response = await Promise.race([ + fetch(fetch_request), + new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), request.timeout)) + ]); + } else { + response = await fetch(fetch_request); + } + } catch (error) { + if (error.message === "Timeout") { + console.error("Request timed out"); + } else if (error.message === "Network request failed") { + console.error("No internet connection"); + } else { + throw error; // rethrow other unexpected errors + } + } + + return response; +} + +async function is_401(response: Response): Promise<boolean> { + if (response.status === 401) { + clear_session_data(); + await replace("/login"); + return true; + } + return false; +} diff --git a/old-apps/web-shared/src/lib/api/root.ts b/old-apps/web-shared/src/lib/api/root.ts new file mode 100644 index 0000000..d65efc4 --- /dev/null +++ b/old-apps/web-shared/src/lib/api/root.ts @@ -0,0 +1,6 @@ +import {http_post} from "$shared/lib/api/internal-fetch"; +import {api_base} from "$shared/lib/configuration"; + +export function server_log(message: string): void { + http_post(api_base("_/api/log"), message); +} diff --git a/old-apps/web-shared/src/lib/api/time-entry.ts b/old-apps/web-shared/src/lib/api/time-entry.ts new file mode 100644 index 0000000..064964a --- /dev/null +++ b/old-apps/web-shared/src/lib/api/time-entry.ts @@ -0,0 +1,84 @@ +import {api_base} from "$shared/lib/configuration"; +import {is_guid} from "$shared/lib/helpers"; +import {http_delete, http_get, http_post} from "./internal-fetch"; +import type {TimeCategoryDto} from "$shared/lib/models/TimeCategoryDto"; +import type {TimeLabelDto} from "$shared/lib/models/TimeLabelDto"; +import type {TimeEntryDto} from "$shared/lib/models/TimeEntryDto"; +import type {TimeEntryQuery} from "$shared/lib/models/TimeEntryQuery"; +import type {IInternalFetchResponse} from "$shared/lib/models/IInternalFetchResponse"; + + +// ENTRIES + +export async function create_time_entry(payload: TimeEntryDto): Promise<IInternalFetchResponse> { + return http_post(api_base("v1/entries/create"), payload); +} + +export async function get_time_entry(entryId: string): Promise<IInternalFetchResponse> { + if (is_guid(entryId)) { + return http_get(api_base("v1/entries/" + entryId)); + } + throw new Error("entryId is not a valid guid."); +} + +export async function get_time_entries(entryQuery: TimeEntryQuery): Promise<IInternalFetchResponse> { + return http_post(api_base("v1/entries/query"), entryQuery); +} + +export async function delete_time_entry(id: string): Promise<IInternalFetchResponse> { + if (!is_guid(id)) throw new Error("id is not a valid guid"); + return http_delete(api_base("v1/entries/" + id + "/delete")); +} + +export async function update_time_entry(entryDto: TimeEntryDto): Promise<IInternalFetchResponse> { + if (!is_guid(entryDto.id ?? "")) throw new Error("id is not a valid guid"); + if (!entryDto.category) throw new Error("category is empty"); + if (!entryDto.stop) throw new Error("stop is empty"); + if (!entryDto.start) throw new Error("start is empty"); + return http_post(api_base("v1/entries/update"), entryDto); +} + +// LABELS +export async function create_time_label(labelDto: TimeLabelDto): Promise<IInternalFetchResponse> { + return http_post(api_base("v1/labels/create"), labelDto); +} + +export async function get_time_labels(): Promise<IInternalFetchResponse> { + return http_get(api_base("v1/labels")); +} + +export async function delete_time_label(id: string): Promise<IInternalFetchResponse> { + if (!is_guid(id)) throw new Error("id is not a valid guid"); + return http_delete(api_base("v1/labels/" + id + "/delete")); +} + +export async function update_time_label(labelDto: TimeLabelDto): Promise<IInternalFetchResponse> { + if (!is_guid(labelDto.id ?? "")) throw new Error("id is not a valid guid"); + if (!labelDto.name) throw new Error("name is empty"); + if (!labelDto.color) throw new Error("color is empty"); + return http_post(api_base("v1/labels/update"), labelDto); +} + + +// CATEGORIES +export async function create_time_category(category: TimeCategoryDto): Promise<IInternalFetchResponse> { + if (!category.name) throw new Error("name is empty"); + if (!category.color) throw new Error("color is empty"); + return http_post(api_base("v1/categories/create"), category); +} + +export async function get_time_categories(): Promise<IInternalFetchResponse> { + return http_get(api_base("v1/categories")); +} + +export async function delete_time_category(id: string): Promise<IInternalFetchResponse> { + if (!is_guid(id)) throw new Error("id is not a valid guid"); + return http_delete(api_base("v1/categories/" + id + "/delete")); +} + +export async function update_time_category(category: TimeCategoryDto): Promise<IInternalFetchResponse> { + if (!is_guid(category.id ?? "")) throw new Error("id is not a valid guid"); + if (!category.name) throw new Error("name is empty"); + if (!category.color) throw new Error("color is empty"); + return http_post(api_base("v1/categories/update"), category); +} diff --git a/old-apps/web-shared/src/lib/api/user.ts b/old-apps/web-shared/src/lib/api/user.ts new file mode 100644 index 0000000..a3a149e --- /dev/null +++ b/old-apps/web-shared/src/lib/api/user.ts @@ -0,0 +1,47 @@ +import {api_base} from "$shared/lib/configuration"; +import {http_delete, http_get, http_post} from "./internal-fetch"; +import type {LoginPayload} from "$shared/lib/models/LoginPayload"; +import type {UpdateProfilePayload} from "$shared/lib/models/UpdateProfilePayload"; +import type {CreateAccountPayload} from "$shared/lib/models/CreateAccountPayload"; +import type {IInternalFetchResponse} from "$shared/lib/models/IInternalFetchResponse"; + +export async function login(payload: LoginPayload): Promise<IInternalFetchResponse> { + return http_post(api_base("_/account/login"), payload); +} + +export async function logout(): Promise<IInternalFetchResponse> { + return http_get(api_base("_/account/logout")); +} + +export async function create_forgot_password_request(username: string): Promise<IInternalFetchResponse> { + if (!username) throw new Error("Username is empty"); + return http_get(api_base("_/forgot-password-requests/create?username=" + username)); +} + +export async function check_forgot_password_request(public_id: string): Promise<IInternalFetchResponse> { + if (!public_id) throw new Error("Id is empty"); + return http_get(api_base("_/forgot-password-requests/is-valid?id=" + public_id)); +} + +export async function fulfill_forgot_password_request(public_id: string, newPassword: string): Promise<IInternalFetchResponse> { + if (!public_id) throw new Error("Id is empty"); + return http_post(api_base("_/forgot-password-requests/fulfill"), {id: public_id, newPassword}); +} + +export async function delete_account(): Promise<IInternalFetchResponse> { + return http_delete(api_base("_/account/delete")); +} + +export async function update_profile(payload: UpdateProfilePayload): Promise<IInternalFetchResponse> { + if (!payload.password && !payload.username) throw new Error("Password and Username is empty"); + return http_post(api_base("_/account/update"), payload); +} + +export async function create_account(payload: CreateAccountPayload): Promise<IInternalFetchResponse> { + if (!payload.password && !payload.username) throw new Error("Password and Username is empty"); + return http_post(api_base("_/account/create"), payload); +} + +export async function get_profile_for_active_check(): Promise<IInternalFetchResponse> { + return http_get(api_base("_/account"), 0, true); +} diff --git a/old-apps/web-shared/src/lib/colors.ts b/old-apps/web-shared/src/lib/colors.ts new file mode 100644 index 0000000..c2da03d --- /dev/null +++ b/old-apps/web-shared/src/lib/colors.ts @@ -0,0 +1,47 @@ +export function generate_random_hex_color(skip_contrast_check = false) { + let hex = __generate_random_hex_color(); + if (skip_contrast_check) return hex; + while ((__calculate_contrast_ratio("#ffffff", hex) < 4.5) || (__calculate_contrast_ratio("#000000", hex) < 4.5)) { + hex = __generate_random_hex_color(); + } + + return hex; +} + +// Largely copied from chroma js api +function __generate_random_hex_color(): string { + let code = "#"; + for (let i = 0; i < 6; i++) { + code += "0123456789abcdef".charAt(Math.floor(Math.random() * 16)); + } + return code; +} + +function __calculate_contrast_ratio(hex1: string, hex2: string): number { + const rgb1 = __hex_to_rgb(hex1); + const rgb2 = __hex_to_rgb(hex2); + const l1 = __get_luminance(rgb1[0], rgb1[1], rgb1[2]); + const l2 = __get_luminance(rgb2[0], rgb2[1], rgb2[2]); + const result = l1 > l2 ? (l1 + 0.05) / (l2 + 0.05) : (l2 + 0.05) / (l1 + 0.05); + return result; +} + +function __hex_to_rgb(hex: string): number[] { + if (!hex.match(/^#([A-Fa-f0-9]{6})$/)) return []; + if (hex[0] === "#") hex = hex.substring(1, hex.length); + return [parseInt(hex.substring(0, 2), 16), parseInt(hex.substring(2, 4), 16), parseInt(hex.substring(4, 6), 16)]; +} + +function __get_luminance(r, g, b) { + // relative luminance + // see http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef + r = __luminance_x(r); + g = __luminance_x(g); + b = __luminance_x(b); + return 0.2126 * r + 0.7152 * g + 0.0722 * b; +} + +function __luminance_x(x) { + x /= 255; + return x <= 0.03928 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4); +} diff --git a/old-apps/web-shared/src/lib/configuration.ts b/old-apps/web-shared/src/lib/configuration.ts new file mode 100644 index 0000000..eb38400 --- /dev/null +++ b/old-apps/web-shared/src/lib/configuration.ts @@ -0,0 +1,78 @@ +export const TOPBASEDOMAIN = "greatoffice.life"; +export const BASE_DOMAIN = "dev.greatoffice.life"; +export const DEV_BASE_DOMAIN = "localhost"; +export const API_ADDRESS = "https://api." + BASE_DOMAIN; +export const PROJECTS_ADDRESS = "https://projects." + BASE_DOMAIN; +export const PORTAL_ADDRESS = "https://portal." + BASE_DOMAIN; +export const FRONTPAGE_ADDRESS = "https://" + BASE_DOMAIN; +export const DEV_PORTAL_ADDRESS = "http://localhost:3001"; +export const DEV_FRONTPAGE_ADDRESS = "http://localhost:3002"; +export const DEV_API_ADDRESS = "http://localhost:5000"; +export const DEV_PROJECTS_ADDRESS = "http://localhost:3000"; +export const SECONDS_BETWEEN_SESSION_CHECK = 600; + +export function projects_base(path: string = ""): string { + return (is_development() ? DEV_PROJECTS_ADDRESS : PROJECTS_ADDRESS) + (path !== "" ? "/" + path : ""); +} + +export function base_domain(path: string = ""): string { + return (is_development() ? DEV_BASE_DOMAIN : TOPBASEDOMAIN) + (path !== "" ? "/" + path : ""); +} + +export function frontpage_base(path: string = ""): string { + return (is_development() ? DEV_FRONTPAGE_ADDRESS : FRONTPAGE_ADDRESS) + (path !== "" ? "/" + path : ""); +} + +export function portal_base(path: string = ""): string { + return (is_development() ? DEV_PORTAL_ADDRESS : PORTAL_ADDRESS) + (path !== "" ? "/" + path : ""); +} + +export function api_base(path: string = ""): string { + return (is_development() ? DEV_API_ADDRESS : API_ADDRESS) + (path !== "" ? "/" + path : ""); +} + +export function is_development(): boolean { + // @ts-ignore + return import.meta.env.DEV; +} + +export function is_debug(): boolean { + return localStorage.getItem(StorageKeys.debug) !== "true"; +} + +export const CookieNames = { + theme: "go_theme", + locale: "go_locale" +}; + +export const QueryKeys = { + labels: "labels", + categories: "categories", + entries: "entries", +}; + +export const IconNames = { + github: "github", + verticalDots: "verticalDots", + clock: "clock", + trash: "trash", + pencilSquare: "pencilSquare", + x: "x", + funnel: "funnel", + funnelFilled: "funnelFilled", + refresh: "refresh", + resetHard: "resetHard", + arrowUp: "arrowUp", + arrowDown: "arrowDown", + chevronDown: "chevronDown" +}; + +export const StorageKeys = { + session: "sessionData", + theme: "theme", + debug: "debug", + categories: "categories", + labels: "labels", + entries: "entries", + stopwatch: "stopwatchState" +}; diff --git a/old-apps/web-shared/src/lib/helpers.ts b/old-apps/web-shared/src/lib/helpers.ts new file mode 100644 index 0000000..ad6f280 --- /dev/null +++ b/old-apps/web-shared/src/lib/helpers.ts @@ -0,0 +1,491 @@ +import { base_domain, CookieNames } from "$shared/lib/configuration"; +import { TimeEntryDto } from "$shared/lib/models/TimeEntryDto"; +import { UnwrappedEntryDateTime } from "$shared/lib/models/UnwrappedEntryDateTime"; +import { Temporal } from "@js-temporal/polyfill"; + +export const EMAIL_REGEX = new RegExp(/^([a-z0-9]+(?:([._\-])[a-z0-9]+)*@(?:[a-z0-9]+(?:(-)[a-z0-9]+)?\.)+[a-z0-9](?:[a-z0-9]*[a-z0-9])?)$/i); +export const URL_REGEX = new RegExp(/^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-.][a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/gm); +export const GUID_REGEX = new RegExp(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i); +export const NORWEGIAN_PHONE_NUMBER_REGEX = new RegExp(/(0047|\+47|47)?\d{8,12}/); + +export function get_default_sorted(unsorted: Array<TimeEntryDto>): Array<TimeEntryDto> { + if (unsorted.length < 1) return unsorted; + const byStart = unsorted.sort((a, b) => { + return Temporal.Instant.compare(Temporal.Instant.from(b.start), Temporal.Instant.from(a.start)); + }); + + return byStart.sort((a, b) => { + return Temporal.Instant.compare(Temporal.Instant.from(b.stop), Temporal.Instant.from(a.stop)); + }); +} + +export function is_email(value: string): boolean { + return EMAIL_REGEX.test(String(value).toLowerCase()); +} + +export function is_url(value: string): boolean { + return URL_REGEX.test(String(value).toLowerCase()); +} + +export function is_norwegian_phone_number(value: string): boolean { + if (value.length < 8 || value.length > 12) { + return false; + } + return NORWEGIAN_PHONE_NUMBER_REGEX.test(String(value)); +} + +export function switch_theme() { + const html = document.querySelector("html"); + if (html.dataset.theme === "dark") { + html.dataset.theme = "light"; + } else { + html.dataset.theme = "dark"; + } + set_cookie(CookieNames.theme, html.dataset.theme, base_domain()); +} + +export function get_cookie(name) { + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) return parts.pop().split(";").shift(); +} + +export function set_cookie(name, value, baseDomain = window.location.host) { + document.cookie = name + "=" + encodeURIComponent(value) + (baseDomain ? ";domain=" + baseDomain : ""); +} + +export function unwrap_date_time_from_entry(entry: TimeEntryDto): UnwrappedEntryDateTime { + if (!entry) throw new Error("entry was undefined"); + const currentTimeZone = Temporal.Now.timeZone().id; + const startInstant = Temporal.Instant.from(entry.start).toZonedDateTimeISO(currentTimeZone); + const stopInstant = Temporal.Instant.from(entry.stop).toZonedDateTimeISO(currentTimeZone); + + return { + start_date: startInstant.toPlainDate(), + stop_date: stopInstant.toPlainDate(), + start_time: startInstant.toPlainTime(), + stop_time: stopInstant.toPlainTime(), + duration: Temporal.Duration.from({ + hours: stopInstant.hour, + minutes: stopInstant.minute + }).subtract(Temporal.Duration.from({ + hours: startInstant.hour, + minutes: startInstant.minute + })) + }; +} + + +export function is_guid(value: string): boolean { + if (!value) { + return false; + } + if (value[0] === "{") { + value = value.substring(1, value.length - 1); + } + return GUID_REGEX.test(value); +} + +export function is_empty_object(obj: object): boolean { + return obj !== void 0 && Object.keys(obj).length > 0; +} + +export function merge_obj_arr<T>(a: Array<T>, b: Array<T>, props: Array<string>): Array<T> { + let start = 0; + let merge = []; + + while (start < a.length) { + + if (a[start] === b[start]) { + //pushing the merged objects into array + merge.push({...a[start], ...b[start]}); + } + //incrementing start value + start = start + 1; + } + return merge; +} + +export function set_favicon(url: string) { + // Find the current favicon element + const favicon = document.querySelector("link[rel=\"icon\"]") as HTMLLinkElement; + if (favicon) { + // Update the new link + favicon.href = url; + } else { + // Create new `link` + const link = document.createElement("link"); + link.rel = "icon"; + link.href = url; + + // Append to the `head` element + document.head.appendChild(link); + } +} + +export function set_emoji_favicon(emoji: string) { + // Create a canvas element + const canvas = document.createElement("canvas"); + canvas.height = 64; + canvas.width = 64; + + // Get the canvas context + const context = canvas.getContext("2d") as CanvasRenderingContext2D; + context.font = "64px serif"; + context.fillText(emoji, 0, 64); + + // Get the custom URL + const url = canvas.toDataURL(); + + // Update the favicon + set_favicon(url); +} + + +// https://stackoverflow.com/a/48400665/11961742 +export function seconds_to_hour_minute_string(seconds: number, hourChar = "h", minuteChar = "m") { + const hours = Math.floor(seconds / (60 * 60)); + seconds -= hours * (60 * 60); + const minutes = Math.floor(seconds / 60); + return hours + "h" + minutes + "m"; +} + +export function seconds_to_hour_minute(seconds: number) { + const hours = Math.floor(seconds / (60 * 60)); + seconds -= hours * (60 * 60); + const minutes = Math.floor(seconds / 60); + return {hours, minutes}; +} + +export function get_query_string(params: any = {}): string { + const map = Object.keys(params).reduce((arr: Array<string>, key: string) => { + if (params[key] !== undefined) { + return arr.concat(`${key}=${encodeURIComponent(params[key])}`); + } + return arr; + }, [] as any); + + if (map.length) { + return `?${map.join("&")}`; + } + + return ""; +} + +export function make_url(url: string, params: object): string { + return `${url}${get_query_string(params)}`; +} + +export function load_script(url: string) { + unload_script(url, () => { + return new Promise(function (resolve, reject) { + const script = document.createElement("script"); + script.src = url; + + script.addEventListener("load", function () { + // The script is loaded completely + resolve(true); + }); + + document.body.appendChild(script); + }); + }); +} + +export function unload_script(src: string, callback?: Function): void { + document.querySelectorAll("script[src='" + src + "']").forEach(el => el.remove()); + if (typeof callback === "function") { + callback(); + } +} + +export function noop() { +} + +export async function run_async(functionToRun: Function): Promise<any> { + return new Promise((greatSuccess, graveFailure) => { + try { + greatSuccess(functionToRun()); + } catch (exception) { + graveFailure(exception); + } + }); +} + +// https://stackoverflow.com/a/45215694/11961742 +export function get_selected_options(domElement: HTMLSelectElement): Array<string> { + const ret = []; + + // fast but not universally supported + if (domElement.selectedOptions !== undefined) { + for (let i = 0; i < domElement.selectedOptions.length; i++) { + ret.push(domElement.selectedOptions[i].value); + } + + // compatible, but can be painfully slow + } else { + for (let i = 0; i < domElement.options.length; i++) { + if (domElement.options[i].selected) { + ret.push(domElement.options[i].value); + } + } + } + return ret; +} + +export function random_string(length: number): string { + if (!length) { + throw new Error("length is undefined"); + } + let result = ""; + const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + const charactersLength = characters.length; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +} + +interface CreateElementOptions { + name: string, + properties?: object, + children?: Array<HTMLElement|Function|Node> +} + +export function create_element_from_object(elementOptions: CreateElementOptions): HTMLElement { + return create_element(elementOptions.name, elementOptions.properties, elementOptions.children); +} + +export function create_element(name: string, properties?: object, children?: Array<HTMLElement|any>): HTMLElement { + if (!name || name.length < 1) { + throw new Error("name is required"); + } + const node = document.createElement(name); + if (properties) { + for (const [key, value] of Object.entries(properties)) { + // @ts-ignore + node[key] = value; + } + } + + if (children && children.length > 0) { + let actualChildren = children; + if (typeof children === "function") { + // @ts-ignore + actualChildren = children(); + } + for (const child of actualChildren) { + node.appendChild(child as Node); + } + } + return node; +} + +export function get_element_position(element: HTMLElement|any) { + if (!element) return {x: 0, y: 0}; + let x = 0; + let y = 0; + while (true) { + x += element.offsetLeft; + y += element.offsetTop; + if (element.offsetParent === null) { + break; + } + element = element.offsetParent; + } + return {x, y}; +} + +export function restrict_input_to_numbers(element: HTMLElement, specials: Array<string> = [], mergeSpecialsWithDefaults: boolean = false): void { + if (element) { + element.addEventListener("keydown", (e) => { + const defaultSpecials = ["Backspace", "ArrowLeft", "ArrowRight", "Tab",]; + let keys = specials.length > 0 ? specials : defaultSpecials; + if (mergeSpecialsWithDefaults && specials) { + keys = [...specials, ...defaultSpecials]; + } + if (keys.indexOf(e.key) !== -1) { + return; + } + if (isNaN(parseInt(e.key))) { + e.preventDefault(); + } + }); + } +} + +export function element_has_focus(element: HTMLElement): boolean { + return element === document.activeElement; +} + +export function move_focus(element: HTMLElement): void { + if (!element) { + element = document.getElementsByTagName("body")[0]; + } + element.focus(); + // @ts-ignore + if (!element_has_focus(element)) { + element.setAttribute("tabindex", "-1"); + element.focus(); + } +} + +export function get_url_parameter(name: string): string { + // @ts-ignore + return new RegExp("[?&]" + name + "=([^&#]*)")?.exec(window.location.href)[1]; +} + +export function update_url_parameter(param: string, newVal: string): void { + let newAdditionalURL = ""; + let tempArray = location.href.split("?"); + const baseURL = tempArray[0]; + const additionalURL = tempArray[1]; + let temp = ""; + if (additionalURL) { + tempArray = additionalURL.split("&"); + for (let i = 0; i < tempArray.length; i++) { + if (tempArray[i].split("=")[0] !== param) { + newAdditionalURL += temp + tempArray[i]; + temp = "&"; + } + } + } + const rows_txt = temp + "" + param + "=" + newVal; + const newUrl = baseURL + "?" + newAdditionalURL + rows_txt; + window.history.replaceState("", "", newUrl); +} + + +export function get_style_string(rules: CSSRuleList) { + let styleString = ""; + for (const [key, value] of Object.entries(rules)) { + styleString += key + ":" + value + ";"; + } + return styleString; +} + +export function parse_iso_local(s: string) { + const b = s.split(/\D/); + //@ts-ignore + return new Date(b[0], b[1] - 1, b[2], b[3], b[4], b[5]); +} + +export function resolve_references(json: any) { + if (!json) return; + if (typeof json === "string") { + json = JSON.parse(json ?? "{}"); + } + const byid = {}, refs = []; + json = function recurse(obj, prop, parent) { + if (typeof obj !== "object" || !obj) { + return obj; + } + if (Object.prototype.toString.call(obj) === "[object Array]") { + for (let i = 0; i < obj.length; i++) { + if (typeof obj[i] !== "object" || !obj[i]) { + continue; + } else if ("$ref" in obj[i]) { + // @ts-ignore + obj[i] = recurse(obj[i], i, obj); + } else { + obj[i] = recurse(obj[i], prop, obj); + } + } + return obj; + } + if ("$ref" in obj) { + let ref = obj.$ref; + if (ref in byid) { + // @ts-ignore + return byid[ref]; + } + refs.push([parent, prop, ref]); + return; + } else if ("$id" in obj) { + let id = obj.$id; + delete obj.$id; + if ("$values" in obj) { + obj = obj.$values.map(recurse); + } else { + for (let prop2 in obj) { + // @ts-ignore + obj[prop2] = recurse(obj[prop2], prop2, obj); + } + } + // @ts-ignore + byid[id] = obj; + } + return obj; + }(json); + for (let i = 0; i < refs.length; i++) { + let ref = refs[i]; + // @ts-ignore + ref[0][ref[1]] = byid[ref[2]]; + } + return json; +} + +export function get_random_int(min: number, max: number): number { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +export function to_readable_bytes(bytes: number): string { + const s = ["bytes", "kB", "MB", "GB", "TB", "PB"]; + const e = Math.floor(Math.log(bytes) / Math.log(1024)); + return (bytes / Math.pow(1024, e)).toFixed(2) + " " + s[e]; +} + +export function can_use_dom(): boolean { + return !!(typeof window !== "undefined" && window.document && window.document.createElement); +} + +export function session_storage_remove_regex(regex: RegExp): void { + let n = sessionStorage.length; + while (n--) { + const key = sessionStorage.key(n); + if (key && regex.test(key)) { + sessionStorage.removeItem(key); + } + } +} + +export function local_storage_remove_regex(regex: RegExp): void { + let n = localStorage.length; + while (n--) { + const key = localStorage.key(n); + if (key && regex.test(key)) { + localStorage.removeItem(key); + } + } +} + +export function session_storage_set_json(key: string, value: object): void { + sessionStorage.setItem(key, JSON.stringify(value)); +} + +export function session_storage_get_json(key: string): object { + return JSON.parse(sessionStorage.getItem(key) ?? "{}"); +} + +export function local_storage_set_json(key: string, value: object): void { + localStorage.setItem(key, JSON.stringify(value)); +} + +export function local_storage_get_json(key: string): object { + return JSON.parse(localStorage.getItem(key) ?? "{}"); +} + +export function get_hash_code(value: string): number|undefined { + let hash = 0; + if (value.length === 0) { + return; + } + for (let i = 0; i < value.length; i++) { + const char = value.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash |= 0; + } + return hash; +} diff --git a/old-apps/web-shared/src/lib/i18n/en/index.ts b/old-apps/web-shared/src/lib/i18n/en/index.ts new file mode 100644 index 0000000..9db1759 --- /dev/null +++ b/old-apps/web-shared/src/lib/i18n/en/index.ts @@ -0,0 +1,18 @@ +import type {BaseTranslation} from "../i18n-types"; + +const en: BaseTranslation = { + stopwatch: { + roundUp: "Round up", + roundDown: "Round down", + createEntry: "Create entry", + whatsYourFocus: "What's your focus?", + reset: "Reset", + start: "Start", + stop: "Stop", + }, + localeSwitcher: { + language: "Language" + } +}; + +export default en; diff --git a/old-apps/web-shared/src/lib/i18n/formatters.ts b/old-apps/web-shared/src/lib/i18n/formatters.ts new file mode 100644 index 0000000..78734f9 --- /dev/null +++ b/old-apps/web-shared/src/lib/i18n/formatters.ts @@ -0,0 +1,11 @@ +import type { FormattersInitializer } from 'typesafe-i18n' +import type { Locales, Formatters } from './i18n-types' + +export const initFormatters: FormattersInitializer<Locales, Formatters> = (locale: Locales) => { + + const formatters: Formatters = { + // add your formatter functions here + } + + return formatters +} diff --git a/old-apps/web-shared/src/lib/i18n/i18n-types.ts b/old-apps/web-shared/src/lib/i18n/i18n-types.ts new file mode 100644 index 0000000..62a13dd --- /dev/null +++ b/old-apps/web-shared/src/lib/i18n/i18n-types.ts @@ -0,0 +1,94 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ +import type { BaseTranslation as BaseTranslationType, LocalizedString } from 'typesafe-i18n' + +export type BaseTranslation = BaseTranslationType +export type BaseLocale = 'en' + +export type Locales = + | 'en' + | 'nb' + +export type Translation = RootTranslation + +export type Translations = RootTranslation + +type RootTranslation = { + stopwatch: { + /** + * Round up + */ + roundUp: string + /** + * Round down + */ + roundDown: string + /** + * Create entry + */ + createEntry: string + /** + * What's your focus? + */ + whatsYourFocus: string + /** + * Reset + */ + reset: string + /** + * Start + */ + start: string + /** + * Stop + */ + stop: string + } + localeSwitcher: { + /** + * Language + */ + language: string + } +} + +export type TranslationFunctions = { + stopwatch: { + /** + * Round up + */ + roundUp: () => LocalizedString + /** + * Round down + */ + roundDown: () => LocalizedString + /** + * Create entry + */ + createEntry: () => LocalizedString + /** + * What's your focus? + */ + whatsYourFocus: () => LocalizedString + /** + * Reset + */ + reset: () => LocalizedString + /** + * Start + */ + start: () => LocalizedString + /** + * Stop + */ + stop: () => LocalizedString + } + localeSwitcher: { + /** + * Language + */ + language: () => LocalizedString + } +} + +export type Formatters = {} diff --git a/old-apps/web-shared/src/lib/i18n/i18n-util.async.ts b/old-apps/web-shared/src/lib/i18n/i18n-util.async.ts new file mode 100644 index 0000000..90e55a7 --- /dev/null +++ b/old-apps/web-shared/src/lib/i18n/i18n-util.async.ts @@ -0,0 +1,27 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { initFormatters } from './formatters' +import type { Locales, Translations } from './i18n-types.js' +import { loadedFormatters, loadedLocales, locales } from './i18n-util' + +const localeTranslationLoaders = { + en: () => import('./en/index.js'), + nb: () => import('./nb/index.js'), +} + +const updateDictionary = (locale: Locales, dictionary: Partial<Translations>) => + loadedLocales[locale] = { ...loadedLocales[locale], ...dictionary } + +export const loadLocaleAsync = async (locale: Locales): Promise<void> => { + updateDictionary( + locale, + (await localeTranslationLoaders[locale]()).default as unknown as Translations + ) + loadFormatters(locale) +} + +export const loadAllLocalesAsync = (): Promise<void[]> => Promise.all(locales.map(loadLocaleAsync)) + +export const loadFormatters = (locale: Locales): void => + void (loadedFormatters[locale] = initFormatters(locale)) diff --git a/old-apps/web-shared/src/lib/i18n/i18n-util.sync.ts b/old-apps/web-shared/src/lib/i18n/i18n-util.sync.ts new file mode 100644 index 0000000..8909831 --- /dev/null +++ b/old-apps/web-shared/src/lib/i18n/i18n-util.sync.ts @@ -0,0 +1,27 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { initFormatters } from './formatters' +import type { Locales, Translations } from './i18n-types.js' +import { loadedFormatters, loadedLocales, locales } from './i18n-util' + +import en from './en/index.js' +import nb from './nb/index.js' + +const localeTranslations = { + en, + nb, +} + +export const loadLocale = (locale: Locales): void => { + if (loadedLocales[locale]) return + + loadedLocales[locale] = localeTranslations[locale] as unknown as Translations + loadFormatters(locale) +} + +export const loadAllLocales = (): void => locales.forEach(loadLocale) + +export const loadFormatters = (locale: Locales): void => { + loadedFormatters[locale] = initFormatters(locale) +} diff --git a/old-apps/web-shared/src/lib/i18n/i18n-util.ts b/old-apps/web-shared/src/lib/i18n/i18n-util.ts new file mode 100644 index 0000000..5a9dd0d --- /dev/null +++ b/old-apps/web-shared/src/lib/i18n/i18n-util.ts @@ -0,0 +1,31 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { i18n as initI18n, i18nObject as initI18nObject, i18nString as initI18nString } from 'typesafe-i18n' +import type { LocaleDetector } from 'typesafe-i18n/detectors' +import { detectLocale as detectLocaleFn } from 'typesafe-i18n/detectors' +import type { Formatters, Locales, Translations, TranslationFunctions } from './i18n-types.js' + +export const baseLocale: Locales = 'en' + +export const locales: Locales[] = [ + 'en', + 'nb' +] + +export const loadedLocales = {} as Record<Locales, Translations> + +export const loadedFormatters = {} as Record<Locales, Formatters> + +export const i18nString = (locale: Locales) => initI18nString<Locales, Formatters>(locale, loadedFormatters[locale]) + +export const i18nObject = (locale: Locales) => + initI18nObject<Locales, Translations, TranslationFunctions, Formatters>( + locale, + loadedLocales[locale], + loadedFormatters[locale] + ) + +export const i18n = () => initI18n<Locales, Translations, TranslationFunctions, Formatters>(loadedLocales, loadedFormatters) + +export const detectLocale = (...detectors: LocaleDetector[]) => detectLocaleFn<Locales>(baseLocale, locales, ...detectors) diff --git a/old-apps/web-shared/src/lib/i18n/nb/index.ts b/old-apps/web-shared/src/lib/i18n/nb/index.ts new file mode 100644 index 0000000..a1d5f53 --- /dev/null +++ b/old-apps/web-shared/src/lib/i18n/nb/index.ts @@ -0,0 +1,15 @@ +import type {Translation} from "../i18n-types"; + +const nb: Translation = { + stopwatch: { + roundUp: "Rund opp", + roundDown: "Rund ned", + createEntry: "Opprett tidsoppføring", + whatsYourFocus: "Hva skal du fokusere på?", + reset: "Tilbakestill", + start: "Start", + stop: "Stopp", + } +}; + +export default nb; diff --git a/old-apps/web-shared/src/lib/locale.ts b/old-apps/web-shared/src/lib/locale.ts new file mode 100644 index 0000000..002f874 --- /dev/null +++ b/old-apps/web-shared/src/lib/locale.ts @@ -0,0 +1,20 @@ +import {writable} from "svelte/store"; +import {base_domain, CookieNames} from "./configuration"; +import {get_cookie, set_cookie} from "./helpers"; + +export function preffered_or_default() { + if (/^en\b/i.test(navigator.language)) { + return "en"; + } + if (/^nb\b/i.test(navigator.language) || /^nn\b/i.test(navigator.language)) { + return "nb"; + } + return "en"; +} + +type Locales = "en"|"nb"; +export const currentLocale = writable<Locales>((get_cookie(CookieNames.locale) === "preffered" ? preffered_or_default() : get_cookie(CookieNames.locale) ?? preffered_or_default()) as Locales); +currentLocale.subscribe(locale => { + // @ts-ignore + set_cookie(CookieNames.locale, locale, base_domain()); +}); diff --git a/old-apps/web-shared/src/lib/models/CreateAccountPayload.ts b/old-apps/web-shared/src/lib/models/CreateAccountPayload.ts new file mode 100644 index 0000000..d116308 --- /dev/null +++ b/old-apps/web-shared/src/lib/models/CreateAccountPayload.ts @@ -0,0 +1,4 @@ +export interface CreateAccountPayload { + username: string, + password: string +} diff --git a/old-apps/web-shared/src/lib/models/ErrorResult.ts b/old-apps/web-shared/src/lib/models/ErrorResult.ts new file mode 100644 index 0000000..7c70017 --- /dev/null +++ b/old-apps/web-shared/src/lib/models/ErrorResult.ts @@ -0,0 +1,4 @@ +export interface ErrorResult { + title: string, + text: string +} diff --git a/old-apps/web-shared/src/lib/models/IInternalFetchRequest.ts b/old-apps/web-shared/src/lib/models/IInternalFetchRequest.ts new file mode 100644 index 0000000..68505e2 --- /dev/null +++ b/old-apps/web-shared/src/lib/models/IInternalFetchRequest.ts @@ -0,0 +1,6 @@ +export interface IInternalFetchRequest { + url: string, + init?: RequestInit, + timeout?: number + retry_count?: number +} diff --git a/old-apps/web-shared/src/lib/models/IInternalFetchResponse.ts b/old-apps/web-shared/src/lib/models/IInternalFetchResponse.ts new file mode 100644 index 0000000..6c91b35 --- /dev/null +++ b/old-apps/web-shared/src/lib/models/IInternalFetchResponse.ts @@ -0,0 +1,6 @@ +export interface IInternalFetchResponse { + ok: boolean, + status: number, + data: any, + http_response: Response +} diff --git a/old-apps/web-shared/src/lib/models/ISession.ts b/old-apps/web-shared/src/lib/models/ISession.ts new file mode 100644 index 0000000..f7ed46b --- /dev/null +++ b/old-apps/web-shared/src/lib/models/ISession.ts @@ -0,0 +1,7 @@ +export interface ISession { + profile: { + username: string, + id: string, + }, + lastChecked: number, +}
\ No newline at end of file diff --git a/old-apps/web-shared/src/lib/models/IValidationResult.ts b/old-apps/web-shared/src/lib/models/IValidationResult.ts new file mode 100644 index 0000000..9a21b13 --- /dev/null +++ b/old-apps/web-shared/src/lib/models/IValidationResult.ts @@ -0,0 +1,31 @@ +export interface IValidationResult { + errors: Array<IValidationError>, + has_errors: Function, + add_error: Function, + remove_error: Function, +} + +export interface IValidationError { + _id?: string, + title: string, + text?: string +} + +export default class ValidationResult implements IValidationResult { + errors: IValidationError[] + has_errors(): boolean { + return this.errors?.length > 0; + } + add_error(prop: string, error: IValidationError): void { + if (!this.errors) this.errors = []; + error._id = prop; + this.errors.push(error); + } + remove_error(property: string): void { + const new_errors = []; + for (const error of this.errors) { + if (error._id != property) new_errors.push(error) + } + this.errors = new_errors; + } +} diff --git a/old-apps/web-shared/src/lib/models/LoginPayload.ts b/old-apps/web-shared/src/lib/models/LoginPayload.ts new file mode 100644 index 0000000..ccd9bed --- /dev/null +++ b/old-apps/web-shared/src/lib/models/LoginPayload.ts @@ -0,0 +1,4 @@ +export interface LoginPayload { + username: string, + password: string +} diff --git a/old-apps/web-shared/src/lib/models/TimeCategoryDto.ts b/old-apps/web-shared/src/lib/models/TimeCategoryDto.ts new file mode 100644 index 0000000..875e8cb --- /dev/null +++ b/old-apps/web-shared/src/lib/models/TimeCategoryDto.ts @@ -0,0 +1,9 @@ +import { Temporal } from "@js-temporal/polyfill"; + +export interface TimeCategoryDto { + selected?: boolean; + id?: string, + modified_at?: Temporal.PlainDate, + name?: string, + color?: string +} diff --git a/old-apps/web-shared/src/lib/models/TimeEntryDto.ts b/old-apps/web-shared/src/lib/models/TimeEntryDto.ts new file mode 100644 index 0000000..71fe7a3 --- /dev/null +++ b/old-apps/web-shared/src/lib/models/TimeEntryDto.ts @@ -0,0 +1,13 @@ +import type { TimeLabelDto } from "./TimeLabelDto"; +import type { TimeCategoryDto } from "./TimeCategoryDto"; +import { Temporal } from "@js-temporal/polyfill"; + +export interface TimeEntryDto { + id: string, + modified_at?: Temporal.PlainDate, + start: string, + stop: string, + description: string, + labels?: Array<TimeLabelDto>, + category: TimeCategoryDto, +} diff --git a/old-apps/web-shared/src/lib/models/TimeEntryQuery.ts b/old-apps/web-shared/src/lib/models/TimeEntryQuery.ts new file mode 100644 index 0000000..6681c79 --- /dev/null +++ b/old-apps/web-shared/src/lib/models/TimeEntryQuery.ts @@ -0,0 +1,27 @@ +import type { TimeCategoryDto } from "./TimeCategoryDto"; +import type { TimeLabelDto } from "./TimeLabelDto"; +import type { Temporal } from "@js-temporal/polyfill"; + +export interface TimeEntryQuery { + duration: TimeEntryQueryDuration, + categories?: Array<TimeCategoryDto>, + labels?: Array<TimeLabelDto>, + dateRange?: TimeEntryQueryDateRange, + specificDate?: Temporal.PlainDateTime + page: number, + pageSize: number +} + +export interface TimeEntryQueryDateRange { + from: Temporal.PlainDateTime, + to: Temporal.PlainDateTime +} + +export enum TimeEntryQueryDuration { + TODAY = 0, + THIS_WEEK = 1, + THIS_MONTH = 2, + THIS_YEAR = 3, + SPECIFIC_DATE = 4, + DATE_RANGE = 5, +} diff --git a/old-apps/web-shared/src/lib/models/TimeLabelDto.ts b/old-apps/web-shared/src/lib/models/TimeLabelDto.ts new file mode 100644 index 0000000..2b42d07 --- /dev/null +++ b/old-apps/web-shared/src/lib/models/TimeLabelDto.ts @@ -0,0 +1,8 @@ +import { Temporal } from "@js-temporal/polyfill"; + +export interface TimeLabelDto { + id?: string, + modified_at?: Temporal.PlainDate, + name?: string, + color?: string +} diff --git a/old-apps/web-shared/src/lib/models/TimeQueryDto.ts b/old-apps/web-shared/src/lib/models/TimeQueryDto.ts new file mode 100644 index 0000000..607c51e --- /dev/null +++ b/old-apps/web-shared/src/lib/models/TimeQueryDto.ts @@ -0,0 +1,29 @@ +import type { TimeEntryDto } from "./TimeEntryDto"; +import ValidationResult, { IValidationResult } from "./IValidationResult"; + +export interface ITimeQueryDto { + results: Array<TimeEntryDto>, + page: number, + pageSize: number, + totalRecords: number, + totalPageCount: number, + is_valid: Function +} + +export class TimeQueryDto implements ITimeQueryDto { + results: TimeEntryDto[]; + page: number; + pageSize: number; + totalRecords: number; + totalPageCount: number; + + is_valid(): IValidationResult { + const result = new ValidationResult(); + if (this.page < 0) { + result.add_error("page", { + title: "Page cannot be less than zero", + }) + } + return result; + } +} diff --git a/old-apps/web-shared/src/lib/models/UnwrappedEntryDateTime.ts b/old-apps/web-shared/src/lib/models/UnwrappedEntryDateTime.ts new file mode 100644 index 0000000..e6022d8 --- /dev/null +++ b/old-apps/web-shared/src/lib/models/UnwrappedEntryDateTime.ts @@ -0,0 +1,9 @@ +import {Temporal} from "@js-temporal/polyfill"; + +export interface UnwrappedEntryDateTime { + start_date: Temporal.PlainDate, + stop_date: Temporal.PlainDate, + start_time: Temporal.PlainTime, + stop_time: Temporal.PlainTime, + duration: Temporal.Duration, +} diff --git a/old-apps/web-shared/src/lib/models/UpdateProfilePayload.ts b/old-apps/web-shared/src/lib/models/UpdateProfilePayload.ts new file mode 100644 index 0000000..d2983ff --- /dev/null +++ b/old-apps/web-shared/src/lib/models/UpdateProfilePayload.ts @@ -0,0 +1,4 @@ +export interface UpdateProfilePayload { + username?: string, + password?: string, +} diff --git a/old-apps/web-shared/src/lib/persistent-store.ts b/old-apps/web-shared/src/lib/persistent-store.ts new file mode 100644 index 0000000..922f3ab --- /dev/null +++ b/old-apps/web-shared/src/lib/persistent-store.ts @@ -0,0 +1,102 @@ +import { writable as _writable, readable as _readable, } from "svelte/store"; +import type { Writable, Readable, StartStopNotifier } from "svelte/store"; + +enum StoreType { + SESSION = 0, + LOCAL = 1 +} + +interface StoreOptions { + store?: StoreType; +} + +const default_store_options = { + store: StoreType.SESSION +} as StoreOptions; + +interface WritableStore<T> { + name: string, + initialState: T, + options?: StoreOptions +} + +interface ReadableStore<T> { + name: string, + initialState: T, + callback: StartStopNotifier<any>, + options?: StoreOptions +} + +function get_store(type: StoreType): Storage { + switch (type) { + case StoreType.SESSION: + return window.sessionStorage; + case StoreType.LOCAL: + return window.localStorage; + } +} + +function prepared_store_value(value: any): string { + try { + return JSON.stringify(value); + } catch (e) { + console.error(e); + return "__INVALID__"; + } +} + +function get_store_value<T>(options: WritableStore<T> | ReadableStore<T>): any { + try { + const storage = get_store(options.options.store); + const value = storage.getItem(options.name); + if (!value) return false; + return JSON.parse(value); + } catch (e) { + console.error(e); + return { __INVALID__: true }; + } +} + +function hydrate<T>(store: Writable<T>, options: WritableStore<T> | ReadableStore<T>): void { + const value = get_store_value<T>(options); + if (value && store.set) store.set(value); +} + +function subscribe<T>(store: Writable<T> | Readable<T>, options: WritableStore<T> | ReadableStore<T>): void { + const storage = get_store(options.options.store); + if (!store.subscribe) return; + store.subscribe((state: any) => { + storage.setItem(options.name, prepared_store_value(state)); + }); +} + +function writable_persistent<T>(options: WritableStore<T>): Writable<T> { + if (options.options === undefined) options.options = default_store_options; + console.log("Creating writable store with options: ", options); + const store = _writable<T>(options.initialState); + hydrate(store, options); + subscribe(store, options); + return store; +} + +function readable_persistent<T>(options: ReadableStore<T>): Readable<T> { + if (options.options === undefined) options.options = default_store_options; + console.log("Creating readable store with options: ", options); + const store = _readable<T>(options.initialState, options.callback); + // hydrate(store, options); + subscribe(store, options); + return store; +} + +export { + writable_persistent, + readable_persistent, + StoreType +}; + +export type { + WritableStore, + ReadableStore, + StoreOptions +}; + diff --git a/old-apps/web-shared/src/lib/session.ts b/old-apps/web-shared/src/lib/session.ts new file mode 100644 index 0000000..f729687 --- /dev/null +++ b/old-apps/web-shared/src/lib/session.ts @@ -0,0 +1,68 @@ +import {Temporal} from "@js-temporal/polyfill"; +import {get_profile_for_active_check, logout} from "./api/user"; +import {is_guid, session_storage_get_json, session_storage_set_json} from "./helpers"; +import {SECONDS_BETWEEN_SESSION_CHECK, StorageKeys} from "./configuration"; +import type {ISession} from "$shared/lib/models/ISession"; + +export async function is_active(forceRefresh: boolean = false): Promise<boolean> { + const nowEpoch = Temporal.Now.instant().epochSeconds; + const data = session_storage_get_json(StorageKeys.session) as ISession; + const expiryEpoch = data?.lastChecked + SECONDS_BETWEEN_SESSION_CHECK; + const lastCheckIsStaleOrNone = !is_guid(data?.profile?.id) || (expiryEpoch < nowEpoch); + if (forceRefresh || lastCheckIsStaleOrNone) { + return await call_api(); + } else { + const sessionIsValid = data.profile && is_guid(data.profile.id); + if (!sessionIsValid) { + clear_session_data(); + console.log("Session data is not valid"); + } + return sessionIsValid; + } +} + +export async function end_session(cb: Function): Promise<void> { + await logout(); + clear_session_data(); + cb(); +} + +async function call_api(): Promise<boolean> { + console.log("Getting profile data while checking session state"); + try { + const response = await get_profile_for_active_check(); + if (response.ok) { + const userData = await response.data; + if (is_guid(userData.id) && userData.username) { + const session = { + profile: userData, + lastChecked: Temporal.Now.instant().epochSeconds + } as ISession; + session_storage_set_json(StorageKeys.session, session); + console.log("Successfully got profile data while checking session state"); + return true; + } else { + console.error("Api returned invalid data while getting profile data"); + clear_session_data(); + return false; + } + } else { + console.error("Api returned unsuccessfully while getting profile data"); + clear_session_data(); + return false; + } + } catch (e) { + console.error(e); + clear_session_data(); + return false; + } +} + +export function clear_session_data() { + session_storage_set_json(StorageKeys.session, {}); + console.log("Cleared session data."); +} + +export function get_session_data(): ISession { + return session_storage_get_json(StorageKeys.session) as ISession; +} diff --git a/old-apps/web-shared/src/styles/_base.scss b/old-apps/web-shared/src/styles/_base.scss new file mode 100644 index 0000000..414b440 --- /dev/null +++ b/old-apps/web-shared/src/styles/_base.scss @@ -0,0 +1,48 @@ +// -------------------------------- + +// Basic Style - Essential CSS rules and utility classes + +// -------------------------------- + +@forward 'base/breakpoints'; +@forward 'base/mixins'; +@forward 'base/grid-layout'; + +@use 'base/reset'; +@use 'base/colors'; +@use 'base/spacing'; +@use 'base/shared-styles'; +@use 'base/typography'; +@use 'base/icons'; +@use 'base/buttons'; +@use 'base/forms'; +@use 'base/z-index'; +@use 'base/visibility'; +@use 'base/accessibility'; +@use 'base/util'; + +pre { + font-family: monospace !important; +} + +*:focus-visible { + outline: 1px auto; +} + +.c-disabled { + cursor: not-allowed !important; + filter: opacity(.45); + pointer-events: none !important; + + &.loading { + cursor: wait !important; + } +} + +button.btn--state-b { + cursor: wait; +} + +button.reset { + cursor: pointer !important; +} diff --git a/old-apps/web-shared/src/styles/base/_accessibility.scss b/old-apps/web-shared/src/styles/base/_accessibility.scss new file mode 100644 index 0000000..9f71937 --- /dev/null +++ b/old-apps/web-shared/src/styles/base/_accessibility.scss @@ -0,0 +1,17 @@ +.sr-only { // content made available only to screen readers + position: absolute; + clip: rect(1px, 1px, 1px, 1px); + clip-path: inset(50%); + width: 1px; + height: 1px; + overflow: hidden; + padding: 0; + border: 0; + white-space: nowrap; +} + +.sr-only-focusable { // focusable, visually hidden element + &:not(:focus):not(:focus-within){ + @extend .sr-only + } +}
\ No newline at end of file diff --git a/old-apps/web-shared/src/styles/base/_breakpoints.scss b/old-apps/web-shared/src/styles/base/_breakpoints.scss new file mode 100644 index 0000000..9b61af3 --- /dev/null +++ b/old-apps/web-shared/src/styles/base/_breakpoints.scss @@ -0,0 +1,19 @@ +$breakpoints: ( + xs: "768px", + sm: "768px", + md: "1200px", + lg: "1200px", + xl: "1600px" +) !default; + +@mixin breakpoint($breakpoint, $logic: false) { + @if ($logic) { + @media #{$logic} and (min-width: map-get($map: $breakpoints, $key: $breakpoint)) { + @content; + } + } @else { + @media (min-width: map-get($map: $breakpoints, $key: $breakpoint)) { + @content; + } + } +} diff --git a/old-apps/web-shared/src/styles/base/_buttons.scss b/old-apps/web-shared/src/styles/base/_buttons.scss new file mode 100644 index 0000000..2a7ff34 --- /dev/null +++ b/old-apps/web-shared/src/styles/base/_buttons.scss @@ -0,0 +1,24 @@ +// don't modify this file -> edit 📁 custom-style/_buttons.scss to create your custom buttons + +.btn { // basic button style + position: relative; + display: inline-flex; + justify-content: center; + align-items: center; + white-space: nowrap; + text-decoration: none; + font-size: var(--btn-font-size, 1em); + padding-top: var(--btn-padding-y, 0.5em); + padding-bottom: var(--btn-padding-y, 0.5em); + padding-left: var(--btn-padding-x, 0.75em); + padding-right: var(--btn-padding-x, 0.75em); + border-radius: var(--btn-radius, 0.25em); +} + +// default size variations +.btn--sm { font-size: var(--btn-font-size-sm, 0.8em); } +.btn--md { font-size: var(--btn-font-size-md, 1.2em); } +.btn--lg { font-size: var(--btn-font-size-lg, 1.4em); } + +// button with (only) icon +.btn--icon { padding: var(--btn-padding-y, 0.5em); }
\ No newline at end of file diff --git a/old-apps/web-shared/src/styles/base/_colors.scss b/old-apps/web-shared/src/styles/base/_colors.scss new file mode 100644 index 0000000..f061d9a --- /dev/null +++ b/old-apps/web-shared/src/styles/base/_colors.scss @@ -0,0 +1,6 @@ +// don't modify this file -> edit 📁 custom-style/_colors.scss to create your color palette + +[data-theme] { + background-color: var(--color-bg, hsl(0, 0%, 100%)); + color: var(--color-contrast-high, hsl(210, 7%, 21%)); +}
\ No newline at end of file diff --git a/old-apps/web-shared/src/styles/base/_forms.scss b/old-apps/web-shared/src/styles/base/_forms.scss new file mode 100644 index 0000000..faffddd --- /dev/null +++ b/old-apps/web-shared/src/styles/base/_forms.scss @@ -0,0 +1,22 @@ +// don't modify this file -> edit 📁 custom-style/_forms.scss to create your custom form elements + +.form-control { + font-size: var(--form-control-font-size, 1em); + padding-top: var(--form-control-padding-y, 0.5em); + padding-bottom: var(--form-control-padding-y, 0.5em); + padding-left: var(--form-control-padding-x, 0.75em); + padding-right: var(--form-control-padding-x, 0.75em); + border-radius: var(--form-control-radius, 0.25em); +} + +.form-legend { + color: var(--color-contrast-higher, hsl(204, 28%, 7%)); + line-height: var(--heading-line-height, 1.2); + font-size: var(--text-md, 1.125rem); + margin-bottom: var(--space-sm); +} + +.form-label { + display: inline-block; + font-size: var(--text-sm, 0.75rem); +} diff --git a/old-apps/web-shared/src/styles/base/_grid-layout.scss b/old-apps/web-shared/src/styles/base/_grid-layout.scss new file mode 100644 index 0000000..bd8b6c9 --- /dev/null +++ b/old-apps/web-shared/src/styles/base/_grid-layout.scss @@ -0,0 +1,261 @@ +@use 'mixins' as *; +@use 'breakpoints' as *; + +// -------------------------------- + +// Container - center content on x-axis + +// -------------------------------- + +.container { + width: calc(100% - 2*var(--component-padding)); + margin-left: auto; + margin-right: auto; +} + +// -------------------------------- + +// Grid System + +// -------------------------------- + +$grid-columns: 12 !default; + +.grid, .flex, .inline-flex { + --gap: 0px; + --gap-x: var(--gap); + --gap-y: var(--gap); + gap: var(--gap-y) var(--gap-x); + + > * { + --sub-gap: 0px; + --sub-gap-x: var(--sub-gap); + --sub-gap-y: var(--sub-gap); + } +} + +.grid { + --grid-columns: 12; + display: flex; + flex-wrap: wrap; + + > * { + flex-basis: 100%; + max-width: 100%; + min-width: 0; + } +} + +/* #region (Safari < 14.1 fallback) */ +@media not all and (min-resolution:.001dpcm) { + @supports (not(translate: none)) { + .grid, .flex[class*="gap-"], .inline-flex[class*="gap-"] { + gap: 0; // reset + margin-bottom: calc(-1 * var(--gap-y)); + margin-left: calc(-1 * var(--gap-x)); + + > * { + margin-bottom: var(--sub-gap-y); + } + } + + .grid { + --offset: var(--gap-x); + --gap-modifier: 0; + --offset-modifier: 1; + + > * { + margin-left: var(--offset); + } + } + + .flex[class*="gap-"], .inline-flex[class*="gap-"] { + > * { + margin-left: var(--sub-gap-x); + } + } + } +} +/* #endregion */ + +.gap-xxxxs { --gap-x: var(--space-xxxxs); --gap-y: var(--space-xxxxs); > * { --sub-gap-x: var(--space-xxxxs); --sub-gap-y: var(--space-xxxxs); }} +.gap-xxxs { --gap-x: var(--space-xxxs); --gap-y: var(--space-xxxs); > * { --sub-gap-x: var(--space-xxxs); --sub-gap-y: var(--space-xxxs); }} +.gap-xxs { --gap-x: var(--space-xxs); --gap-y: var(--space-xxs); > * { --sub-gap-x: var(--space-xxs); --sub-gap-y: var(--space-xxs); }} +.gap-xs { --gap-x: var(--space-xs); --gap-y: var(--space-xs); > * { --sub-gap-x: var(--space-xs); --sub-gap-y: var(--space-xs); }} +.gap-sm { --gap-x: var(--space-sm); --gap-y: var(--space-sm); > * { --sub-gap-x: var(--space-sm); --sub-gap-y: var(--space-sm); }} +.gap-md { --gap-x: var(--space-md); --gap-y: var(--space-md); > * { --sub-gap-x: var(--space-md); --sub-gap-y: var(--space-md); }} +.gap-lg { --gap-x: var(--space-lg); --gap-y: var(--space-lg); > * { --sub-gap-x: var(--space-lg); --sub-gap-y: var(--space-lg); }} +.gap-xl { --gap-x: var(--space-xl); --gap-y: var(--space-xl); > * { --sub-gap-x: var(--space-xl); --sub-gap-y: var(--space-xl); }} +.gap-xxl { --gap-x: var(--space-xxl); --gap-y: var(--space-xxl); > * { --sub-gap-x: var(--space-xxl); --sub-gap-y: var(--space-xxl); }} +.gap-xxxl { --gap-x: var(--space-xxxl); --gap-y: var(--space-xxxl); > * { --sub-gap-x: var(--space-xxxl); --sub-gap-y: var(--space-xxxl); }} +.gap-xxxxl { --gap-x: var(--space-xxxxl); --gap-y: var(--space-xxxxl); > * { --sub-gap-x: var(--space-xxxxl); --sub-gap-y: var(--space-xxxxl); }} +.gap-0 { --gap-x: 0; --gap-y: 0; > * { --sub-gap-x: 0; --sub-gap-y: 0; }} + +.gap-x-xxxxs { --gap-x: var(--space-xxxxs); > * { --sub-gap-x: var(--space-xxxxs); }} +.gap-x-xxxs { --gap-x: var(--space-xxxs); > * { --sub-gap-x: var(--space-xxxs); }} +.gap-x-xxs { --gap-x: var(--space-xxs); > * { --sub-gap-x: var(--space-xxs); }} +.gap-x-xs { --gap-x: var(--space-xs); > * { --sub-gap-x: var(--space-xs); }} +.gap-x-sm { --gap-x: var(--space-sm); > * { --sub-gap-x: var(--space-sm); }} +.gap-x-md { --gap-x: var(--space-md); > * { --sub-gap-x: var(--space-md); }} +.gap-x-lg { --gap-x: var(--space-lg); > * { --sub-gap-x: var(--space-lg); }} +.gap-x-xl { --gap-x: var(--space-xl); > * { --sub-gap-x: var(--space-xl); }} +.gap-x-xxl { --gap-x: var(--space-xxl); > * { --sub-gap-x: var(--space-xxl); }} +.gap-x-xxxl { --gap-x: var(--space-xxxl); > * { --sub-gap-x: var(--space-xxxl); }} +.gap-x-xxxxl { --gap-x: var(--space-xxxxl); > * { --sub-gap-x: var(--space-xxxxl); }} +.gap-x-0 { --gap-x: 0; > * { --sub-gap-x: 0; }} + +.gap-y-xxxxs { --gap-y: var(--space-xxxxs); > * { --sub-gap-y: var(--space-xxxxs); }} +.gap-y-xxxs { --gap-y: var(--space-xxxs); > * { --sub-gap-y: var(--space-xxxs); }} +.gap-y-xxs { --gap-y: var(--space-xxs); > * { --sub-gap-y: var(--space-xxs); }} +.gap-y-xs { --gap-y: var(--space-xs); > * { --sub-gap-y: var(--space-xs); }} +.gap-y-sm { --gap-y: var(--space-sm); > * { --sub-gap-y: var(--space-sm); }} +.gap-y-md { --gap-y: var(--space-md); > * { --sub-gap-y: var(--space-md); }} +.gap-y-lg { --gap-y: var(--space-lg); > * { --sub-gap-y: var(--space-lg); }} +.gap-y-xl { --gap-y: var(--space-xl); > * { --sub-gap-y: var(--space-xl); }} +.gap-y-xxl { --gap-y: var(--space-xxl); > * { --sub-gap-y: var(--space-xxl); }} +.gap-y-xxxl { --gap-y: var(--space-xxxl); > * { --sub-gap-y: var(--space-xxxl); }} +.gap-y-xxxxl { --gap-y: var(--space-xxxxl); > * { --sub-gap-y: var(--space-xxxxl); }} +.gap-y-0 { --gap-y: 0; > * { --sub-gap-y: 0; }} + +$grid-col-class-list: ''; // list of col-{span} classes + +@for $i from 1 through $grid-columns { + $grid-col-class-list: $grid-col-class-list + ".col-#{$i}"; + @if($i < $grid-columns) { + $grid-col-class-list: $grid-col-class-list + ', '; + } + .grid-col-#{$i} { --grid-columns: #{$i}; } // set number of grid columns + .col-#{$i} { --span: #{$i}; } // set grid item span +} + +#{$grid-col-class-list} { + flex-basis: calc(((100% - (var(--grid-columns) - var(--gap-modifier, 1)) * var(--sub-gap-x)) * var(--span) / var(--grid-columns)) + (var(--span) - 1) * var(--sub-gap-x)); + max-width: calc(((100% - (var(--grid-columns) - var(--gap-modifier, 1)) * var(--sub-gap-x)) * var(--span) / var(--grid-columns)) + (var(--span) - 1) * var(--sub-gap-x)); +} + +.col { // auto-expanding column + flex-grow: 1; + flex-basis: 0; + max-width: 100%; +} + +.col-content { // column width depends on its content + flex-grow: 0; + flex-basis: initial; + max-width: initial; +} + +// offset +$grid-offset-class-list: ''; // list of offset-{span} classes + +@for $i from 1 through $grid-columns - 1 { + $grid-offset-class-list: $grid-offset-class-list + ".offset-#{$i}"; + @if($i < $grid-columns) { + $grid-offset-class-list: $grid-offset-class-list + ', '; + } + .offset-#{$i} { --offset: #{$i}; } +} + +#{$grid-offset-class-list} { + margin-left: calc(((100% - (var(--grid-columns) - var(--gap-modifier, 1)) * var(--sub-gap-x)) * var(--offset) / var(--grid-columns)) + (var(--offset) + var(--offset-modifier, 0)) * var(--sub-gap-x)); +} + +// responsive variations +@each $breakpoint, $value in $breakpoints { + @include breakpoint(#{$breakpoint}) { + .gap-xxxxs\@#{$breakpoint} { --gap-x: var(--space-xxxxs); --gap-y: var(--space-xxxxs); > * { --sub-gap-x: var(--space-xxxxs); --sub-gap-y: var(--space-xxxxs); }} + .gap-xxxs\@#{$breakpoint} { --gap-x: var(--space-xxxs); --gap-y: var(--space-xxxs); > * { --sub-gap-x: var(--space-xxxs); --sub-gap-y: var(--space-xxxs); }} + .gap-xxs\@#{$breakpoint} { --gap-x: var(--space-xxs); --gap-y: var(--space-xxs); > * { --sub-gap-x: var(--space-xxs); --sub-gap-y: var(--space-xxs); }} + .gap-xs\@#{$breakpoint} { --gap-x: var(--space-xs); --gap-y: var(--space-xs); > * { --sub-gap-x: var(--space-xs); --sub-gap-y: var(--space-xs); }} + .gap-sm\@#{$breakpoint} { --gap-x: var(--space-sm); --gap-y: var(--space-sm); > * { --sub-gap-x: var(--space-sm); --sub-gap-y: var(--space-sm); }} + .gap-md\@#{$breakpoint} { --gap-x: var(--space-md); --gap-y: var(--space-md); > * { --sub-gap-x: var(--space-md); --sub-gap-y: var(--space-md); }} + .gap-lg\@#{$breakpoint} { --gap-x: var(--space-lg); --gap-y: var(--space-lg); > * { --sub-gap-x: var(--space-lg); --sub-gap-y: var(--space-lg); }} + .gap-xl\@#{$breakpoint} { --gap-x: var(--space-xl); --gap-y: var(--space-xl); > * { --sub-gap-x: var(--space-xl); --sub-gap-y: var(--space-xl); }} + .gap-xxl\@#{$breakpoint} { --gap-x: var(--space-xxl); --gap-y: var(--space-xxl); > * { --sub-gap-x: var(--space-xxl); --sub-gap-y: var(--space-xxl); }} + .gap-xxxl\@#{$breakpoint} { --gap-x: var(--space-xxxl); --gap-y: var(--space-xxxl); > * { --sub-gap-x: var(--space-xxxl); --sub-gap-y: var(--space-xxxl); }} + .gap-xxxxl\@#{$breakpoint} { --gap-x: var(--space-xxxxl); --gap-y: var(--space-xxxxl); > * { --sub-gap-x: var(--space-xxxxl); --sub-gap-y: var(--space-xxxxl); }} + .gap-0\@#{$breakpoint} { --gap-x: 0; --gap-y: 0; > * { --sub-gap-x: 0; --sub-gap-y: 0; }} + + .gap-x-xxxxs\@#{$breakpoint} { --gap-x: var(--space-xxxxs); > * { --sub-gap-x: var(--space-xxxxs); }} + .gap-x-xxxs\@#{$breakpoint} { --gap-x: var(--space-xxxs); > * { --sub-gap-x: var(--space-xxxs); }} + .gap-x-xxs\@#{$breakpoint} { --gap-x: var(--space-xxs); > * { --sub-gap-x: var(--space-xxs); }} + .gap-x-xs\@#{$breakpoint} { --gap-x: var(--space-xs); > * { --sub-gap-x: var(--space-xs); }} + .gap-x-sm\@#{$breakpoint} { --gap-x: var(--space-sm); > * { --sub-gap-x: var(--space-sm); }} + .gap-x-md\@#{$breakpoint} { --gap-x: var(--space-md); > * { --sub-gap-x: var(--space-md); }} + .gap-x-lg\@#{$breakpoint} { --gap-x: var(--space-lg); > * { --sub-gap-x: var(--space-lg); }} + .gap-x-xl\@#{$breakpoint} { --gap-x: var(--space-xl); > * { --sub-gap-x: var(--space-xl); }} + .gap-x-xxl\@#{$breakpoint} { --gap-x: var(--space-xxl); > * { --sub-gap-x: var(--space-xxl); }} + .gap-x-xxxl\@#{$breakpoint} { --gap-x: var(--space-xxxl); > * { --sub-gap-x: var(--space-xxxl); }} + .gap-x-xxxxl\@#{$breakpoint} { --gap-x: var(--space-xxxxl); > * { --sub-gap-x: var(--space-xxxxl); }} + .gap-x-0\@#{$breakpoint} { --gap-x: 0; > * { --sub-gap-x: 0; }} + + .gap-y-xxxxs\@#{$breakpoint} { --gap-y: var(--space-xxxxs); > * { --sub-gap-y: var(--space-xxxxs); }} + .gap-y-xxxs\@#{$breakpoint} { --gap-y: var(--space-xxxs); > * { --sub-gap-y: var(--space-xxxs); }} + .gap-y-xxs\@#{$breakpoint} { --gap-y: var(--space-xxs); > * { --sub-gap-y: var(--space-xxs); }} + .gap-y-xs\@#{$breakpoint} { --gap-y: var(--space-xs); > * { --sub-gap-y: var(--space-xs); }} + .gap-y-sm\@#{$breakpoint} { --gap-y: var(--space-sm); > * { --sub-gap-y: var(--space-sm); }} + .gap-y-md\@#{$breakpoint} { --gap-y: var(--space-md); > * { --sub-gap-y: var(--space-md); }} + .gap-y-lg\@#{$breakpoint} { --gap-y: var(--space-lg); > * { --sub-gap-y: var(--space-lg); }} + .gap-y-xl\@#{$breakpoint} { --gap-y: var(--space-xl); > * { --sub-gap-y: var(--space-xl); }} + .gap-y-xxl\@#{$breakpoint} { --gap-y: var(--space-xxl); > * { --sub-gap-y: var(--space-xxl); }} + .gap-y-xxxl\@#{$breakpoint} { --gap-y: var(--space-xxxl); > * { --sub-gap-y: var(--space-xxxl); }} + .gap-y-xxxxl\@#{$breakpoint} { --gap-y: var(--space-xxxxl); > * { --sub-gap-y: var(--space-xxxxl); }} + .gap-y-0\@#{$breakpoint} { --gap-y: 0; > * { --sub-gap-y: 0; }} + + $grid-col-class-list: ''; // list of col-{span} classes + + @for $i from 1 through $grid-columns { + $grid-col-class-list: $grid-col-class-list + ".col-#{$i}\\@#{$breakpoint}"; + @if($i < $grid-columns) { + $grid-col-class-list: $grid-col-class-list + ', '; + } + .grid-col-#{$i}\@#{$breakpoint} { --grid-columns: #{$i}; } // set number of grid columns + .col-#{$i}\@#{$breakpoint} { --span: #{$i}; } // set grid item span + } + + #{$grid-col-class-list} { + flex-basis: calc(((100% - (var(--grid-columns) - var(--gap-modifier, 1)) * var(--sub-gap-x)) * var(--span) / var(--grid-columns)) + (var(--span) - 1) * var(--sub-gap-x)); + max-width: calc(((100% - (var(--grid-columns) - var(--gap-modifier, 1)) * var(--sub-gap-x)) * var(--span) / var(--grid-columns)) + (var(--span) - 1) * var(--sub-gap-x)); + } + + .col\@#{$breakpoint} { // auto-expanding column + flex-grow: 1; + flex-basis: 0; + max-width: 100%; + } + + .col-content\@#{$breakpoint} { // column width depends on its content + flex-grow: 0; + flex-basis: initial; + max-width: initial; + } + + // offset + $grid-offset-class-list: ''; // list of offset-{span} classes + + @for $i from 1 through $grid-columns - 1 { + $grid-offset-class-list: $grid-offset-class-list + ".offset-#{$i}\\@#{$breakpoint}"; + @if($i < $grid-columns) { + $grid-offset-class-list: $grid-offset-class-list + ', '; + } + .offset-#{$i}\@#{$breakpoint} { --offset: #{$i}; } + } + + #{$grid-offset-class-list} { + margin-left: calc(((100% - (var(--grid-columns) - var(--gap-modifier, 1)) * var(--sub-gap-x)) * var(--offset) / var(--grid-columns)) + (var(--offset) + var(--offset-modifier, 0)) * var(--sub-gap-x)); + } + + .offset-0\@#{$breakpoint} { + margin-left: 0; + } + + @media not all and (min-resolution:.001dpcm) { + @supports (not(translate: none)) { + .offset-0\@#{$breakpoint} { + margin-left: var(--gap-x); + } + } + } + } +} + diff --git a/old-apps/web-shared/src/styles/base/_icons.scss b/old-apps/web-shared/src/styles/base/_icons.scss new file mode 100644 index 0000000..1674a7c --- /dev/null +++ b/old-apps/web-shared/src/styles/base/_icons.scss @@ -0,0 +1,62 @@ +// don't modify this file -> edit 📁 custom-style/_icons.scss to set your custom icons style + +:root { + // default icon sizes + --icon-xxxs: 8px; + --icon-xxs: 12px; + --icon-xs: 16px; + --icon-sm: 24px; + --icon-md: 32px; + --icon-lg: 48px; + --icon-xl: 64px; + --icon-xxl: 96px; + --icon-xxxl: 128px; +} + +.icon { + --size: 1em; + height: var(--size); + width: var(--size); + display: inline-block; + color: inherit; + fill: currentColor; + line-height: 1; + flex-shrink: 0; + max-width: initial; +} + +// icon size +.icon--xxxs { --size: var(--icon-xxxs); } +.icon--xxs { --size: var(--icon-xxs); } +.icon--xs { --size: var(--icon-xs); } +.icon--sm { --size: var(--icon-sm); } +.icon--md { --size: var(--icon-md); } +.icon--lg { --size: var(--icon-lg); } +.icon--xl { --size: var(--icon-xl); } +.icon--xxl { --size: var(--icon-xxl); } +.icon--xxxl { --size: var(--icon-xxxl); } + +.icon--is-spinning { // rotate the icon infinitely + animation: icon-spin 1s infinite linear; +} + +@keyframes icon-spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +// -------------------------------- + +// SVG <symbol> + +// -------------------------------- + +// enable icon color corrections +.icon use { + color: inherit; + fill: currentColor; +}
\ No newline at end of file diff --git a/old-apps/web-shared/src/styles/base/_mixins.scss b/old-apps/web-shared/src/styles/base/_mixins.scss new file mode 100644 index 0000000..8fe82f6 --- /dev/null +++ b/old-apps/web-shared/src/styles/base/_mixins.scss @@ -0,0 +1,151 @@ +@use 'sass:math'; + +// -------------------------------- + +// Typography + +// -------------------------------- + +// edit font rendering -> tip: use for light text on dark backgrounds +@mixin fontSmooth { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +// crop top space on text elements - caused by line height +@mixin lhCrop($line-height, $capital-letter: 1) { + &::before { + content: ''; + display: block; + height: 0; + width: 0; + margin-top: calc((#{$capital-letter} - #{$line-height}) * 0.5em); + } +} + +// edit text unit on a component level +@mixin textUnit($text-unit) { + --text-unit: #{$text-unit}; + font-size: var(--text-unit); +} + +// -------------------------------- + +// Spacing + +// -------------------------------- + +// edit space unit on a component level +@mixin spaceUnit($space-unit) { + --space-unit: #{$space-unit}; +} + +// -------------------------------- + +// Reset + +// -------------------------------- + +// reset user agent style +@mixin reset { + background-color: transparent; + padding: 0; + border: 0; + border-radius: 0; + color: inherit; + line-height: inherit; + appearance: none; +} + +// -------------------------------- + +// Colors + +// -------------------------------- + +// define HSL color variable +@mixin defineColorHSL($color, $hue, $saturation, $lightness) { + #{$color}: unquote("hsl(#{$hue}, #{$saturation}, #{$lightness})");#{$color}-h: #{$hue};#{$color}-s: #{$saturation};#{$color}-l: #{$lightness}; +} + +// return color with different opacity value +@function alpha($color, $opacity) { + $color: str-replace($color, 'var('); + $color: str-replace($color, ')'); + $color-h: var(#{$color+'-h'}); + $color-s: var(#{$color+'-s'}); + $color-l: var(#{$color+'-l'}); + @return hsla($color-h, $color-s, $color-l, $opacity); +} + +// return color with different lightness value +@function lightness($color, $lightnessMultiplier) { + $color: str-replace($color, 'var('); + $color: str-replace($color, ')'); + $color-h: var(#{$color+'-h'}); + $color-s: var(#{$color+'-s'}); + $color-l: var(#{$color+'-l'}); + @return hsl($color-h, $color-s, calc(#{$color-l} * #{$lightnessMultiplier})); +} + +// modify color HSLA values +@function adjustHSLA($color, $hueMultiplier: 1, $saturationMultiplier: 1, $lightnessMultiplier: 1, $opacity: 1) { + $color: str-replace($color, 'var('); + $color: str-replace($color, ')'); + $color-h: var(#{$color+'-h'}); + $color-s: var(#{$color+'-s'}); + $color-l: var(#{$color+'-l'}); + @return hsla(calc(#{$color-h} * #{$hueMultiplier}), calc(#{$color-s} * #{$saturationMultiplier}), calc(#{$color-l} * #{$lightnessMultiplier}), $opacity); +} + +// replace substring with another string +// credits: https://css-tricks.com/snippets/sass/str-replace-function/ +@function str-replace($string, $search, $replace: '') { + $index: str-index($string, $search); + @if $index { + @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace); + } + @return $string; +} + +// -------------------------------- + +// Accessibility + +// -------------------------------- + +// hide - content made available only to screen readers +@mixin srHide { + position: absolute; + clip: rect(1px, 1px, 1px, 1px); + clip-path: inset(50%); +} + +// show +@mixin srShow { + position: static; + clip: auto; + clip-path: none; +} + +// -------------------------------- + +// CSS Triangle + +// -------------------------------- + +@mixin triangle ($direction: up, $width: 12px, $color: red) { + width: 0; + height: 0; + border: $width solid transparent; + + @if( $direction == left ) { + border-right-color: $color; + } @else if( $direction == right ) { + border-left-color: $color; + } @else if( $direction == down ) { + border-top-color: $color; + } @else { + border-bottom-color: $color; + } +} diff --git a/old-apps/web-shared/src/styles/base/_reset.scss b/old-apps/web-shared/src/styles/base/_reset.scss new file mode 100644 index 0000000..5ba4534 --- /dev/null +++ b/old-apps/web-shared/src/styles/base/_reset.scss @@ -0,0 +1,83 @@ +*, *::after, *::before { + box-sizing: inherit; +} + +* { + font: inherit; +} + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video, hr { + margin: 0; + padding: 0; + border: 0; +} + +html { + box-sizing: border-box; +} + +body { + background-color: var(--color-bg, white); +} + +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section, main, form legend { + display: block; +} + +ol, ul, menu { + list-style: none; +} + +blockquote, q { + quotes: none; +} + +button, input, textarea, select { + margin: 0; +} + +.btn, .form-control, .link, .reset { // reset style of buttons + form controls + background-color: transparent; + padding: 0; + border: 0; + border-radius: 0; + color: inherit; + line-height: inherit; + appearance: none; +} + +select.form-control::-ms-expand { + display: none; // hide Select default icon on IE +} + +textarea { + resize: vertical; + overflow: auto; + vertical-align: top; +} + +input::-ms-clear { + display: none; // hide X icon in IE and Edge +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +img, video, svg { + max-width: 100%; +}
\ No newline at end of file diff --git a/old-apps/web-shared/src/styles/base/_shared-styles.scss b/old-apps/web-shared/src/styles/base/_shared-styles.scss new file mode 100644 index 0000000..dae02fe --- /dev/null +++ b/old-apps/web-shared/src/styles/base/_shared-styles.scss @@ -0,0 +1,34 @@ +// don't modify this file -> edit 📁 custom-style/_shared-style.scss to set your custom shared styles + +:root { + // radius + --radius-sm: calc(var(--radius, 0.25em)/2); + --radius-md: var(--radius, 0.25em); + --radius-lg: calc(var(--radius, 0.25em)*2); + + // box shadow + --shadow-xs: 0 0.1px 0.3px rgba(0, 0, 0, 0.06), + 0 1px 2px rgba(0, 0, 0, 0.12); + --shadow-sm: 0 0.3px 0.4px rgba(0, 0, 0, 0.025), + 0 0.9px 1.5px rgba(0, 0, 0, 0.05), + 0 3.5px 6px rgba(0, 0, 0, 0.1); + --shadow-md: 0 0.9px 1.5px rgba(0, 0, 0, 0.03), + 0 3.1px 5.5px rgba(0, 0, 0, 0.08), + 0 14px 25px rgba(0, 0, 0, 0.12); + --shadow-lg: 0 1.2px 1.9px -1px rgba(0, 0, 0, 0.014), + 0 3.3px 5.3px -1px rgba(0, 0, 0, 0.038), + 0 8.5px 12.7px -1px rgba(0, 0, 0, 0.085), + 0 30px 42px -1px rgba(0, 0, 0, 0.15); + --shadow-xl: 0 1.5px 2.1px -6px rgba(0, 0, 0, 0.012), + 0 3.6px 5.2px -6px rgba(0, 0, 0, 0.035), + 0 7.3px 10.6px -6px rgba(0, 0, 0, 0.07), + 0 16.2px 21.9px -6px rgba(0, 0, 0, 0.117), + 0 46px 60px -6px rgba(0, 0, 0, 0.2); + + // timing functions + // credits: https://github.com/ai/easings.net + --ease-in-out: cubic-bezier(0.645, 0.045, 0.355, 1); + --ease-in: cubic-bezier(0.55, 0.055, 0.675, 0.19); + --ease-out: cubic-bezier(0.215, 0.61, 0.355, 1); + --ease-out-back: cubic-bezier(0.34, 1.56, 0.64, 1); +}
\ No newline at end of file diff --git a/old-apps/web-shared/src/styles/base/_spacing.scss b/old-apps/web-shared/src/styles/base/_spacing.scss new file mode 100644 index 0000000..24e6645 --- /dev/null +++ b/old-apps/web-shared/src/styles/base/_spacing.scss @@ -0,0 +1,20 @@ +// don't modify this file -> edit 📁 custom-style/_spacing.scss to set your custom spacing scale + +:root { + --space-unit: 1rem; +} + +:root, * { + --space-xxxxs: calc(0.125 * var(--space-unit)); + --space-xxxs: calc(0.25 * var(--space-unit)); + --space-xxs: calc(0.375 * var(--space-unit)); + --space-xs: calc(0.5 * var(--space-unit)); + --space-sm: calc(0.75 * var(--space-unit)); + --space-md: calc(1.25 * var(--space-unit)); + --space-lg: calc(2 * var(--space-unit)); + --space-xl: calc(3.25 * var(--space-unit)); + --space-xxl: calc(5.25 * var(--space-unit)); + --space-xxxl: calc(8.5 * var(--space-unit)); + --space-xxxxl: calc(13.75 * var(--space-unit)); + --component-padding: var(--space-md); +} diff --git a/old-apps/web-shared/src/styles/base/_typography.scss b/old-apps/web-shared/src/styles/base/_typography.scss new file mode 100644 index 0000000..85b974a --- /dev/null +++ b/old-apps/web-shared/src/styles/base/_typography.scss @@ -0,0 +1,185 @@ +// don't modify this file -> edit 📁 custom-style/_typography.scss to set your custom typography + +@use 'breakpoints' as *; + +:root { + --heading-line-height: 1.2; + --body-line-height: 1.4; +} + +body { + font-size: var(--text-base-size, 1rem); + font-family: var(--font-primary, sans-serif); + color: var(--color-contrast-high, hsl(210, 7%, 21%)); + font-weight: var(--body-font-weight, normal); +} + +h1, h2, h3, h4 { + color: var(--color-contrast-higher, hsl(204, 28%, 7%)); + line-height: var(--heading-line-height, 1.2); + font-weight: var(--heading-font-weight, 700); +} + +h1 { + font-size: var(--text-xxl, 2rem); +} + +h2 { + font-size: var(--text-xl, 1.75rem); +} + +h3 { + font-size: var(--text-lg, 1.375rem); +} + +h4 { + font-size: var(--text-md, 1.125rem); +} + +small { + font-size: var(--text-sm, 0.75rem); +} + +// -------------------------------- + +// Inline Text + +// -------------------------------- + +a, .link { + color: var(--color-primary, hsl(250, 84%, 54%)); + text-decoration: underline; +} + +strong { + font-weight: bold; +} + +s { + text-decoration: line-through; +} + +u { + text-decoration: underline; +} + +// -------------------------------- + +// Text Component - Class used to stylize text blocks + +// -------------------------------- + +.text-component { + h1, h2, h3, h4 { + line-height: calc(var(--heading-line-height) * var(--line-height-multiplier, 1)); + margin-bottom: calc(var(--space-unit) * 0.3125 * var(--text-space-y-multiplier, 1)); + } + + h2, h3, h4 { + margin-top: calc(var(--space-unit) * 0.9375 * var(--text-space-y-multiplier, 1)); + } + + p, blockquote, ul li, ol li { + line-height: calc(var(--body-line-height) * var(--line-height-multiplier, 1)); + } + + ul, ol, p, blockquote, .text-component__block { + margin-bottom: calc(var(--space-unit) * 0.9375 * var(--text-space-y-multiplier, 1)); + } + + ul, ol { + list-style-position: inside; + + ul, ol { + padding-left: 1em; + margin-bottom: 0; + } + } + + ul { + list-style-type: disc; + } + + ol { + list-style-type: decimal; + } + + img { + display: block; + margin: 0 auto; + } + + figcaption { + text-align: center; + margin-top: calc(var(--space-unit) * 0.5); + } + + em { + font-style: italic; + } + + hr { + margin-top: calc(var(--space-unit) * 1.875 * var(--text-space-y-multiplier, 1)); + margin-bottom: calc(var(--space-unit) * 1.875 * var(--text-space-y-multiplier, 1)); + margin-left: auto; + margin-right: auto; + } + + > *:first-child { + margin-top: 0; + } + + > *:last-child { + margin-bottom: 0; + } +} + +// text block container +.text-component__block--full-width { + width: 100vw; + margin-left: calc(50% - 50vw); +} + +@include breakpoint(sm) { + .text-component__block--left, + .text-component__block--right { + width: 45%; + + img { + width: 100%; + } + } + + .text-component__block--left { + float: left; + margin-right: calc(var(--space-unit) * 0.9375 * var(--text-space-y-multiplier, 1)); + } + + .text-component__block--right { + float: right; + margin-left: calc(var(--space-unit) * 0.9375 * var(--text-space-y-multiplier, 1)); + } +} + +// outset content +@include breakpoint(xl) { + .text-component__block--outset { + width: calc(100% + 10.5 * var(--space-unit)); + + img { + width: 100%; + } + } + + .text-component__block--outset:not(.text-component__block--right) { + margin-left: calc(-5.25 * var(--space-unit)); + } + + .text-component__block--left, .text-component__block--right { + width: 50%; + } + + .text-component__block--right.text-component__block--outset { + margin-right: calc(-5.25 * var(--space-unit)); + } +}
\ No newline at end of file diff --git a/old-apps/web-shared/src/styles/base/_util.scss b/old-apps/web-shared/src/styles/base/_util.scss new file mode 100644 index 0000000..c384c2c --- /dev/null +++ b/old-apps/web-shared/src/styles/base/_util.scss @@ -0,0 +1,1738 @@ +@use 'mixins' as *; +@use 'breakpoints' as *; + +// -------------------------------- + +// Flexbox + +// -------------------------------- + +.flex { display: flex; } +.inline-flex { display: inline-flex; } +.flex-wrap { flex-wrap: wrap; } +.flex-nowrap { flex-wrap: nowrap; } +.flex-column { flex-direction: column; } +.flex-column-reverse { flex-direction: column-reverse; } +.flex-row { flex-direction: row; } +.flex-row-reverse { flex-direction: row-reverse; } +.flex-center { justify-content: center; align-items: center; } + + +// flex items +.flex-grow { flex-grow: 1; } +.flex-grow-0 { flex-grow: 0; } +.flex-shrink { flex-shrink: 1; } +.flex-shrink-0 { flex-shrink: 0; } +.flex-basis-0 { flex-basis: 0; } + +// -------------------------------- + +// Justify Content + +// -------------------------------- + +.justify-start { justify-content: flex-start; } +.justify-end { justify-content: flex-end; } +.justify-center { justify-content: center; } +.justify-between { justify-content: space-between; } + +// -------------------------------- + +// Align Items + +// -------------------------------- + +.items-center { align-items: center; } +.items-start { align-items: flex-start; } +.items-end { align-items: flex-end; } +.items-baseline { align-items: baseline; } + +// -------------------------------- + +// Order + +// -------------------------------- + +.order-1 { order: 1; } +.order-2 { order: 2; } +.order-3 { order: 3; } + +// -------------------------------- + +// Aspect Ratio + +// -------------------------------- + +[class^="aspect-ratio"], [class*=" aspect-ratio"] { + --aspect-ratio: calc(16/9); + position: relative; + height: 0; + padding-bottom: calc(100%/(var(--aspect-ratio))); + + > * { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + + &:not(iframe) { + object-fit: cover; + } + } +} + +.aspect-ratio-16\:9 { --aspect-ratio: calc(16/9); } +.aspect-ratio-3\:2 { --aspect-ratio: calc(3/2); } +.aspect-ratio-4\:3 { --aspect-ratio: calc(4/3); } +.aspect-ratio-5\:4 { --aspect-ratio: calc(5/4); } +.aspect-ratio-1\:1 { --aspect-ratio: calc(1/1); } +.aspect-ratio-4\:5 { --aspect-ratio: calc(4/5); } +.aspect-ratio-3\:4 { --aspect-ratio: calc(3/4); } +.aspect-ratio-2\:3 { --aspect-ratio: calc(2/3); } +.aspect-ratio-9\:16 { --aspect-ratio: calc(9/16); } + +// -------------------------------- + +// Display + +// -------------------------------- + +.block { display: block; } +.inline-block { display: inline-block; } +.inline { display: inline; } +.contents { display: contents; } +.hide { display: none; } + +// -------------------------------- + +// Space unit + +// -------------------------------- + +.space-unit-rem { --space-unit: 1rem; } +.space-unit-em { --space-unit: 1em; } +.space-unit-px { --space-unit: 16px; } + +// -------------------------------- + +// Margin + +// -------------------------------- + +.margin-xxxxs { margin: var(--space-xxxxs); } +.margin-xxxs { margin: var(--space-xxxs); } +.margin-xxs { margin: var(--space-xxs); } +.margin-xs { margin: var(--space-xs); } +.margin-sm { margin: var(--space-sm); } +.margin-md { margin: var(--space-md); } +.margin-lg { margin: var(--space-lg); } +.margin-xl { margin: var(--space-xl); } +.margin-xxl { margin: var(--space-xxl); } +.margin-xxxl { margin: var(--space-xxxl); } +.margin-xxxxl { margin: var(--space-xxxxl); } +.margin-auto { margin: auto; } +.margin-0 { margin: 0; } + +.margin-top-xxxxs { margin-top: var(--space-xxxxs); } +.margin-top-xxxs { margin-top: var(--space-xxxs); } +.margin-top-xxs { margin-top: var(--space-xxs); } +.margin-top-xs { margin-top: var(--space-xs); } +.margin-top-sm { margin-top: var(--space-sm); } +.margin-top-md { margin-top: var(--space-md); } +.margin-top-lg { margin-top: var(--space-lg); } +.margin-top-xl { margin-top: var(--space-xl); } +.margin-top-xxl { margin-top: var(--space-xxl); } +.margin-top-xxxl { margin-top: var(--space-xxxl); } +.margin-top-xxxxl { margin-top: var(--space-xxxxl); } +.margin-top-auto { margin-top: auto; } +.margin-top-0 { margin-top: 0; } + +.margin-bottom-xxxxs { margin-bottom: var(--space-xxxxs); } +.margin-bottom-xxxs { margin-bottom: var(--space-xxxs); } +.margin-bottom-xxs { margin-bottom: var(--space-xxs); } +.margin-bottom-xs { margin-bottom: var(--space-xs); } +.margin-bottom-sm { margin-bottom: var(--space-sm); } +.margin-bottom-md { margin-bottom: var(--space-md); } +.margin-bottom-lg { margin-bottom: var(--space-lg); } +.margin-bottom-xl { margin-bottom: var(--space-xl); } +.margin-bottom-xxl { margin-bottom: var(--space-xxl); } +.margin-bottom-xxxl { margin-bottom: var(--space-xxxl); } +.margin-bottom-xxxxl { margin-bottom: var(--space-xxxxl); } +.margin-bottom-auto { margin-bottom: auto; } +.margin-bottom-0 { margin-bottom: 0; } + +.margin-right-xxxxs { margin-right: var(--space-xxxxs); } +.margin-right-xxxs { margin-right: var(--space-xxxs); } +.margin-right-xxs { margin-right: var(--space-xxs); } +.margin-right-xs { margin-right: var(--space-xs); } +.margin-right-sm { margin-right: var(--space-sm); } +.margin-right-md { margin-right: var(--space-md); } +.margin-right-lg { margin-right: var(--space-lg); } +.margin-right-xl { margin-right: var(--space-xl); } +.margin-right-xxl { margin-right: var(--space-xxl); } +.margin-right-xxxl { margin-right: var(--space-xxxl); } +.margin-right-xxxxl { margin-right: var(--space-xxxxl); } +.margin-right-auto { margin-right: auto; } +.margin-right-0 { margin-right: 0; } + +.margin-left-xxxxs { margin-left: var(--space-xxxxs); } +.margin-left-xxxs { margin-left: var(--space-xxxs); } +.margin-left-xxs { margin-left: var(--space-xxs); } +.margin-left-xs { margin-left: var(--space-xs); } +.margin-left-sm { margin-left: var(--space-sm); } +.margin-left-md { margin-left: var(--space-md); } +.margin-left-lg { margin-left: var(--space-lg); } +.margin-left-xl { margin-left: var(--space-xl); } +.margin-left-xxl { margin-left: var(--space-xxl); } +.margin-left-xxxl { margin-left: var(--space-xxxl); } +.margin-left-xxxxl { margin-left: var(--space-xxxxl); } +.margin-left-auto { margin-left: auto; } +.margin-left-0 { margin-left: 0; } + +.margin-x-xxxxs { margin-left: var(--space-xxxxs); margin-right: var(--space-xxxxs); } +.margin-x-xxxs { margin-left: var(--space-xxxs); margin-right: var(--space-xxxs); } +.margin-x-xxs { margin-left: var(--space-xxs); margin-right: var(--space-xxs); } +.margin-x-xs { margin-left: var(--space-xs); margin-right: var(--space-xs); } +.margin-x-sm { margin-left: var(--space-sm); margin-right: var(--space-sm); } +.margin-x-md { margin-left: var(--space-md); margin-right: var(--space-md); } +.margin-x-lg { margin-left: var(--space-lg); margin-right: var(--space-lg); } +.margin-x-xl { margin-left: var(--space-xl); margin-right: var(--space-xl); } +.margin-x-xxl { margin-left: var(--space-xxl); margin-right: var(--space-xxl); } +.margin-x-xxxl { margin-left: var(--space-xxxl); margin-right: var(--space-xxxl); } +.margin-x-xxxxl { margin-left: var(--space-xxxxl); margin-right: var(--space-xxxxl); } +.margin-x-auto { margin-left: auto; margin-right: auto; } +.margin-x-0 { margin-left: 0; margin-right: 0; } + +.margin-y-xxxxs { margin-top: var(--space-xxxxs); margin-bottom: var(--space-xxxxs); } +.margin-y-xxxs { margin-top: var(--space-xxxs); margin-bottom: var(--space-xxxs); } +.margin-y-xxs { margin-top: var(--space-xxs); margin-bottom: var(--space-xxs); } +.margin-y-xs { margin-top: var(--space-xs); margin-bottom: var(--space-xs); } +.margin-y-sm { margin-top: var(--space-sm); margin-bottom: var(--space-sm); } +.margin-y-md { margin-top: var(--space-md); margin-bottom: var(--space-md); } +.margin-y-lg { margin-top: var(--space-lg); margin-bottom: var(--space-lg); } +.margin-y-xl { margin-top: var(--space-xl); margin-bottom: var(--space-xl); } +.margin-y-xxl { margin-top: var(--space-xxl); margin-bottom: var(--space-xxl); } +.margin-y-xxxl { margin-top: var(--space-xxxl); margin-bottom: var(--space-xxxl); } +.margin-y-xxxxl { margin-top: var(--space-xxxxl); margin-bottom: var(--space-xxxxl); } +.margin-y-auto { margin-top: auto; margin-bottom: auto; } +.margin-y-0 { margin-top: 0; margin-bottom: 0; } + +// -------------------------------- + +// Padding + +// -------------------------------- + +.padding-xxxxs { padding: var(--space-xxxxs); } +.padding-xxxs { padding: var(--space-xxxs); } +.padding-xxs { padding: var(--space-xxs); } +.padding-xs { padding: var(--space-xs); } +.padding-sm { padding: var(--space-sm); } +.padding-md { padding: var(--space-md); } +.padding-lg { padding: var(--space-lg); } +.padding-xl { padding: var(--space-xl); } +.padding-xxl { padding: var(--space-xxl); } +.padding-xxxl { padding: var(--space-xxxl); } +.padding-xxxxl { padding: var(--space-xxxxl); } +.padding-0 { padding: 0; } +.padding-component { padding: var(--component-padding); } + +.padding-top-xxxxs { padding-top: var(--space-xxxxs); } +.padding-top-xxxs { padding-top: var(--space-xxxs); } +.padding-top-xxs { padding-top: var(--space-xxs); } +.padding-top-xs { padding-top: var(--space-xs); } +.padding-top-sm { padding-top: var(--space-sm); } +.padding-top-md { padding-top: var(--space-md); } +.padding-top-lg { padding-top: var(--space-lg); } +.padding-top-xl { padding-top: var(--space-xl); } +.padding-top-xxl { padding-top: var(--space-xxl); } +.padding-top-xxxl { padding-top: var(--space-xxxl); } +.padding-top-xxxxl { padding-top: var(--space-xxxxl); } +.padding-top-0 { padding-top: 0; } +.padding-top-component { padding-top: var(--component-padding); } + +.padding-bottom-xxxxs { padding-bottom: var(--space-xxxxs); } +.padding-bottom-xxxs { padding-bottom: var(--space-xxxs); } +.padding-bottom-xxs { padding-bottom: var(--space-xxs); } +.padding-bottom-xs { padding-bottom: var(--space-xs); } +.padding-bottom-sm { padding-bottom: var(--space-sm); } +.padding-bottom-md { padding-bottom: var(--space-md); } +.padding-bottom-lg { padding-bottom: var(--space-lg); } +.padding-bottom-xl { padding-bottom: var(--space-xl); } +.padding-bottom-xxl { padding-bottom: var(--space-xxl); } +.padding-bottom-xxxl { padding-bottom: var(--space-xxxl); } +.padding-bottom-xxxxl { padding-bottom: var(--space-xxxxl); } +.padding-bottom-0 { padding-bottom: 0; } +.padding-bottom-component { padding-bottom: var(--component-padding); } + +.padding-right-xxxxs { padding-right: var(--space-xxxxs); } +.padding-right-xxxs { padding-right: var(--space-xxxs); } +.padding-right-xxs { padding-right: var(--space-xxs); } +.padding-right-xs { padding-right: var(--space-xs); } +.padding-right-sm { padding-right: var(--space-sm); } +.padding-right-md { padding-right: var(--space-md); } +.padding-right-lg { padding-right: var(--space-lg); } +.padding-right-xl { padding-right: var(--space-xl); } +.padding-right-xxl { padding-right: var(--space-xxl); } +.padding-right-xxxl { padding-right: var(--space-xxxl); } +.padding-right-xxxxl { padding-right: var(--space-xxxxl); } +.padding-right-0 { padding-right: 0; } +.padding-right-component { padding-right: var(--component-padding); } + +.padding-left-xxxxs { padding-left: var(--space-xxxxs); } +.padding-left-xxxs { padding-left: var(--space-xxxs); } +.padding-left-xxs { padding-left: var(--space-xxs); } +.padding-left-xs { padding-left: var(--space-xs); } +.padding-left-sm { padding-left: var(--space-sm); } +.padding-left-md { padding-left: var(--space-md); } +.padding-left-lg { padding-left: var(--space-lg); } +.padding-left-xl { padding-left: var(--space-xl); } +.padding-left-xxl { padding-left: var(--space-xxl); } +.padding-left-xxxl { padding-left: var(--space-xxxl); } +.padding-left-xxxxl { padding-left: var(--space-xxxxl); } +.padding-left-0 { padding-left: 0; } +.padding-left-component { padding-left: var(--component-padding); } + +.padding-x-xxxxs { padding-left: var(--space-xxxxs); padding-right: var(--space-xxxxs); } +.padding-x-xxxs { padding-left: var(--space-xxxs); padding-right: var(--space-xxxs); } +.padding-x-xxs { padding-left: var(--space-xxs); padding-right: var(--space-xxs); } +.padding-x-xs { padding-left: var(--space-xs); padding-right: var(--space-xs); } +.padding-x-sm { padding-left: var(--space-sm); padding-right: var(--space-sm); } +.padding-x-md { padding-left: var(--space-md); padding-right: var(--space-md); } +.padding-x-lg { padding-left: var(--space-lg); padding-right: var(--space-lg); } +.padding-x-xl { padding-left: var(--space-xl); padding-right: var(--space-xl); } +.padding-x-xxl { padding-left: var(--space-xxl); padding-right: var(--space-xxl); } +.padding-x-xxxl { padding-left: var(--space-xxxl); padding-right: var(--space-xxxl); } +.padding-x-xxxxl { padding-left: var(--space-xxxxl); padding-right: var(--space-xxxxl); } +.padding-x-0 { padding-left: 0; padding-right: 0; } +.padding-x-component { padding-left: var(--component-padding); padding-right: var(--component-padding); } + +.padding-y-xxxxs { padding-top: var(--space-xxxxs); padding-bottom: var(--space-xxxxs); } +.padding-y-xxxs { padding-top: var(--space-xxxs); padding-bottom: var(--space-xxxs); } +.padding-y-xxs { padding-top: var(--space-xxs); padding-bottom: var(--space-xxs); } +.padding-y-xs { padding-top: var(--space-xs); padding-bottom: var(--space-xs); } +.padding-y-sm { padding-top: var(--space-sm); padding-bottom: var(--space-sm); } +.padding-y-md { padding-top: var(--space-md); padding-bottom: var(--space-md); } +.padding-y-lg { padding-top: var(--space-lg); padding-bottom: var(--space-lg); } +.padding-y-xl { padding-top: var(--space-xl); padding-bottom: var(--space-xl); } +.padding-y-xxl { padding-top: var(--space-xxl); padding-bottom: var(--space-xxl); } +.padding-y-xxxl { padding-top: var(--space-xxxl); padding-bottom: var(--space-xxxl); } +.padding-y-xxxxl { padding-top: var(--space-xxxxl); padding-bottom: var(--space-xxxxl); } +.padding-y-0 { padding-top: 0; padding-bottom: 0; } +.padding-y-component { padding-top: var(--component-padding); padding-bottom: var(--component-padding); } + +// -------------------------------- + +// Vertical Align + +// -------------------------------- + +.align-baseline { vertical-align: baseline; } +.align-top { vertical-align: top; } +.align-middle { vertical-align: middle; } +.align-bottom { vertical-align: bottom; } + +// -------------------------------- + +// Typography + +// -------------------------------- + +.truncate, .text-truncate { // truncate text if it exceeds its parent + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.text-replace { // replace text with bg img + overflow: hidden; + color: transparent; + text-indent: 100%; + white-space: nowrap; +} + +.break-word { + overflow-wrap: break-word; + min-width: 0; +} + +// -------------------------------- + +// Font Size + +// -------------------------------- + +.text-unit-rem, .text-unit-em, .text-unit-px { + font-size: var(--text-unit); +} + +.text-unit-rem { --text-unit: 1rem; } +.text-unit-em { --text-unit: 1em; } +.text-unit-px { --text-unit: 16px; } + +.text-xs { font-size: var(--text-xs, 0.6875rem); } +.text-sm { font-size: var(--text-sm, 0.75rem); } +.text-base { font-size: var(--text-unit, 1rem); } +.text-md { font-size: var(--text-md, 1.125rem); } +.text-lg { font-size: var(--text-lg, 1.375rem); } +.text-xl { font-size: var(--text-xl, 1.75rem); } +.text-xxl { font-size: var(--text-xxl, 2rem); } +.text-xxxl { font-size: var(--text-xxxl, 2.5rem); } +.text-xxxxl { font-size: var(--text-xxxxl, 3rem); } + +// -------------------------------- + +// Text Transform + +// -------------------------------- + +.text-uppercase { text-transform: uppercase; } +.text-capitalize { text-transform: capitalize; } + +// -------------------------------- + +// Letter Spacing + +// -------------------------------- + +.letter-spacing-xs { letter-spacing: -0.1em; } +.letter-spacing-sm { letter-spacing: -0.05em; } +.letter-spacing-md { letter-spacing: 0.05em; } +.letter-spacing-lg { letter-spacing: 0.1em; } +.letter-spacing-xl { letter-spacing: 0.2em; } + +// -------------------------------- + +// Font Weight + +// -------------------------------- + +.font-light { font-weight: 300; } +.font-normal { font-weight: 400; } +.font-medium { font-weight: 500; } +.font-semibold { font-weight: 600; } +.font-bold, .text-bold { font-weight: 700; } + +// -------------------------------- + +// Font Style + +// -------------------------------- + +.font-italic { font-style: italic; } + +// -------------------------------- + +// Font Smooth + +// -------------------------------- + +.font-smooth { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +// -------------------------------- + +// Font Family + +// -------------------------------- + +.font-primary { font-family: var(--font-primary); } + +// -------------------------------- + +// Text Align + +// -------------------------------- + +.text-center { text-align: center; } +.text-left { text-align: left; } +.text-right { text-align: right; } +.text-justify { text-align: justify; } + +// -------------------------------- + +// Text Decoration + +// -------------------------------- + +.text-line-through { text-decoration: line-through; } +.text-underline { text-decoration: underline; } +.text-decoration-none { text-decoration: none; } + +// -------------------------------- + +// Text Shadow + +// -------------------------------- + +.text-shadow-xs { text-shadow: 0 1px 1px rgba(#000, 0.15); } +.text-shadow-sm { text-shadow: 0 1px 2px rgba(#000, 0.25); } +.text-shadow-md { text-shadow: 0 1px 2px rgba(#000, 0.1), 0 2px 4px rgba(#000, 0.2); } +.text-shadow-lg { text-shadow: 0 1px 4px rgba(#000, 0.1), 0 2px 8px rgba(#000, 0.15), 0 4px 16px rgba(#000, 0.2); } +.text-shadow-xl { text-shadow: 0 1px 4px rgba(#000, 0.1), 0 2px 8px rgba(#000, 0.15), 0 4px 16px rgba(#000, 0.2), 0 6px 24px rgba(#000, 0.25); } +.text-shadow-none { text-shadow: none; } + +// -------------------------------- + +// .text-component vertical spacing + +// -------------------------------- + +.text-space-y-xxs { --text-space-y-multiplier: 0.25 !important; } +.text-space-y-xs { --text-space-y-multiplier: 0.5 !important; } +.text-space-y-sm { --text-space-y-multiplier: 0.75 !important; } +.text-space-y-md { --text-space-y-multiplier: 1.25 !important; } +.text-space-y-lg { --text-space-y-multiplier: 1.5 !important; } +.text-space-y-xl { --text-space-y-multiplier: 1.75 !important; } +.text-space-y-xxl { --text-space-y-multiplier: 2 !important; } + +// -------------------------------- + +// Line Height + +// -------------------------------- + +.line-height-xs { + --heading-line-height: 1; + --body-line-height: 1.1; + + &:not(.text-component) { + line-height: 1.1; + } +} + +.line-height-sm { + --heading-line-height: 1.1; + --body-line-height: 1.2; + + &:not(.text-component) { + line-height: 1.2; + } +} + +.line-height-md { + --heading-line-height: 1.15; + --body-line-height: 1.4; + + &:not(.text-component) { + line-height: 1.4; + } +} + +.line-height-lg { + --heading-line-height: 1.22; + --body-line-height: 1.58; + + &:not(.text-component) { + line-height: 1.58; + } +} + +.line-height-xl { + --heading-line-height: 1.3; + --body-line-height: 1.72; + + &:not(.text-component) { + line-height: 1.72; + } +} + +.line-height-body { line-height: var(--body-line-height); } +.line-height-heading { line-height: var(--heading-line-height); } +.line-height-normal { line-height: normal !important; } +.line-height-1 { line-height: 1 !important; } + +// -------------------------------- + +// White Space + +// -------------------------------- + +.ws-nowrap, .text-nowrap { white-space: nowrap; } + +// -------------------------------- + +// Cursor + +// -------------------------------- + +.cursor-pointer { cursor: pointer; } +.cursor-default { cursor: default; } + +// -------------------------------- + +// Pointer Events + +// -------------------------------- + +.pointer-events-auto { pointer-events: auto; } +.pointer-events-none { pointer-events: none; } + +// -------------------------------- + +// User Select + +// -------------------------------- + +.user-select-none { user-select: none; } +.user-select-all { user-select: all; } + +// -------------------------------- + +// Color + +// -------------------------------- + +[class^="color-"], [class*=" color-"] { --color-o: 1; } + +.color-inherit { color: inherit; } + +.color-bg-darker { color: alpha(var(--color-bg-darker), var(--color-o, 1)); } +.color-bg-dark { color: alpha(var(--color-bg-dark), var(--color-o, 1)); } +.color-bg { color: alpha(var(--color-bg), var(--color-o, 1)); } +.color-bg-light { color: alpha(var(--color-bg-light), var(--color-o, 1)); } +.color-bg-lighter { color: alpha(var(--color-bg-lighter), var(--color-o, 1)); } + +.color-contrast-lower { color: alpha(var(--color-contrast-lower), var(--color-o, 1)); } +.color-contrast-low { color: alpha(var(--color-contrast-low), var(--color-o, 1)); } +.color-contrast-medium { color: alpha(var(--color-contrast-medium), var(--color-o, 1)); } +.color-contrast-high { color: alpha(var(--color-contrast-high), var(--color-o, 1)); } +.color-contrast-higher { color: alpha(var(--color-contrast-higher), var(--color-o, 1)); } + +.color-primary-darker { color: alpha(var(--color-primary-darker), var(--color-o, 1)); } +.color-primary-dark { color: alpha(var(--color-primary-dark), var(--color-o, 1)); } +.color-primary { color: alpha(var(--color-primary), var(--color-o, 1)); } +.color-primary-light { color: alpha(var(--color-primary-light), var(--color-o, 1)); } +.color-primary-lighter { color: alpha(var(--color-primary-lighter), var(--color-o, 1)); } + +.color-accent-darker { color: alpha(var(--color-accent-darker), var(--color-o, 1)); } +.color-accent-dark { color: alpha(var(--color-accent-dark), var(--color-o, 1)); } +.color-accent { color: alpha(var(--color-accent), var(--color-o, 1)); } +.color-accent-light { color: alpha(var(--color-accent-light), var(--color-o, 1)); } +.color-accent-lighter { color: alpha(var(--color-accent-lighter), var(--color-o, 1)); } + +.color-success-darker { color: alpha(var(--color-success-darker), var(--color-o, 1)); } +.color-success-dark { color: alpha(var(--color-success-dark), var(--color-o, 1)); } +.color-success { color: alpha(var(--color-success), var(--color-o, 1)); } +.color-success-light { color: alpha(var(--color-success-light), var(--color-o, 1)); } +.color-success-lighter { color: alpha(var(--color-success-lighter), var(--color-o, 1)); } + +.color-warning-darker { color: alpha(var(--color-warning-darker), var(--color-o, 1)); } +.color-warning-dark { color: alpha(var(--color-warning-dark), var(--color-o, 1)); } +.color-warning { color: alpha(var(--color-warning), var(--color-o, 1)); } +.color-warning-light { color: alpha(var(--color-warning-light), var(--color-o, 1)); } +.color-warning-lighter { color: alpha(var(--color-warning-lighter), var(--color-o, 1)); } + +.color-error-darker { color: alpha(var(--color-error-darker), var(--color-o, 1)); } +.color-error-dark { color: alpha(var(--color-error-dark), var(--color-o, 1)); } +.color-error { color: alpha(var(--color-error), var(--color-o, 1)); } +.color-error-light { color: alpha(var(--color-error-light), var(--color-o, 1)); } +.color-error-lighter { color: alpha(var(--color-error-lighter), var(--color-o, 1)); } + +.color-white { color: alpha(var(--color-white), var(--color-o, 1)); } +.color-black { color: alpha(var(--color-black), var(--color-o, 1)); } + +.color-opacity-0 { --color-o: 0; } +.color-opacity-10\% { --color-o: 0.1; } +.color-opacity-20\% { --color-o: 0.2; } +.color-opacity-30\% { --color-o: 0.3; } +.color-opacity-40\% { --color-o: 0.4; } +.color-opacity-50\% { --color-o: 0.5; } +.color-opacity-60\% { --color-o: 0.6; } +.color-opacity-70\% { --color-o: 0.7; } +.color-opacity-80\% { --color-o: 0.8; } +.color-opacity-90\% { --color-o: 0.9; } + +// -------------------------------- + +// Gradients + +// -------------------------------- + +[class^="color-gradient"], [class*=" color-gradient"] { + color: transparent !important; + background-clip: text; +} + +// -------------------------------- + +// Width + +// -------------------------------- + +.width-xxxxs { width: var(--size-xxxxs, 0.25rem); } +.width-xxxs { width: var(--size-xxxs, 0.5rem); } +.width-xxs { width: var(--size-xxs, 0.75rem); } +.width-xs { width: var(--size-xs, 1rem); } +.width-sm { width: var(--size-sm, 1.5rem); } +.width-md { width: var(--size-md, 2rem); } +.width-lg { width: var(--size-lg, 3rem); } +.width-xl { width: var(--size-xl, 4rem); } +.width-xxl { width: var(--size-xxl, 6rem); } +.width-xxxl { width: var(--size-xxxl, 8rem); } +.width-xxxxl { width: var(--size-xxxxl, 16rem); } +.width-0 { width: 0; } +.width-10\% { width: 10%; } +.width-20\% { width: 20%; } +.width-25\% { width: 25%; } +.width-30\% { width: 30%; } +.width-33\% { width: calc(100% / 3); } +.width-40\% { width: 40%; } +.width-50\% { width: 50%; } +.width-60\% { width: 60%; } +.width-66\% { width: calc(100% / 1.5); } +.width-70\% { width: 70%; } +.width-75\% { width: 75%; } +.width-80\% { width: 80%; } +.width-90\% { width: 90%; } +.width-100\% { width: 100%; } +.width-100vw { width: 100vw; } +.width-auto { width: auto; } +.width-fit-content { width: fit-content; } +.width-max-content { width: max-content; } + +// -------------------------------- + +// Height + +// -------------------------------- + +.height-xxxxs { height: var(--size-xxxxs, 0.25rem); } +.height-xxxs { height: var(--size-xxxs, 0.5rem); } +.height-xxs { height: var(--size-xxs, 0.75rem); } +.height-xs { height: var(--size-xs, 1rem); } +.height-sm { height: var(--size-sm, 1.5rem); } +.height-md { height: var(--size-md, 2rem); } +.height-lg { height: var(--size-lg, 3rem); } +.height-xl { height: var(--size-xl, 4rem); } +.height-xxl { height: var(--size-xxl, 6rem); } +.height-xxxl { height: var(--size-xxxl, 8rem); } +.height-xxxxl { height: var(--size-xxxxl, 16rem); } +.height-0 { height: 0; } +.height-10\% { height: 10%; } +.height-20\% { height: 20%; } +.height-25\% { height: 25%; } +.height-30\% { height: 30%; } +.height-33\% { height: calc(100% / 3); } +.height-40\% { height: 40%; } +.height-50\% { height: 50%; } +.height-60\% { height: 60%; } +.height-66\% { height: calc(100% / 1.5); } +.height-70\% { height: 70%; } +.height-75\% { height: 75%; } +.height-80\% { height: 80%; } +.height-90\% { height: 90%; } +.height-100\% { height: 100%; } +.height-100vh { height: 100vh; } +.height-auto { height: auto; } +.height-fit-content { height: fit-content; } +.height-max-content { height: max-content; } + +// -------------------------------- + +// Min-Width + +// -------------------------------- + +.min-width-0 { min-width: 0; } +.min-width-25\% { min-width: 25%; } +.min-width-33\% { min-width: calc(100% / 3); } +.min-width-50\% { min-width: 50%; } +.min-width-66\% { min-width: calc(100% / 1.5); } +.min-width-75\% { min-width: 75%; } +.min-width-100\% { min-width: 100%; } +.min-width-100vw { min-width: 100vw; } +.min-width-fit-content { min-width: fit-content; } +.min-width-max-content { min-width: max-content; } + +// -------------------------------- + +// Min-Height + +// -------------------------------- + +.min-height-100\% { min-height: 100%; } +.min-height-100vh { min-height: 100vh; } +.min-height-fit-content { min-height: fit-content; } +.min-height-max-content { min-height: max-content; } + +// -------------------------------- + +// Max-Width + +// -------------------------------- + +:root { + --max-width-xxxxs: 20rem; // ~320px + --max-width-xxxs: 26rem; // ~416px + --max-width-xxs: 32rem; // ~512px + --max-width-xs: 38rem; // ~608px + --max-width-sm: 48rem; // ~768px + --max-width-md: 64rem; // ~1024px + --max-width-lg: 80rem; // ~1280px + --max-width-xl: 90rem; // ~1440px + --max-width-xxl: 100rem; // ~1600px + --max-width-xxxl: 120rem; // ~1920px + --max-width-xxxxl: 150rem; // ~2400px +} + +.max-width-xxxxs { max-width: var(--max-width-xxxxs); } +.max-width-xxxs { max-width: var(--max-width-xxxs); } +.max-width-xxs { max-width: var(--max-width-xxs); } +.max-width-xs { max-width: var(--max-width-xs); } +.max-width-sm { max-width: var(--max-width-sm); } +.max-width-md { max-width: var(--max-width-md); } +.max-width-lg { max-width: var(--max-width-lg); } +.max-width-xl { max-width: var(--max-width-xl); } +.max-width-xxl { max-width: var(--max-width-xxl); } +.max-width-xxxl { max-width: var(--max-width-xxxl); } +.max-width-xxxxl { max-width: var(--max-width-xxxxl); } +.max-width-100\% { max-width: 100%; } +.max-width-none { max-width: none; } +.max-width-fit-content { max-width: fit-content; } +.max-width-max-content { max-width: max-content; } + +// alt approach - max-width is equal to current breakpoint +$breakpointsNr: length($breakpoints); +@each $breakpoint, $value in $breakpoints { + $i: index($breakpoints, $breakpoint $value); + @if $i == 1 { + [class^="max-width-adaptive"], [class*=" max-width-adaptive"] { + max-width: map-get($map: $breakpoints, $key: #{$breakpoint}); + } + } @else { + $classList : ''; + @each $subBreakpoint, $subValue in $breakpoints { + $j: index($breakpoints, $subBreakpoint $subValue); + @if $j == $i { + $classList: '.max-width-adaptive-#{$subBreakpoint}'; + } @else if $j > $i { + $classList: $classList+', .max-width-adaptive-#{$subBreakpoint}'; + } + } + @if $i < $breakpointsNr { + $classList: $classList+', .max-width-adaptive'; + } + @include breakpoint(#{$breakpoint}) { + #{$classList} { + max-width: map-get($map: $breakpoints, $key: #{$breakpoint}); + } + } + } +} + +// -------------------------------- + +// Max-Height + +// -------------------------------- + +.max-height-100\% { max-height: 100%; } +.max-height-100vh { max-height: 100vh; } + +// -------------------------------- + +// Box-Shadow + +// -------------------------------- + +.shadow-xs { box-shadow: var(--shadow-xs); } +.shadow-sm { box-shadow: var(--shadow-sm); } +.shadow-md { box-shadow: var(--shadow-md); } +.shadow-lg { box-shadow: var(--shadow-lg); } +.shadow-xl { box-shadow: var(--shadow-xl); } +.shadow-none { box-shadow: none; } + +:root { + --inner-glow: inset 0 0 0.5px 1px hsla(0, 0%, 100%, 0.075); + --inner-glow-top: inset 0 1px 0.5px hsla(0, 0%, 100%, 0.075); +} + +.inner-glow, .inner-glow-top { + position: relative; + + &::after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border-radius: inherit; + pointer-events: none; + } +} + +.inner-glow::after { box-shadow: var(--inner-glow); } +.inner-glow-top::after { box-shadow: var(--inner-glow-top); } + +// -------------------------------- + +// Position + +// -------------------------------- + +.position-relative { position: relative; } +.position-absolute { position: absolute; } +.position-fixed { position: fixed; } +.position-sticky { position: sticky; } + +.inset-0 { top: 0; right: 0; bottom: 0; left: 0; } + +.top-0 { top: 0; } +.top-50\% { top: 50%; } +.top-xxxxs { top: var(--space-xxxxs); } +.top-xxxs { top: var(--space-xxxs); } +.top-xxs { top: var(--space-xxs); } +.top-xs { top: var(--space-xs); } +.top-sm { top: var(--space-sm); } +.top-md { top: var(--space-md); } +.top-lg { top: var(--space-lg); } +.top-xl { top: var(--space-xl); } +.top-xxl { top: var(--space-xxl); } +.top-xxxl { top: var(--space-xxxl); } +.top-xxxxl { top: var(--space-xxxxl); } + +.bottom-0 { bottom: 0; } +.bottom-unset { bottom: unset; } +.bottom-50\% { bottom: 50%; } +.bottom-xxxxs { bottom: var(--space-xxxxs); } +.bottom-xxxs { bottom: var(--space-xxxs); } +.bottom-xxs { bottom: var(--space-xxs); } +.bottom-xs { bottom: var(--space-xs); } +.bottom-sm { bottom: var(--space-sm); } +.bottom-md { bottom: var(--space-md); } +.bottom-lg { bottom: var(--space-lg); } +.bottom-xl { bottom: var(--space-xl); } +.bottom-xxl { bottom: var(--space-xxl); } +.bottom-xxxl { bottom: var(--space-xxxl); } +.bottom-xxxxl { bottom: var(--space-xxxxl); } + +.right-0 { right: 0; } +.right-50\% { right: 50%; } +.right-xxxxs { right: var(--space-xxxxs); } +.right-xxxs { right: var(--space-xxxs); } +.right-xxs { right: var(--space-xxs); } +.right-xs { right: var(--space-xs); } +.right-sm { right: var(--space-sm); } +.right-md { right: var(--space-md); } +.right-lg { right: var(--space-lg); } +.right-xl { right: var(--space-xl); } +.right-xxl { right: var(--space-xxl); } +.right-xxxl { right: var(--space-xxxl); } +.right-xxxxl { right: var(--space-xxxxl); } + +.left-0 { left: 0; } +.left-50\% { left: 50%; } +.left-xxxxs { left: var(--space-xxxxs); } +.left-xxxs { left: var(--space-xxxs); } +.left-xxs { left: var(--space-xxs); } +.left-xs { left: var(--space-xs); } +.left-sm { left: var(--space-sm); } +.left-md { left: var(--space-md); } +.left-lg { left: var(--space-lg); } +.left-xl { left: var(--space-xl); } +.left-xxl { left: var(--space-xxl); } +.left-xxxl { left: var(--space-xxxl); } +.left-xxxxl { left: var(--space-xxxxl); } + +// -------------------------------- + +// Z-Index + +// -------------------------------- + +.z-index-header { z-index: var(--z-index-header); } +.z-index-popover { z-index: var(--z-index-popover); } +.z-index-fixed-element { z-index: var(--z-index-fixed-element); } +.z-index-overlay { z-index: var(--z-index-overlay); } + +.z-index-1 { z-index: 1; } +.z-index-2 { z-index: 2; } +.z-index-3 { z-index: 3; } + +// -------------------------------- + +// Overflow + +// -------------------------------- + +.overflow-hidden { overflow: hidden; } +.overflow-auto { overflow: auto; } +.momentum-scrolling { -webkit-overflow-scrolling: touch; } + +// overscroll-behavior +.overscroll-contain { overscroll-behavior: contain; } + +// -------------------------------- + +// Scroll Behavior + +// -------------------------------- + +.scroll-smooth { scroll-behavior: smooth; } + +.scroll-padding-xxxxs { scroll-padding: var(--space-xxxxs); } +.scroll-padding-xxxs { scroll-padding: var(--space-xxxs); } +.scroll-padding-xxs { scroll-padding: var(--space-xxs); } +.scroll-padding-xs { scroll-padding: var(--space-xs); } +.scroll-padding-sm { scroll-padding: var(--space-sm); } +.scroll-padding-md { scroll-padding: var(--space-md); } +.scroll-padding-lg { scroll-padding: var(--space-lg); } +.scroll-padding-xl { scroll-padding: var(--space-xl); } +.scroll-padding-xxl { scroll-padding: var(--space-xxl); } +.scroll-padding-xxxl { scroll-padding: var(--space-xxxl); } +.scroll-padding-xxxxl { scroll-padding: var(--space-xxxxl); } + + +// -------------------------------- + +// Opacity + +// -------------------------------- + +.opacity-0 { opacity: 0; } +.opacity-10\% { opacity: 0.1; } +.opacity-20\% { opacity: 0.2; } +.opacity-30\% { opacity: 0.3; } +.opacity-40\% { opacity: 0.4; } +.opacity-50\% { opacity: 0.5; } +.opacity-60\% { opacity: 0.6; } +.opacity-70\% { opacity: 0.7; } +.opacity-80\% { opacity: 0.8; } +.opacity-90\% { opacity: 0.9; } + +// -------------------------------- + +// Float + +// -------------------------------- + +.float-left { float: left; } +.float-right { float: right; } + +.clearfix::after { + content: ""; + display: table; + clear: both; +} + +// -------------------------------- + +// Border + +// -------------------------------- + +[class^="border-"], [class*=" border-"] { + --border-o: 1; +} + +.border { border: var(--border-width, 1px) var(--border-style, solid) alpha(var(--color-contrast-lower), var(--border-o, 1)); } +.border-top { border-top: var(--border-width, 1px) var(--border-style, solid) alpha(var(--color-contrast-lower), var(--border-o, 1)); } +.border-bottom { border-bottom: var(--border-width, 1px) var(--border-style, solid) alpha(var(--color-contrast-lower), var(--border-o, 1)); } +.border-left { border-left: var(--border-width, 1px) var(--border-style, solid) alpha(var(--color-contrast-lower), var(--border-o, 1)); } +.border-right { border-right: var(--border-width, 1px) var(--border-style, solid) alpha(var(--color-contrast-lower), var(--border-o, 1)); } + +.border-2 { --border-width: 2px; } +.border-3 { --border-width: 3px; } +.border-4 { --border-width: 4px; } +.border-dotted { --border-style: dotted; } +.border-dashed { --border-style: dashed; } + +.border-bg-darker { border-color: alpha(var(--color-bg-darker), var(--border-o, 1)); } +.border-bg-dark { border-color: alpha(var(--color-bg-dark), var(--border-o, 1)); } +.border-bg { border-color: alpha(var(--color-bg), var(--border-o, 1)); } +.border-bg-light { border-color: alpha(var(--color-bg-light), var(--border-o, 1)); } +.border-bg-lighter { border-color: alpha(var(--color-bg-lighter), var(--border-o, 1)); } + +.border-contrast-lower { border-color: alpha(var(--color-contrast-lower), var(--border-o, 1)); } +.border-contrast-low { border-color: alpha(var(--color-contrast-low), var(--border-o, 1)); } +.border-contrast-medium { border-color: alpha(var(--color-contrast-medium), var(--border-o, 1)); } +.border-contrast-high { border-color: alpha(var(--color-contrast-high), var(--border-o, 1)); } +.border-contrast-higher { border-color: alpha(var(--color-contrast-higher), var(--border-o, 1)); } + +.border-primary-darker { border-color: alpha(var(--color-primary-darker), var(--border-o, 1)); } +.border-primary-dark { border-color: alpha(var(--color-primary-dark), var(--border-o, 1)); } +.border-primary { border-color: alpha(var(--color-primary), var(--border-o, 1)); } +.border-primary-light { border-color: alpha(var(--color-primary-light), var(--border-o, 1)); } +.border-primary-lighter { border-color: alpha(var(--color-primary-lighter), var(--border-o, 1)); } + +.border-accent-darker { border-color: alpha(var(--color-accent-darker), var(--border-o, 1)); } +.border-accent-dark { border-color: alpha(var(--color-accent-dark), var(--border-o, 1)); } +.border-accent { border-color: alpha(var(--color-accent), var(--border-o, 1)); } +.border-accent-light { border-color: alpha(var(--color-accent-light), var(--border-o, 1)); } +.border-accent-lighter { border-color: alpha(var(--color-accent-lighter), var(--border-o, 1)); } + +.border-success-darker { border-color: alpha(var(--color-success-darker), var(--border-o, 1)); } +.border-success-dark { border-color: alpha(var(--color-success-dark), var(--border-o, 1)); } +.border-success { border-color: alpha(var(--color-success), var(--border-o, 1)); } +.border-success-light { border-color: alpha(var(--color-success-light), var(--border-o, 1)); } +.border-success-lighter { border-color: alpha(var(--color-success-lighter), var(--border-o, 1)); } + +.border-warning-darker { border-color: alpha(var(--color-warning-darker), var(--border-o, 1)); } +.border-warning-dark { border-color: alpha(var(--color-warning-dark), var(--border-o, 1)); } +.border-warning { border-color: alpha(var(--color-warning), var(--border-o, 1)); } +.border-warning-light { border-color: alpha(var(--color-warning-light), var(--border-o, 1)); } +.border-warning-lighter { border-color: alpha(var(--color-warning-lighter), var(--border-o, 1)); } + +.border-error-darker { border-color: alpha(var(--color-error-darker), var(--border-o, 1)); } +.border-error-dark { border-color: alpha(var(--color-error-dark), var(--border-o, 1)); } +.border-error { border-color: alpha(var(--color-error), var(--border-o, 1)); } +.border-error-light { border-color: alpha(var(--color-error-light), var(--border-o, 1)); } +.border-error-lighter { border-color: alpha(var(--color-error-lighter), var(--border-o, 1)); } + +.border-white { border-color: alpha(var(--color-white), var(--border-o, 1)); } +.border-black { border-color: alpha(var(--color-black), var(--border-o, 1)); } + +.border-opacity-0 { --border-o: 0; } +.border-opacity-10\% { --border-o: 0.1; } +.border-opacity-20\% { --border-o: 0.2; } +.border-opacity-30\% { --border-o: 0.3; } +.border-opacity-40\% { --border-o: 0.4; } +.border-opacity-50\% { --border-o: 0.5; } +.border-opacity-60\% { --border-o: 0.6; } +.border-opacity-70\% { --border-o: 0.7; } +.border-opacity-80\% { --border-o: 0.8; } +.border-opacity-90\% { --border-o: 0.9; } + +// -------------------------------- + +// Border Radius + +// -------------------------------- + +.radius-sm { border-radius: var(--radius-sm); } +.radius-md { border-radius: var(--radius-md); } +.radius-lg { border-radius: var(--radius-lg); } +.radius-50\% { border-radius: 50%; } +.radius-full { border-radius: 50em; } +.radius-0 { border-radius: 0; } +.radius-inherit { border-radius: inherit; } +.radius-top-left-0 { border-top-left-radius: 0; } +.radius-top-right-0 { border-top-right-radius: 0; } +.radius-bottom-right-0 { border-bottom-right-radius: 0; } +.radius-bottom-left-0 { border-bottom-left-radius: 0; } + +// -------------------------------- + +// Background + +// -------------------------------- + +.bg, [class^="bg-"], [class*=" bg-"] { --bg-o: 1; } + +.bg-transparent { background-color: transparent; } +.bg-inherit { background-color: inherit; } + +.bg-darker { background-color: alpha(var(--color-bg-darker), var(--bg-o)); } +.bg-dark { background-color: alpha(var(--color-bg-dark), var(--bg-o)); } +.bg { background-color: alpha(var(--color-bg), var(--bg-o)); } +.bg-light { background-color: alpha(var(--color-bg-light), var(--bg-o)); } +.bg-lighter { background-color: alpha(var(--color-bg-lighter), var(--bg-o)); } + +.bg-contrast-lower { background-color: alpha(var(--color-contrast-lower), var(--bg-o, 1)); } +.bg-contrast-low { background-color: alpha(var(--color-contrast-low), var(--bg-o, 1)); } +.bg-contrast-medium { background-color: alpha(var(--color-contrast-medium), var(--bg-o, 1)); } +.bg-contrast-high { background-color: alpha(var(--color-contrast-high), var(--bg-o, 1)); } +.bg-contrast-higher { background-color: alpha(var(--color-contrast-higher), var(--bg-o, 1)); } + +.bg-primary-darker { background-color: alpha(var(--color-primary-darker), var(--bg-o, 1)); } +.bg-primary-dark { background-color: alpha(var(--color-primary-dark), var(--bg-o, 1)); } +.bg-primary { background-color: alpha(var(--color-primary), var(--bg-o, 1)); } +.bg-primary-light { background-color: alpha(var(--color-primary-light), var(--bg-o, 1)); } +.bg-primary-lighter { background-color: alpha(var(--color-primary-lighter), var(--bg-o, 1)); } + +.bg-accent-darker { background-color: alpha(var(--color-accent-darker), var(--bg-o, 1)); } +.bg-accent-dark { background-color: alpha(var(--color-accent-dark), var(--bg-o, 1)); } +.bg-accent { background-color: alpha(var(--color-accent), var(--bg-o, 1)); } +.bg-accent-light { background-color: alpha(var(--color-accent-light), var(--bg-o, 1)); } +.bg-accent-lighter { background-color: alpha(var(--color-accent-lighter), var(--bg-o, 1)); } + +.bg-success-darker { background-color: alpha(var(--color-success-darker), var(--bg-o, 1)); } +.bg-success-dark { background-color: alpha(var(--color-success-dark), var(--bg-o, 1)); } +.bg-success { background-color: alpha(var(--color-success), var(--bg-o, 1)); } +.bg-success-light { background-color: alpha(var(--color-success-light), var(--bg-o, 1)); } +.bg-success-lighter { background-color: alpha(var(--color-success-lighter), var(--bg-o, 1)); } + +.bg-warning-darker { background-color: alpha(var(--color-warning-darker), var(--bg-o, 1)); } +.bg-warning-dark { background-color: alpha(var(--color-warning-dark), var(--bg-o, 1)); } +.bg-warning { background-color: alpha(var(--color-warning), var(--bg-o, 1)); } +.bg-warning-light { background-color: alpha(var(--color-warning-light), var(--bg-o, 1)); } +.bg-warning-lighter { background-color: alpha(var(--color-warning-lighter), var(--bg-o, 1)); } + +.bg-error-darker { background-color: alpha(var(--color-error-darker), var(--bg-o, 1)); } +.bg-error-dark { background-color: alpha(var(--color-error-dark), var(--bg-o, 1)); } +.bg-error { background-color: alpha(var(--color-error), var(--bg-o, 1)); } +.bg-error-light { background-color: alpha(var(--color-error-light), var(--bg-o, 1)); } +.bg-error-lighter { background-color: alpha(var(--color-error-lighter), var(--bg-o, 1)); } + +.bg-white { background-color: alpha(var(--color-white), var(--bg-o, 1)); } +.bg-black { background-color: alpha(var(--color-black), var(--bg-o, 1)); } + +.bg-opacity-0 { --bg-o: 0; } +.bg-opacity-10\% { --bg-o: 0.1; } +.bg-opacity-20\% { --bg-o: 0.2; } +.bg-opacity-30\% { --bg-o: 0.3; } +.bg-opacity-40\% { --bg-o: 0.4; } +.bg-opacity-50\% { --bg-o: 0.5; } +.bg-opacity-60\% { --bg-o: 0.6; } +.bg-opacity-70\% { --bg-o: 0.7; } +.bg-opacity-80\% { --bg-o: 0.8; } +.bg-opacity-90\% { --bg-o: 0.9; } + +.bg-center { background-position: center; } +.bg-top { background-position: center top; } +.bg-right { background-position: right center; } +.bg-bottom { background-position: center bottom; } +.bg-left { background-position: left center; } +.bg-top-left { background-position: left top; } +.bg-top-right { background-position: right top; } +.bg-bottom-left { background-position: left bottom; } +.bg-bottom-right { background-position: right bottom; } + +.bg-cover { background-size: cover; } +.bg-no-repeat { background-repeat: no-repeat; } + +// -------------------------------- + +// Backdrop Filter + +// -------------------------------- + +.backdrop-blur-10 { backdrop-filter: blur(10px); } +.backdrop-blur-20 { backdrop-filter: blur(20px); } + +// -------------------------------- + +// Mix-Blend Mode + +// -------------------------------- + +.isolate { isolation: isolate; } +.blend-multiply { mix-blend-mode: multiply; } +.blend-overlay { mix-blend-mode: overlay; } +.blend-difference { mix-blend-mode: difference; } + +// -------------------------------- + +// Object-Fit + +// -------------------------------- + +.object-contain { object-fit: contain; } +.object-cover { object-fit: cover; } + +// -------------------------------- + +// Perspective + +// -------------------------------- + +.perspective-xs { perspective: 250px; } +.perspective-sm { perspective: 500px; } +.perspective-md { perspective: 1000px; } +.perspective-lg { perspective: 1500px; } +.perspective-xl { perspective: 3000px; } + +// -------------------------------- + +// Transform + +// -------------------------------- + +[class^="flip"], [class*=" flip"], +[class^="-rotate"], [class*=" -rotate"], +[class^="rotate"], [class*=" rotate"], +[class^="-translate"], [class*=" -translate"], +[class^="translate"], [class*=" translate"], +[class^="-scale"], [class*=" -scale"], +[class^="scale"], [class*=" scale"], +[class^="-skew"], [class*=" -skew"], +[class^="skew"], [class*=" skew"] { + --translate: 0; + --rotate: 0; + --skew: 0; + --scale: 1; + + transform: translate3d(var(--translate-x, var(--translate)), var(--translate-y, var(--translate)), var(--translate-z, 0)) rotateX(var(--rotate-x, 0)) rotateY(var(--rotate-y, 0)) rotateZ(var(--rotate-z, var(--rotate))) skewX(var(--skew-x, var(--skew))) skewY(var(--skew-y, 0)) scaleX(var(--scale-x, var(--scale))) scaleY(var(--scale-y, var(--scale))); +} + +.flip { --scale: -1; } +.flip-x { --scale-x: -1; } +.flip-y { --scale-y: -1; } + +.rotate-90 { --rotate: 90deg; } +.rotate-180 { --rotate: 180deg; } +.rotate-270 { --rotate: 270deg; } + +.-translate-50\% { --translate: -50%; } +.-translate-x-50\% { --translate-x: -50%; } +.-translate-y-50\% { --translate-y: -50%; } + +.translate-50\% { --translate: 50%; } +.translate-x-50\% { --translate-x: 50%; } +.translate-y-50\% { --translate-y: 50%; } + +// -------------------------------- + +// Transform Origin + +// -------------------------------- + +.origin-center { transform-origin: center; } +.origin-top { transform-origin: center top; } +.origin-right { transform-origin: right center; } +.origin-bottom { transform-origin: center bottom; } +.origin-left { transform-origin: left center; } +.origin-top-left { transform-origin: left top; } +.origin-top-right { transform-origin: right top; } +.origin-bottom-left { transform-origin: left bottom; } +.origin-bottom-right { transform-origin: right bottom; } + +// -------------------------------- + +// SVG + +// -------------------------------- + +.fill-current { fill: currentColor; } + +.stroke-current { stroke: currentColor; } + +.stroke-1 { stroke-width: 1px; } +.stroke-2 { stroke-width: 2px; } +.stroke-3 { stroke-width: 3px; } +.stroke-4 { stroke-width: 4px; } + +// -------------------------------- + +// Visibility + +// -------------------------------- + +.visible { visibility: visible; } +.invisible { visibility: hidden; } + +// -------------------------------- + +// Responsive Variations + +// -------------------------------- + +@each $breakpoint, $value in $breakpoints { + @include breakpoint(#{$breakpoint}) { + // flexbox + .flex\@#{$breakpoint} { display: flex; } + .inline-flex\@#{$breakpoint} { display: inline-flex; } + .flex-wrap\@#{$breakpoint} { flex-wrap: wrap; } + .flex-nowrap\@#{$breakpoint} { flex-wrap:nowrap; } + .flex-column\@#{$breakpoint} { flex-direction: column; } + .flex-column-reverse\@#{$breakpoint} { flex-direction: column-reverse; } + .flex-row\@#{$breakpoint} { flex-direction: row; } + .flex-row-reverse\@#{$breakpoint} { flex-direction: row-reverse; } + .flex-center\@#{$breakpoint} { justify-content: center; align-items: center; } + + .flex-grow\@#{$breakpoint} { flex-grow: 1; } + .flex-grow-0\@#{$breakpoint} { flex-grow: 0; } + .flex-shrink\@#{$breakpoint} { flex-shrink: 1; } + .flex-shrink-0\@#{$breakpoint} { flex-shrink: 0; } + .flex-basis-0\@#{$breakpoint} { flex-basis: 0; } + + // justify-content + .justify-start\@#{$breakpoint} { justify-content: flex-start; } + .justify-end\@#{$breakpoint} { justify-content: flex-end; } + .justify-center\@#{$breakpoint} { justify-content: center; } + .justify-between\@#{$breakpoint} { justify-content: space-between; } + + // align-items + .items-center\@#{$breakpoint} { align-items: center; } + .items-start\@#{$breakpoint} { align-items: flex-start; } + .items-end\@#{$breakpoint} { align-items: flex-end; } + .items-baseline\@#{$breakpoint} { align-items: baseline; } + + // order + .order-1\@#{$breakpoint} { order: 1; } + .order-2\@#{$breakpoint} { order: 2; } + .order-3\@#{$breakpoint} { order: 3; } + + // display + .block\@#{$breakpoint} { display: block; } + .inline-block\@#{$breakpoint} { display: inline-block; } + .inline\@#{$breakpoint} { display: inline; } + .contents\@#{$breakpoint} { display: contents; } + .hide\@#{$breakpoint} { display: none !important; } + + // margin + .margin-xxxxs\@#{$breakpoint} { margin: var(--space-xxxxs); } + .margin-xxxs\@#{$breakpoint} { margin: var(--space-xxxs); } + .margin-xxs\@#{$breakpoint} { margin: var(--space-xxs); } + .margin-xs\@#{$breakpoint} { margin: var(--space-xs); } + .margin-sm\@#{$breakpoint} { margin: var(--space-sm); } + .margin-md\@#{$breakpoint} { margin: var(--space-md); } + .margin-lg\@#{$breakpoint} { margin: var(--space-lg); } + .margin-xl\@#{$breakpoint} { margin: var(--space-xl); } + .margin-xxl\@#{$breakpoint} { margin: var(--space-xxl); } + .margin-xxxl\@#{$breakpoint} { margin: var(--space-xxxl); } + .margin-xxxxl\@#{$breakpoint} { margin: var(--space-xxxxl); } + .margin-auto\@#{$breakpoint} { margin: auto; } + .margin-0\@#{$breakpoint} { margin: 0; } + + .margin-top-xxxxs\@#{$breakpoint} { margin-top: var(--space-xxxxs); } + .margin-top-xxxs\@#{$breakpoint} { margin-top: var(--space-xxxs); } + .margin-top-xxs\@#{$breakpoint} { margin-top: var(--space-xxs); } + .margin-top-xs\@#{$breakpoint} { margin-top: var(--space-xs); } + .margin-top-sm\@#{$breakpoint} { margin-top: var(--space-sm); } + .margin-top-md\@#{$breakpoint} { margin-top: var(--space-md); } + .margin-top-lg\@#{$breakpoint} { margin-top: var(--space-lg); } + .margin-top-xl\@#{$breakpoint} { margin-top: var(--space-xl); } + .margin-top-xxl\@#{$breakpoint} { margin-top: var(--space-xxl); } + .margin-top-xxxl\@#{$breakpoint} { margin-top: var(--space-xxxl); } + .margin-top-xxxxl\@#{$breakpoint} { margin-top: var(--space-xxxxl); } + .margin-top-auto\@#{$breakpoint} { margin-top: auto; } + .margin-top-0\@#{$breakpoint} { margin-top: 0; } + + .margin-bottom-xxxxs\@#{$breakpoint} { margin-bottom: var(--space-xxxxs); } + .margin-bottom-xxxs\@#{$breakpoint} { margin-bottom: var(--space-xxxs); } + .margin-bottom-xxs\@#{$breakpoint} { margin-bottom: var(--space-xxs); } + .margin-bottom-xs\@#{$breakpoint} { margin-bottom: var(--space-xs); } + .margin-bottom-sm\@#{$breakpoint} { margin-bottom: var(--space-sm); } + .margin-bottom-md\@#{$breakpoint} { margin-bottom: var(--space-md); } + .margin-bottom-lg\@#{$breakpoint} { margin-bottom: var(--space-lg); } + .margin-bottom-xl\@#{$breakpoint} { margin-bottom: var(--space-xl); } + .margin-bottom-xxl\@#{$breakpoint} { margin-bottom: var(--space-xxl); } + .margin-bottom-xxxl\@#{$breakpoint} { margin-bottom: var(--space-xxxl); } + .margin-bottom-xxxxl\@#{$breakpoint} { margin-bottom: var(--space-xxxxl); } + .margin-bottom-auto\@#{$breakpoint} { margin-bottom: auto; } + .margin-bottom-0\@#{$breakpoint} { margin-bottom: 0; } + + .margin-right-xxxxs\@#{$breakpoint} { margin-right: var(--space-xxxxs); } + .margin-right-xxxs\@#{$breakpoint} { margin-right: var(--space-xxxs); } + .margin-right-xxs\@#{$breakpoint} { margin-right: var(--space-xxs); } + .margin-right-xs\@#{$breakpoint} { margin-right: var(--space-xs); } + .margin-right-sm\@#{$breakpoint} { margin-right: var(--space-sm); } + .margin-right-md\@#{$breakpoint} { margin-right: var(--space-md); } + .margin-right-lg\@#{$breakpoint} { margin-right: var(--space-lg); } + .margin-right-xl\@#{$breakpoint} { margin-right: var(--space-xl); } + .margin-right-xxl\@#{$breakpoint} { margin-right: var(--space-xxl); } + .margin-right-xxxl\@#{$breakpoint} { margin-right: var(--space-xxxl); } + .margin-right-xxxxl\@#{$breakpoint} { margin-right: var(--space-xxxxl); } + .margin-right-auto\@#{$breakpoint} { margin-right: auto; } + .margin-right-0\@#{$breakpoint} { margin-right: 0; } + + .margin-left-xxxxs\@#{$breakpoint} { margin-left: var(--space-xxxxs); } + .margin-left-xxxs\@#{$breakpoint} { margin-left: var(--space-xxxs); } + .margin-left-xxs\@#{$breakpoint} { margin-left: var(--space-xxs); } + .margin-left-xs\@#{$breakpoint} { margin-left: var(--space-xs); } + .margin-left-sm\@#{$breakpoint} { margin-left: var(--space-sm); } + .margin-left-md\@#{$breakpoint} { margin-left: var(--space-md); } + .margin-left-lg\@#{$breakpoint} { margin-left: var(--space-lg); } + .margin-left-xl\@#{$breakpoint} { margin-left: var(--space-xl); } + .margin-left-xxl\@#{$breakpoint} { margin-left: var(--space-xxl); } + .margin-left-xxxl\@#{$breakpoint} { margin-left: var(--space-xxxl); } + .margin-left-xxxxl\@#{$breakpoint} { margin-left: var(--space-xxxxl); } + .margin-left-auto\@#{$breakpoint} { margin-left: auto; } + .margin-left-0\@#{$breakpoint} { margin-left: 0; } + + .margin-x-xxxxs\@#{$breakpoint} { margin-left: var(--space-xxxxs); margin-right: var(--space-xxxxs); } + .margin-x-xxxs\@#{$breakpoint} { margin-left: var(--space-xxxs); margin-right: var(--space-xxxs); } + .margin-x-xxs\@#{$breakpoint} { margin-left: var(--space-xxs); margin-right: var(--space-xxs); } + .margin-x-xs\@#{$breakpoint} { margin-left: var(--space-xs); margin-right: var(--space-xs); } + .margin-x-sm\@#{$breakpoint} { margin-left: var(--space-sm); margin-right: var(--space-sm); } + .margin-x-md\@#{$breakpoint} { margin-left: var(--space-md); margin-right: var(--space-md); } + .margin-x-lg\@#{$breakpoint} { margin-left: var(--space-lg); margin-right: var(--space-lg); } + .margin-x-xl\@#{$breakpoint} { margin-left: var(--space-xl); margin-right: var(--space-xl); } + .margin-x-xxl\@#{$breakpoint} { margin-left: var(--space-xxl); margin-right: var(--space-xxl); } + .margin-x-xxxl\@#{$breakpoint} { margin-left: var(--space-xxxl); margin-right: var(--space-xxxl); } + .margin-x-xxxxl\@#{$breakpoint} { margin-left: var(--space-xxxxl); margin-right: var(--space-xxxxl); } + .margin-x-auto\@#{$breakpoint} { margin-left: auto; margin-right: auto; } + .margin-x-0\@#{$breakpoint} { margin-left: 0; margin-right: 0; } + + .margin-y-xxxxs\@#{$breakpoint} { margin-top: var(--space-xxxxs); margin-bottom: var(--space-xxxxs); } + .margin-y-xxxs\@#{$breakpoint} { margin-top: var(--space-xxxs); margin-bottom: var(--space-xxxs); } + .margin-y-xxs\@#{$breakpoint} { margin-top: var(--space-xxs); margin-bottom: var(--space-xxs); } + .margin-y-xs\@#{$breakpoint} { margin-top: var(--space-xs); margin-bottom: var(--space-xs); } + .margin-y-sm\@#{$breakpoint} { margin-top: var(--space-sm); margin-bottom: var(--space-sm); } + .margin-y-md\@#{$breakpoint} { margin-top: var(--space-md); margin-bottom: var(--space-md); } + .margin-y-lg\@#{$breakpoint} { margin-top: var(--space-lg); margin-bottom: var(--space-lg); } + .margin-y-xl\@#{$breakpoint} { margin-top: var(--space-xl); margin-bottom: var(--space-xl); } + .margin-y-xxl\@#{$breakpoint} { margin-top: var(--space-xxl); margin-bottom: var(--space-xxl); } + .margin-y-xxxl\@#{$breakpoint} { margin-top: var(--space-xxxl); margin-bottom: var(--space-xxxl); } + .margin-y-xxxxl\@#{$breakpoint} { margin-top: var(--space-xxxxl); margin-bottom: var(--space-xxxxl); } + .margin-y-auto\@#{$breakpoint} { margin-top: auto; margin-bottom: auto; } + .margin-y-0\@#{$breakpoint} { margin-top: 0; margin-bottom: 0; } + + // padding + .padding-xxxxs\@#{$breakpoint} { padding: var(--space-xxxxs); } + .padding-xxxs\@#{$breakpoint} { padding: var(--space-xxxs); } + .padding-xxs\@#{$breakpoint} { padding: var(--space-xxs); } + .padding-xs\@#{$breakpoint} { padding: var(--space-xs); } + .padding-sm\@#{$breakpoint} { padding: var(--space-sm); } + .padding-md\@#{$breakpoint} { padding: var(--space-md); } + .padding-lg\@#{$breakpoint} { padding: var(--space-lg); } + .padding-xl\@#{$breakpoint} { padding: var(--space-xl); } + .padding-xxl\@#{$breakpoint} { padding: var(--space-xxl); } + .padding-xxxl\@#{$breakpoint} { padding: var(--space-xxxl); } + .padding-xxxxl\@#{$breakpoint} { padding: var(--space-xxxxl); } + .padding-0\@#{$breakpoint} { padding: 0; } + .padding-component\@#{$breakpoint} { padding: var(--component-padding); } + + .padding-top-xxxxs\@#{$breakpoint} { padding-top: var(--space-xxxxs); } + .padding-top-xxxs\@#{$breakpoint} { padding-top: var(--space-xxxs); } + .padding-top-xxs\@#{$breakpoint} { padding-top: var(--space-xxs); } + .padding-top-xs\@#{$breakpoint} { padding-top: var(--space-xs); } + .padding-top-sm\@#{$breakpoint} { padding-top: var(--space-sm); } + .padding-top-md\@#{$breakpoint} { padding-top: var(--space-md); } + .padding-top-lg\@#{$breakpoint} { padding-top: var(--space-lg); } + .padding-top-xl\@#{$breakpoint} { padding-top: var(--space-xl); } + .padding-top-xxl\@#{$breakpoint} { padding-top: var(--space-xxl); } + .padding-top-xxxl\@#{$breakpoint} { padding-top: var(--space-xxxl); } + .padding-top-xxxxl\@#{$breakpoint} { padding-top: var(--space-xxxxl); } + .padding-top-0\@#{$breakpoint} { padding-top: 0; } + .padding-top-component\@#{$breakpoint} { padding-top: var(--component-padding); } + + .padding-bottom-xxxxs\@#{$breakpoint} { padding-bottom: var(--space-xxxxs); } + .padding-bottom-xxxs\@#{$breakpoint} { padding-bottom: var(--space-xxxs); } + .padding-bottom-xxs\@#{$breakpoint} { padding-bottom: var(--space-xxs); } + .padding-bottom-xs\@#{$breakpoint} { padding-bottom: var(--space-xs); } + .padding-bottom-sm\@#{$breakpoint} { padding-bottom: var(--space-sm); } + .padding-bottom-md\@#{$breakpoint} { padding-bottom: var(--space-md); } + .padding-bottom-lg\@#{$breakpoint} { padding-bottom: var(--space-lg); } + .padding-bottom-xl\@#{$breakpoint} { padding-bottom: var(--space-xl); } + .padding-bottom-xxl\@#{$breakpoint} { padding-bottom: var(--space-xxl); } + .padding-bottom-xxxl\@#{$breakpoint} { padding-bottom: var(--space-xxxl); } + .padding-bottom-xxxxl\@#{$breakpoint} { padding-bottom: var(--space-xxxxl); } + .padding-bottom-0\@#{$breakpoint} { padding-bottom: 0; } + .padding-bottom-component\@#{$breakpoint} { padding-bottom: var(--component-padding); } + + .padding-right-xxxxs\@#{$breakpoint} { padding-right: var(--space-xxxxs); } + .padding-right-xxxs\@#{$breakpoint} { padding-right: var(--space-xxxs); } + .padding-right-xxs\@#{$breakpoint} { padding-right: var(--space-xxs); } + .padding-right-xs\@#{$breakpoint} { padding-right: var(--space-xs); } + .padding-right-sm\@#{$breakpoint} { padding-right: var(--space-sm); } + .padding-right-md\@#{$breakpoint} { padding-right: var(--space-md); } + .padding-right-lg\@#{$breakpoint} { padding-right: var(--space-lg); } + .padding-right-xl\@#{$breakpoint} { padding-right: var(--space-xl); } + .padding-right-xxl\@#{$breakpoint} { padding-right: var(--space-xxl); } + .padding-right-xxxl\@#{$breakpoint} { padding-right: var(--space-xxxl); } + .padding-right-xxxxl\@#{$breakpoint} { padding-right: var(--space-xxxxl); } + .padding-right-0\@#{$breakpoint} { padding-right: 0; } + .padding-right-component\@#{$breakpoint} { padding-right: var(--component-padding); } + + .padding-left-xxxxs\@#{$breakpoint} { padding-left: var(--space-xxxxs); } + .padding-left-xxxs\@#{$breakpoint} { padding-left: var(--space-xxxs); } + .padding-left-xxs\@#{$breakpoint} { padding-left: var(--space-xxs); } + .padding-left-xs\@#{$breakpoint} { padding-left: var(--space-xs); } + .padding-left-sm\@#{$breakpoint} { padding-left: var(--space-sm); } + .padding-left-md\@#{$breakpoint} { padding-left: var(--space-md); } + .padding-left-lg\@#{$breakpoint} { padding-left: var(--space-lg); } + .padding-left-xl\@#{$breakpoint} { padding-left: var(--space-xl); } + .padding-left-xxl\@#{$breakpoint} { padding-left: var(--space-xxl); } + .padding-left-xxxl\@#{$breakpoint} { padding-left: var(--space-xxxl); } + .padding-left-xxxxl\@#{$breakpoint} { padding-left: var(--space-xxxxl); } + .padding-left-0\@#{$breakpoint} { padding-left: 0; } + .padding-left-component\@#{$breakpoint} { padding-left: var(--component-padding); } + + .padding-x-xxxxs\@#{$breakpoint} { padding-left: var(--space-xxxxs); padding-right: var(--space-xxxxs); } + .padding-x-xxxs\@#{$breakpoint} { padding-left: var(--space-xxxs); padding-right: var(--space-xxxs); } + .padding-x-xxs\@#{$breakpoint} { padding-left: var(--space-xxs); padding-right: var(--space-xxs); } + .padding-x-xs\@#{$breakpoint} { padding-left: var(--space-xs); padding-right: var(--space-xs); } + .padding-x-sm\@#{$breakpoint} { padding-left: var(--space-sm); padding-right: var(--space-sm); } + .padding-x-md\@#{$breakpoint} { padding-left: var(--space-md); padding-right: var(--space-md); } + .padding-x-lg\@#{$breakpoint} { padding-left: var(--space-lg); padding-right: var(--space-lg); } + .padding-x-xl\@#{$breakpoint} { padding-left: var(--space-xl); padding-right: var(--space-xl); } + .padding-x-xxl\@#{$breakpoint} { padding-left: var(--space-xxl); padding-right: var(--space-xxl); } + .padding-x-xxxl\@#{$breakpoint} { padding-left: var(--space-xxxl); padding-right: var(--space-xxxl); } + .padding-x-xxxxl\@#{$breakpoint} { padding-left: var(--space-xxxxl); padding-right: var(--space-xxxxl); } + .padding-x-0\@#{$breakpoint} { padding-left: 0; padding-right: 0; } + .padding-x-component\@#{$breakpoint} { padding-left: var(--component-padding); padding-right: var(--component-padding); } + + .padding-y-xxxxs\@#{$breakpoint} { padding-top: var(--space-xxxxs); padding-bottom: var(--space-xxxxs); } + .padding-y-xxxs\@#{$breakpoint} { padding-top: var(--space-xxxs); padding-bottom: var(--space-xxxs); } + .padding-y-xxs\@#{$breakpoint} { padding-top: var(--space-xxs); padding-bottom: var(--space-xxs); } + .padding-y-xs\@#{$breakpoint} { padding-top: var(--space-xs); padding-bottom: var(--space-xs); } + .padding-y-sm\@#{$breakpoint} { padding-top: var(--space-sm); padding-bottom: var(--space-sm); } + .padding-y-md\@#{$breakpoint} { padding-top: var(--space-md); padding-bottom: var(--space-md); } + .padding-y-lg\@#{$breakpoint} { padding-top: var(--space-lg); padding-bottom: var(--space-lg); } + .padding-y-xl\@#{$breakpoint} { padding-top: var(--space-xl); padding-bottom: var(--space-xl); } + .padding-y-xxl\@#{$breakpoint} { padding-top: var(--space-xxl); padding-bottom: var(--space-xxl); } + .padding-y-xxxl\@#{$breakpoint} { padding-top: var(--space-xxxl); padding-bottom: var(--space-xxxl); } + .padding-y-xxxxl\@#{$breakpoint} { padding-top: var(--space-xxxxl); padding-bottom: var(--space-xxxxl); } + .padding-y-0\@#{$breakpoint} { padding-top: 0; padding-bottom: 0; } + .padding-y-component\@#{$breakpoint} { padding-top: var(--component-padding); padding-bottom: var(--component-padding); } + + // text-align + .text-center\@#{$breakpoint} { text-align: center; } + .text-left\@#{$breakpoint} { text-align: left; } + .text-right\@#{$breakpoint} { text-align: right; } + .text-justify\@#{$breakpoint} { text-align: justify; } + + // font-size + .text-xs\@#{$breakpoint} { font-size: var(--text-xs, 0.6875rem); } + .text-sm\@#{$breakpoint} { font-size: var(--text-sm, 0.75rem); } + .text-base\@#{$breakpoint} { font-size: var(--text-unit, 1rem); } + .text-md\@#{$breakpoint} { font-size: var(--text-md, 1.125rem); } + .text-lg\@#{$breakpoint} { font-size: var(--text-lg, 1.375rem); } + .text-xl\@#{$breakpoint} { font-size: var(--text-xl, 1.75rem); } + .text-xxl\@#{$breakpoint} { font-size: var(--text-xxl, 2rem); } + .text-xxxl\@#{$breakpoint} { font-size: var(--text-xxxl, 2.5rem); } + .text-xxxxl\@#{$breakpoint} { font-size: var(--text-xxxxl, 3rem); } + + // width + .width-xxxxs\@#{$breakpoint} { width: var(--size-xxxxs, 0.25rem); } + .width-xxxs\@#{$breakpoint} { width: var(--size-xxxs, 0.5rem); } + .width-xxs\@#{$breakpoint} { width: var(--size-xxs, 0.75rem); } + .width-xs\@#{$breakpoint} { width: var(--size-xs, 1rem); } + .width-sm\@#{$breakpoint} { width: var(--size-sm, 1.5rem); } + .width-md\@#{$breakpoint} { width: var(--size-md, 2rem); } + .width-lg\@#{$breakpoint} { width: var(--size-lg, 3rem); } + .width-xl\@#{$breakpoint} { width: var(--size-xl, 4rem); } + .width-xxl\@#{$breakpoint} { width: var(--size-xxl, 6rem); } + .width-xxxl\@#{$breakpoint} { width: var(--size-xxxl, 8rem); } + .width-xxxxl\@#{$breakpoint} { width: var(--size-xxxxl, 16rem); } + .width-0\@#{$breakpoint} { width: 0; } + .width-10\%\@#{$breakpoint} { width: 10%; } + .width-20\%\@#{$breakpoint} { width: 20%; } + .width-25\%\@#{$breakpoint} { width: 25%; } + .width-30\%\@#{$breakpoint} { width: 30%; } + .width-33\%\@#{$breakpoint} { width: calc(100% / 3); } + .width-40\%\@#{$breakpoint} { width: 40%; } + .width-50\%\@#{$breakpoint} { width: 50%; } + .width-60\%\@#{$breakpoint} { width: 60%; } + .width-66\%\@#{$breakpoint} { width: calc(100% / 1.5); } + .width-70\%\@#{$breakpoint} { width: 70%; } + .width-75\%\@#{$breakpoint} { width: 75%; } + .width-80\%\@#{$breakpoint} { width: 80%; } + .width-90\%\@#{$breakpoint} { width: 90%; } + .width-100\%\@#{$breakpoint} { width: 100%; } + .width-100vw\@#{$breakpoint} { width: 100vw; } + .width-auto\@#{$breakpoint} { width: auto; } + + // height + .height-xxxxs\@#{$breakpoint} { height: var(--size-xxxxs, 0.25rem); } + .height-xxxs\@#{$breakpoint} { height: var(--size-xxxs, 0.5rem); } + .height-xxs\@#{$breakpoint} { height: var(--size-xxs, 0.75rem); } + .height-xs\@#{$breakpoint} { height: var(--size-xs, 1rem); } + .height-sm\@#{$breakpoint} { height: var(--size-sm, 1.5rem); } + .height-md\@#{$breakpoint} { height: var(--size-md, 2rem); } + .height-lg\@#{$breakpoint} { height: var(--size-lg, 3rem); } + .height-xl\@#{$breakpoint} { height: var(--size-xl, 4rem); } + .height-xxl\@#{$breakpoint} { height: var(--size-xxl, 6rem); } + .height-xxxl\@#{$breakpoint} { height: var(--size-xxxl, 8rem); } + .height-xxxxl\@#{$breakpoint} { height: var(--size-xxxxl, 16rem); } + .height-0\@#{$breakpoint} { height: 0; } + .height-10\%\@#{$breakpoint} { height: 10%; } + .height-20\%\@#{$breakpoint} { height: 20%; } + .height-25\%\@#{$breakpoint} { height: 25%; } + .height-30\%\@#{$breakpoint} { height: 30%; } + .height-33\%\@#{$breakpoint} { height: calc(100% / 3); } + .height-40\%\@#{$breakpoint} { height: 40%; } + .height-50\%\@#{$breakpoint} { height: 50%; } + .height-60\%\@#{$breakpoint} { height: 60%; } + .height-66\%\@#{$breakpoint} { height: calc(100% / 1.5); } + .height-70\%\@#{$breakpoint} { height: 70%; } + .height-75\%\@#{$breakpoint} { height: 75%; } + .height-80\%\@#{$breakpoint} { height: 80%; } + .height-90\%\@#{$breakpoint} { height: 90%; } + .height-100\%\@#{$breakpoint} { height: 100%; } + .height-100vh\@#{$breakpoint} { height: 100vh; } + .height-auto\@#{$breakpoint} { height: auto; } + + // max-width + .max-width-xxxxs\@#{$breakpoint} { max-width: var(--max-width-xxxxs); } + .max-width-xxxs\@#{$breakpoint} { max-width: var(--max-width-xxxs); } + .max-width-xxs\@#{$breakpoint} { max-width: var(--max-width-xxs); } + .max-width-xs\@#{$breakpoint} { max-width: var(--max-width-xs); } + .max-width-sm\@#{$breakpoint} { max-width: var(--max-width-sm); } + .max-width-md\@#{$breakpoint} { max-width: var(--max-width-md); } + .max-width-lg\@#{$breakpoint} { max-width: var(--max-width-lg); } + .max-width-xl\@#{$breakpoint} { max-width: var(--max-width-xl); } + .max-width-xxl\@#{$breakpoint} { max-width: var(--max-width-xxl); } + .max-width-xxxl\@#{$breakpoint} { max-width: var(--max-width-xxxl); } + .max-width-xxxxl\@#{$breakpoint} { max-width: var(--max-width-xxxxl); } + .max-width-100\%\@#{$breakpoint} { max-width: 100%; } + .max-width-none\@#{$breakpoint} { max-width: none; } + + // position + .position-relative\@#{$breakpoint} { position: relative; } + .position-absolute\@#{$breakpoint} { position: absolute; } + .position-fixed\@#{$breakpoint} { position: fixed; } + .position-sticky\@#{$breakpoint} { position: sticky; } + .position-static\@#{$breakpoint} { position: static; } + + .inset-0\@#{$breakpoint} { top: 0; right: 0; bottom: 0; left: 0; } + + .top-0\@#{$breakpoint} { top: 0; } + .top-50\%\@#{$breakpoint} { top: 50%; } + .top-xxxxs\@#{$breakpoint} { top: var(--space-xxxxs); } + .top-xxxs\@#{$breakpoint} { top: var(--space-xxxs); } + .top-xxs\@#{$breakpoint} { top: var(--space-xxs); } + .top-xs\@#{$breakpoint} { top: var(--space-xs); } + .top-sm\@#{$breakpoint} { top: var(--space-sm); } + .top-md\@#{$breakpoint} { top: var(--space-md); } + .top-lg\@#{$breakpoint} { top: var(--space-lg); } + .top-xl\@#{$breakpoint} { top: var(--space-xl); } + .top-xxl\@#{$breakpoint} { top: var(--space-xxl); } + .top-xxxl\@#{$breakpoint} { top: var(--space-xxxl); } + .top-xxxxl\@#{$breakpoint} { top: var(--space-xxxxl); } + + .bottom-0\@#{$breakpoint} { bottom: 0; } + .bottom-unset\@#{$breakpoint} { bottom: unset; } + .bottom-50\%\@#{$breakpoint} { bottom: 50%; } + .bottom-xxxxs\@#{$breakpoint} { bottom: var(--space-xxxxs); } + .bottom-xxxs\@#{$breakpoint} { bottom: var(--space-xxxs); } + .bottom-xxs\@#{$breakpoint} { bottom: var(--space-xxs); } + .bottom-xs\@#{$breakpoint} { bottom: var(--space-xs); } + .bottom-sm\@#{$breakpoint} { bottom: var(--space-sm); } + .bottom-md\@#{$breakpoint} { bottom: var(--space-md); } + .bottom-lg\@#{$breakpoint} { bottom: var(--space-lg); } + .bottom-xl\@#{$breakpoint} { bottom: var(--space-xl); } + .bottom-xxl\@#{$breakpoint} { bottom: var(--space-xxl); } + .bottom-xxxl\@#{$breakpoint} { bottom: var(--space-xxxl); } + .bottom-xxxxl\@#{$breakpoint} { bottom: var(--space-xxxxl); } + + .right-0\@#{$breakpoint} { right: 0; } + .right-50\%\@#{$breakpoint} { right: 50%; } + .right-xxxxs\@#{$breakpoint} { right: var(--space-xxxxs); } + .right-xxxs\@#{$breakpoint} { right: var(--space-xxxs); } + .right-xxs\@#{$breakpoint} { right: var(--space-xxs); } + .right-xs\@#{$breakpoint} { right: var(--space-xs); } + .right-sm\@#{$breakpoint} { right: var(--space-sm); } + .right-md\@#{$breakpoint} { right: var(--space-md); } + .right-lg\@#{$breakpoint} { right: var(--space-lg); } + .right-xl\@#{$breakpoint} { right: var(--space-xl); } + .right-xxl\@#{$breakpoint} { right: var(--space-xxl); } + .right-xxxl\@#{$breakpoint} { right: var(--space-xxxl); } + .right-xxxxl\@#{$breakpoint} { right: var(--space-xxxxl); } + + .left-0\@#{$breakpoint} { left: 0; } + .left-50\%\@#{$breakpoint} { left: 50%; } + .left-xxxxs\@#{$breakpoint} { left: var(--space-xxxxs); } + .left-xxxs\@#{$breakpoint} { left: var(--space-xxxs); } + .left-xxs\@#{$breakpoint} { left: var(--space-xxs); } + .left-xs\@#{$breakpoint} { left: var(--space-xs); } + .left-sm\@#{$breakpoint} { left: var(--space-sm); } + .left-md\@#{$breakpoint} { left: var(--space-md); } + .left-lg\@#{$breakpoint} { left: var(--space-lg); } + .left-xl\@#{$breakpoint} { left: var(--space-xl); } + .left-xxl\@#{$breakpoint} { left: var(--space-xxl); } + .left-xxxl\@#{$breakpoint} { left: var(--space-xxxl); } + .left-xxxxl\@#{$breakpoint} { left: var(--space-xxxxl); } + + // overflow + .overflow-hidden\@#{$breakpoint} { overflow: hidden; } + .overflow-auto\@#{$breakpoint} { overflow: auto; } + .momentum-scrolling\@#{$breakpoint} { -webkit-overflow-scrolling: touch; } + .overscroll-contain\@#{$breakpoint} { overscroll-behavior: contain; } + + // visibility + .visible\@#{$breakpoint} { visibility: visible; } + .invisible\@#{$breakpoint} { visibility: hidden; } + } + + @include breakpoint(#{$breakpoint}, "not all") { + .display\@#{$breakpoint} { display: none !important; } + } +} diff --git a/old-apps/web-shared/src/styles/base/_visibility.scss b/old-apps/web-shared/src/styles/base/_visibility.scss new file mode 100644 index 0000000..ab6a516 --- /dev/null +++ b/old-apps/web-shared/src/styles/base/_visibility.scss @@ -0,0 +1,23 @@ +:root { + --display: block; +} + +.is-visible { + display: var(--display) !important; +} + +.is-hidden { + display: none !important; +} + +html:not(.js) { + .no-js\:is-hidden { + display: none !important; + } +} + +@media print { + .print\:is-hidden { + display: none !important; + } +}
\ No newline at end of file diff --git a/old-apps/web-shared/src/styles/base/_z-index.scss b/old-apps/web-shared/src/styles/base/_z-index.scss new file mode 100644 index 0000000..5af9ff3 --- /dev/null +++ b/old-apps/web-shared/src/styles/base/_z-index.scss @@ -0,0 +1,6 @@ +:root { + --z-index-header: 3; // e.g., main header + --z-index-popover: 5; // e.g., tooltips and dropdown + --z-index-fixed-element: 10; // e.g., 'back to top' button + --z-index-overlay: 15; // e.g., modals and dialogs +}
\ No newline at end of file diff --git a/old-apps/web-shared/src/styles/components/404.scss b/old-apps/web-shared/src/styles/components/404.scss new file mode 100644 index 0000000..1a0267a --- /dev/null +++ b/old-apps/web-shared/src/styles/components/404.scss @@ -0,0 +1,43 @@ +@use '../base' as *; + +.fof { +} + +.fof__animation { + svg { + display: block; + max-width: 520px; + margin-left: auto; + margin-right: auto; + } +} + +#i-fof-browser { + transform-origin: 260px 304px; + animation: i-fof-browser 4s infinite; +} + +#i-fof-shadow { + transform-origin: 282px 410px; + animation: i-fof-shadow 4s infinite; +} + +@keyframes i-fof-browser { + 0%, 100% { + transform: translateY(0) scale(1); + } + + 50% { + transform: translateY(-10%) scale(0.9); + } +} + +@keyframes i-fof-shadow { + 0%, 100% { + transform: scale(1); + } + + 50% { + transform: scale(0.8); + } +} diff --git a/old-apps/web-shared/src/styles/components/adv-custom-select.scss b/old-apps/web-shared/src/styles/components/adv-custom-select.scss new file mode 100644 index 0000000..bd28247 --- /dev/null +++ b/old-apps/web-shared/src/styles/components/adv-custom-select.scss @@ -0,0 +1,79 @@ +@use '../base' as *; +@use 'popover.scss' as *; + +/* -------------------------------- + +File#: _2_adv-custom-select +Title: Advanced Custom Select +Descr: Custom select with advanced structure options +Usage: codyhouse.co/license + +-------------------------------- */ + +.adv-select { +} + +.adv-select__control { +} + +.adv-select-popover { + // use rem units + @include spaceUnit(1rem); + @include textUnit(1rem); + + &.popover { // popover component - dependency + --popover-width: 250px; + --popover-control-gap: 4px; // ⚠️ use px units - vertical gap between the popover and its control + --popover-viewport-gap: 20px; // ⚠️ use px units - vertical gap between the popover and the viewport - visible if popover height > viewport height + --popover-transition-duration: 0.2s; + + @include breakpoint(md) { + --popover-width: 320px; + } + } +} + +.adv-select-popover__optgroup:not(:first-of-type) { // custom <optgroup> + padding-top: var(--space-xxs); +} + +.adv-select-popover__optgroup:not(:last-of-type) { + border-bottom: 1px solid alpha(var(--color-contrast-higher), 0.1); + padding-bottom: var(--space-xxs); +} + +.adv-select-popover__check { + display: none; +} + +.adv-select-popover__label { +} + +.adv-select-popover__option { + position: relative; + cursor: pointer; + @include fontSmooth; + transition: .2s; + + &:hover { + background-color: alpha(var(--color-contrast-higher), 0.075); + } + + &:focus { + outline: none; + background-color: alpha(var(--color-primary), 0.15); + } + + &[aria-selected=true] { // selected option + background-color: var(--color-primary); + color: var(--color-white); + + .adv-select-popover__check { + display: block; + } + + &:focus { + box-shadow: inset 0 0 0 2px var(--color-primary-dark); + } + } +} diff --git a/old-apps/web-shared/src/styles/components/alert.scss b/old-apps/web-shared/src/styles/components/alert.scss new file mode 100644 index 0000000..9d9008d --- /dev/null +++ b/old-apps/web-shared/src/styles/components/alert.scss @@ -0,0 +1,69 @@ +@use '../base' as *; + +/* -------------------------------- + +File#: _1_alert +Title: Alert +Descr: Feedback message +Usage: codyhouse.co/license + +-------------------------------- */ + +.alert { + background-color: alpha(var(--color-primary), 0.2); + color: var(--color-contrast-higher); + + // hide element + position: absolute; + clip: rect(1px, 1px, 1px, 1px); + clip-path: inset(50%); +} + +.alert__icon { + color: var(--color-primary); +} + +.alert__close-btn { + display: inline-block; + + .icon { + display: block; + } + + &:hover { + opacity: 0.7; + } +} + +// themes +.alert--success { + background-color: alpha(var(--color-success), 0.2); + + .alert__icon { + color: var(--color-success); + } +} + +.alert--error { + background-color: alpha(var(--color-error), 0.2); + + .alert__icon { + color: var(--color-error); + } +} + +.alert--warning { + background-color: alpha(var(--color-warning), 0.2); + + .alert__icon { + color: var(--color-warning); + } +} + +// toggle visibility +.alert--is-visible { + position: static; + clip: auto; + clip-path: none; +} + diff --git a/old-apps/web-shared/src/styles/components/auto-sized-grid.scss b/old-apps/web-shared/src/styles/components/auto-sized-grid.scss new file mode 100644 index 0000000..a3b1be5 --- /dev/null +++ b/old-apps/web-shared/src/styles/components/auto-sized-grid.scss @@ -0,0 +1,56 @@ +@use '../base' as *; + +/* -------------------------------- + +File#: _1_auto-sized-grid +Title: Auto Sized Grid +Descr: A grid layout based on CSS Grid where the columns are automatically created according to a min-width value +Usage: codyhouse.co/license + +-------------------------------- */ + +.grid-auto-xs, .grid-auto-sm, .grid-auto-md, .grid-auto-lg, .grid-auto-xl { + display: grid; + gap: var(--gap-y, 0px) var(--gap-x, 0px); + grid-template-columns: repeat(auto-fit, minmax(var(--col-min-width), 1fr)); // auto add new cols +} + +.grid-auto-xs { + --col-min-width: 8rem; +} + +.grid-auto-sm { + --col-min-width: 10rem; +} + +.grid-auto-md { + --col-min-width: 15rem; +} + +.grid-auto-lg { + --col-min-width: 20rem; +} + +.grid-auto-xl { + --col-min-width: 25rem; +} + +@each $breakpoint, $value in $breakpoints { + @include breakpoint(#{$breakpoint}) { + .grid-auto-xs\@#{$breakpoint} { + --col-min-width: 8rem; + } + .grid-auto-sm\@#{$breakpoint} { + --col-min-width: 10rem; + } + .grid-auto-md\@#{$breakpoint} { + --col-min-width: 15rem; + } + .grid-auto-lg\@#{$breakpoint} { + --col-min-width: 20rem; + } + .grid-auto-xl\@#{$breakpoint} { + --col-min-width: 25rem; + } + } +} diff --git a/old-apps/web-shared/src/styles/components/autocomplete.scss b/old-apps/web-shared/src/styles/components/autocomplete.scss new file mode 100644 index 0000000..c20d508 --- /dev/null +++ b/old-apps/web-shared/src/styles/components/autocomplete.scss @@ -0,0 +1,76 @@ +@use '../base' as *; +@use 'circle-loader.scss' as *; + +/* -------------------------------- + +File#: _2_autocomplete +Title: Autocomplete +Descr: Autocomplete plugin for input elements +Usage: codyhouse.co/license + +-------------------------------- */ + +:root { + --autocomplete-dropdown-vertical-gap: 4px; // gap between input and results list + --autocomplete-dropdown-max-height: 150px; + --autocomplete-dropdown-scrollbar-width: 6px; // custom scrollbar width - webkit browsers +} + +// results dropdown +.autocomplete__results { + position: absolute; + z-index: var(--z-index-popover, 5); + width: 100%; + left: 0; + top: calc(100% + var(--autocomplete-dropdown-vertical-gap)); + background-color: var(--color-bg-light); + box-shadow: var(--inner-glow), var(--shadow-xs); + border-radius: var(--radius-md); + opacity: 0; + visibility: hidden; + overflow: hidden; + + .autocomplete--results-visible & { + opacity: 1; + visibility: visible; + } +} + +.autocomplete__list { + max-height: var(--autocomplete-dropdown-max-height); + overflow: auto; + -webkit-overflow-scrolling: touch; + + // custom scrollbar + &::-webkit-scrollbar { // scrollbar width + width: var(--autocomplete-dropdown-scrollbar-width); + } + + &::-webkit-scrollbar-track { // progress bar + background-color: alpha(var(--color-contrast-higher), 0.08); + border-radius: 0; + } + + &::-webkit-scrollbar-thumb { // handle + background-color: alpha(var(--color-contrast-higher), 0.12); + border-radius: 0; + + &:hover { + background-color: alpha(var(--color-contrast-higher), 0.2); + } + } +} + +// single result item +.autocomplete__item { + cursor: pointer; + + &:hover { + background-color: alpha(var(--color-contrast-higher), 0.075); + } + + &:focus { + outline: none; + background-color: alpha(var(--color-primary), 0.15); + } +} diff --git a/old-apps/web-shared/src/styles/components/breadcrumbs.scss b/old-apps/web-shared/src/styles/components/breadcrumbs.scss new file mode 100644 index 0000000..45bf7c6 --- /dev/null +++ b/old-apps/web-shared/src/styles/components/breadcrumbs.scss @@ -0,0 +1,18 @@ +@use '../base' as *; + +/* -------------------------------- + +File#: _1_breadcrumbs +Title: Breadcrumbs +Descr: List of links to help the user move within website structure +Usage: codyhouse.co/license + +-------------------------------- */ + +.breadcrumbs {} + +.breadcrumbs__item { + display: inline-block; // flex fallback + display: inline-flex; + align-items: center; +} diff --git a/old-apps/web-shared/src/styles/components/btn-states.scss b/old-apps/web-shared/src/styles/components/btn-states.scss new file mode 100644 index 0000000..a2fc6c5 --- /dev/null +++ b/old-apps/web-shared/src/styles/components/btn-states.scss @@ -0,0 +1,51 @@ +@use '../base' as *; + +/* -------------------------------- + +File#: _1_btn-states +Title: Buttons states +Descr: Multi-state button elements +Usage: codyhouse.co/license + +-------------------------------- */ + +.btn__content-a { + display: inline-flex; +} + +.btn__content-b { + display: none; +} + +.btn__content-a, .btn__content-b { + align-items: center; +} + +.btn--state-b { + .btn__content-a { + display: none; + } + + .btn__content-b { + display: inline-block; // fallback + display: inline-flex; + } +} + +/* preserve button width when switching from state A to state B */ +.btn--preserve-width { + .btn__content-b { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + justify-content: center; + } + + &.btn--state-b .btn__content-a { + display: inline-block; // fallback + display: inline-flex; + visibility: hidden; + } +} diff --git a/old-apps/web-shared/src/styles/components/chip.scss b/old-apps/web-shared/src/styles/components/chip.scss new file mode 100644 index 0000000..1bb93db --- /dev/null +++ b/old-apps/web-shared/src/styles/components/chip.scss @@ -0,0 +1,117 @@ +@use '../base' as *; + +/* -------------------------------- + +File#: _1_chips +Title: Chips +Descr: A list of compact pieces of information +Usage: codyhouse.co/license + +-------------------------------- */ + +.chip { + /* reset - in case the class is applied to a <button> or an <a> */ + border: 0; + color: inherit; + line-height: 1; + appearance: none; + + display: inline-flex; + align-items: center; + border-radius: var(--radius-sm); + + background-color: alpha(var(--color-contrast-higher), 0.1); + padding: var(--space-xxxs) 0; +} + +.chip--outline { + background-color: transparent; + box-shadow: inset 0 0 0 1px alpha(var(--color-contrast-higher), 0.25); +} + +.chip--error { + background-color: alpha(var(--color-error), 0.2); + color: var(--color-contrast-higher); +} + +.chip--success { + background-color: alpha(var(--color-success), 0.2); + color: var(--color-contrast-higher); +} + +.chip--warning { + background-color: alpha(var(--color-warning), 0.2); + color: var(--color-contrast-higher); +} + +.chip--interactive { + cursor: pointer; + + &:hover { + background-color: alpha(var(--color-contrast-higher), 0.2); + } + + &:focus { + outline: none; + box-shadow: 0 0 0 3px alpha(var(--color-contrast-higher), 0.3); + } + + &:focus:not(:focus-visible) { + box-shadow: none; + } +} + +.chip__label { + padding: 0 var(--space-xxs); +} + +.chip__img { + display: block; + width: 1.5em; + height: 1.5em; + border-radius: 50%; + object-fit: cover; +} + +.chip__icon-wrapper { + display: flex; + width: 1.5em; + height: 1.5em; + border-radius: 50%; + background-color: alpha(var(--color-contrast-higher), 0.95); + color: var(--color-bg); /* icon color */ + + .icon { + display: block; + margin: auto; + } +} + +.chip__btn { + @include reset; + display: flex; + width: 1em; + height: 1em; + background-color: alpha(var(--color-contrast-higher), 0.2); + border-radius: 50%; + cursor: pointer; + margin-right: 7px; + + .icon { + display: block; + margin: 0 auto; + } + + &:hover { + background-color: alpha(var(--color-contrast-higher), 0.3); + } + + &:focus { + outline: none; + box-shadow: 0 0 0 2px alpha(var(--color-contrast-higher), 0.5); + } + + &:focus:not(:focus-visible) { + box-shadow: none; + } +} diff --git a/old-apps/web-shared/src/styles/components/circle-loader.scss b/old-apps/web-shared/src/styles/components/circle-loader.scss new file mode 100644 index 0000000..5116d39 --- /dev/null +++ b/old-apps/web-shared/src/styles/components/circle-loader.scss @@ -0,0 +1,315 @@ +@use '../base' as *; + +/* -------------------------------- + +File#: _1_circle-loader +Title: Circle Loader +Descr: A collection of animated circle loaders +Usage: codyhouse.co/license + +-------------------------------- */ + +:root { + // v1 + --circle-loader-v1-size: 48px; + --circle-loader-v1-stroke-width: 4px; + + // v2 + --circle-loader-v2-size: 64px; + --circle-loader-v2-stroke-width: 2; + + // v3 + --circle-loader-v3-size: 64px; + + // v4 + --circle-loader-v4-size: 48px; + + // v5 + --circle-loader-v5-size: 64px; + + // v6 + --circle-loader-v6-size: 48px; +} + +.circle-loader { + position: relative; + display: inline-block; +} + +@supports (animation-name: this) { + .circle-loader__label { + @include srHide; // show label only to screen readers if animations are supported + } +} + +// loader v1 - rotation +@supports (animation-name: this) { + .circle-loader--v1 { + transform: rotate(45deg); + will-change: transform; + animation: circle-loader-1 0.75s infinite var(--ease-in-out); + + .circle-loader__circle { + width: var(--circle-loader-v1-size); // loader width + height: var(--circle-loader-v1-size); // loader height + border-width: var(--circle-loader-v1-stroke-width); // loader stroke width + border-style: solid; + border-color: alpha(var(--color-primary), 0.2); // loader base color + border-radius: 50%; + + &::after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border-width: inherit; + border-style: inherit; + border-color: transparent; + border-top-color: var(--color-primary); // loader fill color + border-radius: inherit; + } + } + } +} + +@keyframes circle-loader-1 { + 0% { + transform: rotate(45deg); + } + + 100% { + transform: rotate(405deg); + } +} + +// loader v2 - filling +@supports (animation-name: this) { + .circle-loader--v2 { + will-change: transform; + animation: circle-loader-spinning-main 1.4s infinite linear; + + .circle-loader__svg { + display: block; + width: var(--circle-loader-v2-size); + height: var(--circle-loader-v2-size); + color: var(--color-primary); // loader color + + > * { + stroke-width: var(--circle-loader-v2-stroke-width); // loader stroke width + } + } + + .circle-loader__base { + opacity: 0.2; + } + + .circle-loader__fill { + stroke-linecap: round; // optional - remove if you prefer butt caps + stroke-dashoffset: 0; + stroke-dasharray: 90 120; + transform-origin: 50% 50%; + transform: rotate(45deg); + animation: circle-loader-dash 1.4s infinite; + } + } +} + +@keyframes circle-loader-dash { + 0%, 20% { + stroke-dashoffset: 0; + transform: rotate(0); + } + + 50%, 70% { + stroke-dashoffset: 80; + transform: rotate(270deg); + } + + 100% { + stroke-dashoffset: 0; + transform: rotate(360deg); + } +} + +@keyframes circle-loader-spinning-main { + to { + transform: rotate(360deg); + } +} + +// loader v3 - drop +@supports (animation-name: this) { + .circle-loader--v3 { + width: var(--circle-loader-v3-size); // loader width + height: var(--circle-loader-v3-size); // loader height + + .circle-loader__circle { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border-radius: 50%; + background-color: var(--color-primary); // loader color + transform: scale(0); + opacity: 0.8; + will-change: transform, opacity; + animation: circle-loader-3 1.2s infinite; + } + + .circle-loader__circle--2nd { + animation-delay: 0.6s; // this should be half the duration of animation + } + } +} + +@keyframes circle-loader-3 { + to { + transform: scale(1); + opacity: 0; + } +} + +// loader v4 - eclipse +@supports (animation-name: this) { + .circle-loader--v4 { + width: var(--circle-loader-v4-size); // loader width + height: var(--circle-loader-v4-size); // loader height + border-radius: 50%; + overflow: hidden; + + .circle-loader__mask, + .circle-loader__circle { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border-radius: inherit; + } + + .circle-loader__mask { + clip-path: circle(calc(0.5 * var(--circle-loader-v4-size))); // fix iOS issue - it needs to be = half size of loader + } + + .circle-loader__circle--1st { + background-color: var(--color-contrast-low); // loader base color + } + + .circle-loader__circle--2nd { + background-color: var(--color-primary); // loader fill color + will-change: transform; + transform-origin: 50% 100%; + animation: circle-loader-4 1.2s infinite cubic-bezier(.23, .9, .75, .1); + transform: translateX(-100%); + } + } +} + +@keyframes circle-loader-4 { + to { + transform: translateX(100%); + } +} + +// loader v5 - bounce +@supports (animation-name: this) { + .circle-loader--v5 { + font-size: var(--circle-loader-v5-size); // loader size - if you edit this value all elements scale accordingly + width: 1em; + height: 1em; + + .circle-loader__label { + font-size: 1rem; + } + + .circle-loader__ball { + position: absolute; + top: 0; + left: calc(50% - 0.140625em); + width: 0.28125em; + height: 0.28125em; + background-color: var(--color-primary); + border-radius: 50%; + animation: circle-loader-5-ball 0.8s infinite; + } + + .circle-loader__shadow { + position: absolute; + bottom: 0; + left: calc(50% - 0.15625em); + width: 0.3125em; + height: 0.3125em; + background-color: var(--color-contrast-lower); + border-radius: 50%; + transform: scaleY(0.4) scaleX(1.2); + animation: circle-loader-5-shadow 0.8s infinite; + } + } +} + +@keyframes circle-loader-5-ball { + 0% { + transform: translateY(0); + animation-timing-function: cubic-bezier(.61, .12, .85, .4); + } + + 50% { + transform: translateY(0.5625em); + animation-timing-function: cubic-bezier(.12, .59, .46, .95); + } + + 100% { + transform: translateY(0); + } +} + +@keyframes circle-loader-5-shadow { + 0% { + transform: scaleY(0.4) scaleX(1.2); + background-color: var(--color-contrast-lower); + animation-timing-function: cubic-bezier(.61, .12, .85, .4); + } + + 50% { + transform: scaleY(0.2) scaleX(0.6); + background-color: var(--color-contrast-low); + animation-timing-function: cubic-bezier(.12, .59, .46, .95); + } + + 100% { + transform: scaleY(0.4) scaleX(1.2); + background-color: var(--color-contrast-lower); + } +} + +// loader v6 - worm +@supports (animation-name: this) { + .circle-loader--v6 { + .circle-loader__svg { + display: block; + width: var(--circle-loader-v6-size); + height: var(--circle-loader-v6-size); + color: var(--color-primary); // loader color + } + + .circle-loader__fill { + stroke-width: 8px; // loader stroke width + stroke-dashoffset: 35; + stroke-dasharray: 36 36; + animation: circle-loader-6 1.5s infinite; + } + } +} + +@keyframes circle-loader-6 { + 0%, 100% { + stroke-dashoffset: 35; + } + + 50% { + stroke-dashoffset: -35; + } +} diff --git a/old-apps/web-shared/src/styles/components/custom-checkbox.scss b/old-apps/web-shared/src/styles/components/custom-checkbox.scss new file mode 100644 index 0000000..5722ee0 --- /dev/null +++ b/old-apps/web-shared/src/styles/components/custom-checkbox.scss @@ -0,0 +1,131 @@ +@use '../base' as *; + +/* -------------------------------- + +File#: _1_custom-checkbox +Title: Custom Checkbox +Descr: Replace the native checkbox button with a custom element (e.g., an icon) +Usage: codyhouse.co/license + +-------------------------------- */ + +:root { + --custom-checkbox-size: 20px; + --custom-checkbox-radius: 4px; + --custom-checkbox-border-width: 1px; + --custom-checkbox-marker-size: 18px; +} + +.custom-checkbox { + position: relative; + z-index: 1; + display: inline-block; + font-size: var(--custom-checkbox-size); +} + +.custom-checkbox__input { + position: relative; + /* hide native input */ + margin: 0; + padding: 0; + opacity: 0; + height: 1em; + width: 1em; + display: block; + z-index: 1; +} + +.custom-checkbox__control { + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + z-index: -1; + pointer-events: none; + color: alpha(var(--color-contrast-low), 0.65); /* unchecked color */ + + &::before, &::after { + content: ''; + position: absolute; + } + + &::before { /* focus circle */ + width: 160%; + height: 160%; + background-color: currentColor; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) scale(0); + opacity: 0; + border-radius: 50%; + will-change: transform; + } + + &::after { /* custom checkbox */ + top: 0; + left: 0; + width: 100%; + height: 100%; + + /* custom checkbox style */ + background-color: var(--color-bg); + border-radius: var(--custom-checkbox-radius); + box-shadow: inset 0 0 0 var(--custom-checkbox-border-width) currentColor, var(--shadow-xs); /* border */ + } +} + +.custom-checkbox__input:checked ~ .custom-checkbox__control, +.custom-checkbox__input:indeterminate ~ .custom-checkbox__control { + &::after { + background-color: currentColor; + background-repeat: no-repeat; + background-position: center; + background-size: var(--custom-checkbox-marker-size); + box-shadow: none; + } +} + +.custom-checkbox__input:checked ~ .custom-checkbox__control { + color: var(--color-primary); /* checked color */ + + &::after { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpolyline points='2.5 8 6.5 12 13.5 3' fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5'/%3E%3C/svg%3E"); + } +} + +.custom-checkbox__input:indeterminate ~ .custom-checkbox__control { + &::after { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cline x1='2' y1='8' x2='14' y2='8' fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'/%3E%3C/svg%3E"); + } +} + +.custom-checkbox__input:active ~ .custom-checkbox__control { + transform: scale(0.9); +} + +.custom-checkbox__input:checked:active ~ .custom-checkbox__control, +.custom-checkbox__input:indeterminate:active ~ .custom-checkbox__control { + transform: scale(1); +} + +.custom-checkbox__input:focus ~ .custom-checkbox__control::before { + opacity: 0.2; + transform: translate(-50%, -50%) scale(1); +} + +/* --icon */ +.custom-checkbox--icon { + --custom-checkbox-size: 32px; + + .custom-checkbox__control::after { + display: none; + } + + .icon { + display: block; + color: inherit; + position: relative; + z-index: 1; + } +} diff --git a/old-apps/web-shared/src/styles/components/custom-select.scss b/old-apps/web-shared/src/styles/components/custom-select.scss new file mode 100644 index 0000000..9cd3b5e --- /dev/null +++ b/old-apps/web-shared/src/styles/components/custom-select.scss @@ -0,0 +1,158 @@ +@use '../base' as *; + +/* -------------------------------- + +File#: _1_custom-select +Title: Custom Select +Descr: Custom Select Control +Usage: codyhouse.co/license + +-------------------------------- */ + +:root { + // --default variation only 👇 + --select-icon-size: 16px; + --select-icon-right-margin: var(--space-sm); // icon margin right + --select-text-icon-gap: var(--space-xxxs); // gap between text and icon +} + +.select { + position: relative; +} + +.select__input { + width: 100%; + height: 100%; + padding-right: calc(var(--select-icon-size) + var(--select-icon-right-margin) + var(--select-text-icon-gap)) !important; +} + +.select__icon { + width: var(--select-icon-size); + height: var(--select-icon-size); + pointer-events: none; + position: absolute; + right: var(--select-icon-right-margin); + top: 50%; + transform: translateY(-50%); +} + +// --custom-dropdown +:root { + --select-dropdown-gap: 4px; // distance between select control and custom dropdown +} + +.select__button { // created in JS - custom select control + width: 100%; +} + +.select__button[aria-expanded="true"] { + // custom select control if dropdown = visible +} + +.select__dropdown { // created in JS - custom select dropdown + position: absolute; + left: 0; + top: 100%; + min-width: 200px; + max-height: 1px; // updated in JS + background-color: var(--color-bg-light); + box-shadow: var(--inner-glow), var(--shadow-md); + padding: var(--space-xxxs) 0; + border-radius: var(--radius-md); + z-index: var(--z-index-popover, 5); + margin-top: var(--select-dropdown-gap); + margin-bottom: var(--select-dropdown-gap); + overflow: auto; + + // use rem units + @include spaceUnit(1rem); + @include textUnit(1rem); + + visibility: hidden; + opacity: 0; +} + +.select__dropdown--right { // change dropdown position based on the available space + right: 0; + left: auto; +} + +.select__dropdown--up { + bottom: 100%; + top: auto; +} + +.select__button[aria-expanded="true"] + .select__dropdown { + visibility: visible; + opacity: 1; +} + +// custom <optgroup> list - include all <option>s if no <optgroup> available +.select__list { + list-style: none !important; +} + +.select__list:not(:first-of-type) { + padding-top: var(--space-xxs); +} + +.select__list:not(:last-of-type) { + border-bottom: 1px solid alpha(var(--color-contrast-higher), 0.1); + padding-bottom: var(--space-xxs); +} + +.select__item { // single item inside .select__list + display: flex; + align-items: center; + padding: var(--space-xxs) var(--space-sm); + color: var(--color-contrast-high); + width: 100%; + text-align: left; + // truncate text + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.select__item--optgroup { // custom <optgroup> label + font-size: var(--text-sm); + color: var(--color-contrast-medium); +} + +.select__item--option { // custom <option> label + cursor: pointer; + + &:hover { + background-color: alpha(var(--color-contrast-higher), 0.075); + } + + &:focus { + outline: none; + background-color: alpha(var(--color-primary), 0.15); + } + + &[aria-selected=true] { // selected option + background-color: var(--color-primary); + color: var(--color-white); + position: relative; + @include fontSmooth; + + &::after { // check icon next to the selected language + content: ''; + display: block; + height: 1em; + width: 1em; + margin-left: auto; + background-color: currentColor; + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpolyline stroke-width='2' stroke='%23ffffff' fill='none' stroke-linecap='round' stroke-linejoin='round' points='1,9 5,13 15,3 '/%3E%3C/svg%3E"); + } + + &:focus { + box-shadow: inset 0 0 0 2px var(--color-primary-dark); + } + } +} + +html:not(.js ) .select .icon { // hide icon if JS = disabled + display: none; +} diff --git a/old-apps/web-shared/src/styles/components/details.scss b/old-apps/web-shared/src/styles/components/details.scss new file mode 100644 index 0000000..b4c122d --- /dev/null +++ b/old-apps/web-shared/src/styles/components/details.scss @@ -0,0 +1,57 @@ +@use '../base' as *; + +/* -------------------------------- + +File#: _1_details +Title: Details +Descr: A button that toggles the visibility of additional information +Usage: codyhouse.co/license + +-------------------------------- */ + +.details {} + +.details__summary { + display: inline-block; + cursor: pointer; + user-select: none; + + &:hover { + color: var(--color-primary); + } + + &:focus { + outline: 2px solid alpha(var(--color-primary), 0.2); + outline-offset: 4px; + } + + .icon { + flex-shrink: 0; + } +} + +// if JS = enabled +.js { + .details__summary { + list-style: none; // remove summary default icon + } + + .details__summary::-webkit-details-marker { + display: none; // remove default icon in webkit browsers + } + + .details__summary[aria-expanded="true"] .icon { + transform: rotate(90deg); // rotate icon when content is visible + } + + .details__content[aria-hidden="true"] { + display: none; + } +} + +// if JS = disabled +html:not(.js) .details__summary { + .icon { + display: none; + } +} diff --git a/old-apps/web-shared/src/styles/components/dropdown.scss b/old-apps/web-shared/src/styles/components/dropdown.scss new file mode 100644 index 0000000..c5ded33 --- /dev/null +++ b/old-apps/web-shared/src/styles/components/dropdown.scss @@ -0,0 +1,98 @@ +@use '../base' as *; + +/* -------------------------------- + +File#: _2_dropdown +Title: Dropdown +Descr: A hoverable link that toggles the visibility of a dropdown list +Usage: codyhouse.co/license + +-------------------------------- */ + +:root { + --dropdown-item-padding: var(--space-xxs) var(--space-sm); +} + +.dropdown { + position: relative; +} + +.dropdown__menu { + border-radius: var(--radius-md); + background-color: var(--color-bg-light); + box-shadow: var(--inner-glow), var(--shadow-sm); + z-index: var(--z-index-popover, 5); + position: absolute; + left: 0; + top: 100%; + opacity: 0; + visibility: hidden; +} + +.dropdown__wrapper { + max-height: 24px; +} + +@media (pointer: fine) { // user has pointing device (e.g., mouse) + .dropdown__wrapper, + .open-dropdown { + &:hover .dropdown__menu, + &:focus .dropdown__menu { + opacity: 1; + visibility: visible; + } + } + + .dropdown__sub-wrapper:hover > .dropdown__menu { + left: 100%; + } +} + +@media not all and (pointer: fine) { + .dropdown__trigger-icon { + display: none; + } +} + +.dropdown__item { + display: block; + text-decoration: none; + color: var(--color-contrast-high); + padding: var(--dropdown-item-padding); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + &:hover, &.dropdown__item--hover { + background-color: alpha(var(--color-contrast-higher), 0.075); + } +} + +.dropdown__separator { // h line divider + height: 1px; + background-color: var(--color-contrast-lower); + margin: var(--dropdown-item-padding); +} + +.dropdown__sub-wrapper { + position: relative; + + > .dropdown__item { // item w/ right arrow + position: relative; + padding-right: calc(var(--space-sm) + 12px); + + .icon { // right arrow + position: absolute; + display: block; + width: 12px; + height: 12px; + right: var(--space-xxs); + top: calc(50% - 6px); + } + } + + > .dropdown__menu { // sub menu + top: calc(var(--space-xxs) * -1); + box-shadow: var(--inner-glow), var(--shadow-md); + } +} diff --git a/old-apps/web-shared/src/styles/components/form-validator.scss b/old-apps/web-shared/src/styles/components/form-validator.scss new file mode 100644 index 0000000..cc9f9a3 --- /dev/null +++ b/old-apps/web-shared/src/styles/components/form-validator.scss @@ -0,0 +1,18 @@ +@use '../base' as *; + +/* -------------------------------- + +File#: _1_form-validator +Title: Form Validator +Descr: A plugin to validate form fields +Usage: codyhouse.co/license + +-------------------------------- */ + +.form-validate__error-msg { + display: none; // hide error message by default + + .form-validate__input-wrapper--error & { + display: block; // show error message + } +} diff --git a/old-apps/web-shared/src/styles/components/interactive-table.scss b/old-apps/web-shared/src/styles/components/interactive-table.scss new file mode 100644 index 0000000..f239c62 --- /dev/null +++ b/old-apps/web-shared/src/styles/components/interactive-table.scss @@ -0,0 +1,156 @@ +@use '../base' as *; +@use 'menu.scss' as *; +@use 'menu-bar.scss' as *; + +/* -------------------------------- + +File#: _3_interactive-table +Title: Interactive Table +Descr: Table with the option of sorting data and selecting rows to perform specific actions +Usage: codyhouse.co/license + +-------------------------------- */ + +.int-table { + overflow: hidden; + border-bottom: 2px solid var(--color-contrast-lower); +} + +.int-table__inner { + position: relative; + overflow: auto; + + &::-webkit-scrollbar { // custom scrollbar style + height: 8px; + width: 8px; + } + + &::-webkit-scrollbar-track { // progress bar + background-color: var(--color-contrast-lower); + } + + &::-webkit-scrollbar-thumb { // handle + background-color: alpha(var(--color-contrast-higher), 0.9); + border-radius: 50em; + } + + &::-webkit-scrollbar-thumb:hover { + background-color: var(--color-contrast-higher); + } +} + +.int-table__table { + width: 100%; +} + +.int-table__header { + .int-table__cell { + background-color: var(--color-bg); + box-shadow: 0 2px 0 var(--color-contrast-lower); + } +} + +.int-table__body { + .int-table__row { + border-bottom: 1px solid var(--color-contrast-lower); + + &:last-child { + border-bottom: none; + } + } + + .int-table__row--checked { + background-color: alpha(var(--color-primary), 0.1); + border-color: alpha(var(--color-primary), 0.25); + } +} + +.int-table__cell { // standard cell + padding: var(--space-xxxs); +} + +.int-table__cell--th { // header cell + font-weight: 600; +} + +.int-table__cell--sort { // header cell with sorting option + user-select: none; + + &:hover, &:focus-within { + background-color: alpha(var(--color-contrast-higher), 0.075); + } + + &:hover { + cursor: pointer; + } +} + +.int-table__cell--focus { + background-color: alpha(var(--color-primary), 0.15); +} + +.int-table__sort-icon { // sorting icon indicator + .arrow-up, .arrow-down { + fill: alpha(var(--color-contrast-higher), 0.3); + } +} + +.int-table__cell--asc .int-table__sort-icon .arrow-up, +.int-table__cell--desc .int-table__sort-icon .arrow-down { + fill: var(--color-contrast-higher); +} + +.int-table__checkbox { + --custom-checkbox-size: 18px; + --custom-checkbox-marker-size: 16px; + display: block; + width: var(--custom-checkbox-size); + height: var(--custom-checkbox-size); +} + +.int-table__menu-btn { + display: flex; + align-items: center; + justify-content: center; + width: 2em; + cursor: pointer; + + .icon { + display: block; + width: 16px; + height: 16px; + } +} + +// --sticky-header +.int-table--sticky-header { + position: relative; + z-index: 1; + + .int-table__inner { + max-height: 605px; + } + + .int-table__header { + .int-table__cell { + position: sticky; + top: 0; + z-index: 2; + } + } +} + +// actions +.int-table-actions { + .menu-bar { + --menu-bar-button-size: 38px; // size of the menu buttons + --menu-bar-icon-size: 16px; // size of the icons inside the buttons + --menu-bar-horizontal-gap: var(--space-xxxxs); // horizontal gap between buttons + --menu-bar-vertical-gap: 4px; // vertical gap between buttons and labels (tooltips) + --menu-bar-label-size: var(--text-xs); // label font size + } + + .menu-bar__icon { + color: alpha(var(--color-contrast-higher), 0.5); + } +} diff --git a/old-apps/web-shared/src/styles/components/light-dark-switch.scss b/old-apps/web-shared/src/styles/components/light-dark-switch.scss new file mode 100644 index 0000000..23dc03a --- /dev/null +++ b/old-apps/web-shared/src/styles/components/light-dark-switch.scss @@ -0,0 +1,96 @@ +@use '../base' as *; +@use 'popover.scss' as *; +@use 'adv-custom-select.scss' as *; + +/* -------------------------------- + +File#: _3_light-dark-switch +Title: Light/Dark Switch +Descr: Color theme switcher +Usage: codyhouse.co/license + +-------------------------------- */ + +.ld-switch {} + +.ld-switch-btn { + position: relative; + width: 24px; + height: 24px; + overflow: hidden; + display: inline-block; + + + &:hover { + cursor: pointer; + opacity: 0.8; + } + + &:focus { + outline: none; + color: var(--color-primary); + } +} + +.ld-switch-btn.popover-control--active { + /* class added to the control button when the dropdown is visible */ + color: var(--color-primary); +} + +.ld-switch-btn__icon-wrapper { + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + display: flex; + justify-content: center; + align-items: center; + opacity: 0; + transform: translateY(100%) rotate(35deg) scale(0.5); +} + +.ld-switch-btn__icon-wrapper--in { + opacity: 1; + transform: translateY(0) rotate(0) scale(1); +} + +.ld-switch-btn__icon-wrapper--out { + opacity: 0; + transform: translateY(-100%) rotate(-35deg) scale(0.5); +} + + +.ld-switch-btn__icon { + margin: auto; + --size: 20px; /* icon size */ +} + +.popover.ld-switch-popover { + --popover-width: 250px; +} + +.ld-switch-popover__option { + user-select: none; + + &:hover { + cursor: pointer; + opacity: 0.85; + } + + &:focus { + outline: none; + + figure { + box-shadow: 0 0 0 1px var(--color-bg-light), 0 0 0 3px var(--color-contrast-higher); + } + } + + &[aria-selected=true] { + color: var(--color-primary); + + figure { + box-shadow: 0 0 0 1px var(--color-bg-light), 0 0 0 3px currentColor; + } + } +} diff --git a/old-apps/web-shared/src/styles/components/link-card.scss b/old-apps/web-shared/src/styles/components/link-card.scss new file mode 100644 index 0000000..dad4f98 --- /dev/null +++ b/old-apps/web-shared/src/styles/components/link-card.scss @@ -0,0 +1,56 @@ +@use '../base' as *; + +/* -------------------------------- + +File#: _1_link-card +Title: Link Card +Descr: Link card for app UI +Usage: codyhouse.co/license + +-------------------------------- */ + +.link-card { + text-decoration: none; + color: inherit; + box-shadow: var(--inner-glow), var(--shadow-xs); + + &:hover { + box-shadow: var(--inner-glow), var(--shadow-sm); + } +} + +.link-card__footer { + position: relative; + overflow: hidden; + height: 60px; + + > * { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + } + + > *:last-child { + transform: translateY(100%); + opacity: 0; + } +} + +.link-card:hover { + .link-card__footer { + > *:first-child { + transform: translateY(-100%); + opacity: 0; + } + + > *:last-child { + transform: translateY(0); + opacity: 1; + } + } +} diff --git a/old-apps/web-shared/src/styles/components/list.scss b/old-apps/web-shared/src/styles/components/list.scss new file mode 100644 index 0000000..df600a3 --- /dev/null +++ b/old-apps/web-shared/src/styles/components/list.scss @@ -0,0 +1,195 @@ +@use '../base' as *; + +/* -------------------------------- + +File#: _1_list +Title: List +Descr: Custom list component +Usage: codyhouse.co/license + +-------------------------------- */ + +:root { + --list-space-y: 0.375em; // vertical gaps + --list-offset: 1em; // sublist horizontal offset + --list-line-height-multiplier: 1; // line-height multiplier +} + +.list, .text-component .list { + padding-left: 0; + list-style: none; + + ul, ol { + list-style: none; + margin: 0; // reset + margin-top: calc((var(--list-space-y) / 2) * var(--text-space-y-multiplier, 1)); + padding-top: calc((var(--list-space-y) / 2) * var(--text-space-y-multiplier, 1)); + padding-left: var(--list-offset); + } + + li { + padding-bottom: calc((var(--list-space-y) / 2) * var(--text-space-y-multiplier, 1)); + margin-bottom: calc((var(--list-space-y) / 2) * var(--text-space-y-multiplier, 1)); + line-height: calc(var(--body-line-height) * var(--list-line-height-multiplier)); + } + + > li:last-child, ul > li:last-child, ol > li:last-child { + margin-bottom: 0; + } + + &:not(.list--border) > li:last-child, ul > li:last-child, ol > li:last-child { + padding-bottom: 0; + } +} + +/* #region (ul + ol) */ +.list--ul, .text-component .list--ul, +.list--ol, .text-component .list--ol { + --list-offset: calc(var(--list-bullet-size) + var(--list-bullet-margin-right)); + + ul, ol { + padding-left: 0; + } + + li { + @supports (--css: variables) { + padding-left: var(--list-offset) !important; + } + } + + li::before { + display: inline-flex; + justify-content: center; + align-items: center; + vertical-align: middle; + position: relative; + top: -0.1em; + + @supports (--css: variables) { + width: var(--list-bullet-size) !important; + height: var(--list-bullet-size) !important; + margin-left: calc(var(--list-bullet-size) * -1) !important; + left: calc(var(--list-bullet-margin-right) * -1) !important; + } + } +} + +// unordered list +.list--ul, .text-component .list--ul { + --list-bullet-size: 7px; // dot width and height + --list-bullet-margin-right: 12px; // gap between bullet and content + + > li { + padding-left: 19px; // IE fallback + } + + > li::before { // bullet + content: ''; + border-radius: 50%; + color: var(--color-contrast-lower); // bullet color + background-color: currentColor; + + // IE fallback + width: 7px; + height: 7px; + margin-left: -7px; + left: -12px; + // end - IE fallback + } + + ul li::before { + background-color: transparent; + box-shadow: inset 0 0 0 2px currentColor; + } +} + +// ordered list +.list--ol, .text-component .list--ol { + --list-bullet-size: 26px; // ⚠️ use px or rem units - circle width and height + --list-bullet-margin-right: 6px; // ⚠️ use px or rem units - gap between circle and content + --list-bullet-font-size: 14px; // ⚠️ use px or rem units - bullet font size + counter-reset: list-items; + + > li { + counter-increment: list-items; + padding-left: 32px; // IE fallback + } + + ol { + counter-reset: list-items; + } + + > li::before { + content: counter(list-items); + font-size: var(--list-bullet-font-size, 14px); + background-color: var(--color-contrast-lower); + color: var(--color-contrast-high); + line-height: 1; + border-radius: 50%; + + // IE fallback + width: 26px; + height: 26px; + margin-left: -26px; + left: -6px; + // end - IE fallback + } + + ol > li::before { + background-color: transparent; + box-shadow: inset 0 0 0 2px var(--color-contrast-lower); + } +} +/* #endregion */ + +/* #region (border) */ +.list--border, .text-component .list--border { // show border divider among list items + li:not(:last-child) { + border-bottom: 1px solid var(--color-contrast-lower); + } + + ul, ol { + border-top: 1px solid var(--color-contrast-lower); + } +} +/* #endregion */ + +/* #region (icons) */ +.list--icons, .text-component .list--icons { // use icons as bullet points + --list-bullet-size: 24px; + --list-bullet-margin-right: 8px; // gap between icon and text + --list-offset: calc(var(--list-bullet-size) + var(--list-bullet-margin-right)); + + ul, ol { + padding-left: 32px; // IE fallback + + @supports (--css: variables) { + padding-left: var(--list-offset); + } + } +} + +.list__icon { + position: relative; + + // IE fallback + width: 24px; + height: 24px; + margin-right: 8px; + + &:not(.top-0) { + top: calc((1em * var(--body-line-height) - 24px) / 2); + } + // end - IE fallback + + @supports (--css: variables) { + width: var(--list-bullet-size); + height: var(--list-bullet-size); + margin-right: var(--list-bullet-margin-right); + + &:not(.top-0) { + top: calc((1em * var(--body-line-height) * var(--list-line-height-multiplier) - var(--list-bullet-size)) / 2); + } + } +} +/* #endregion */ diff --git a/old-apps/web-shared/src/styles/components/menu-bar.scss b/old-apps/web-shared/src/styles/components/menu-bar.scss new file mode 100644 index 0000000..3f70fbe --- /dev/null +++ b/old-apps/web-shared/src/styles/components/menu-bar.scss @@ -0,0 +1,139 @@ +@use '../base' as *; +@use 'menu.scss' as *; + +/* -------------------------------- + +File#: _2_menu-bar +Title: Menu Bar +Descr: Application menu with a list of common actions that users can perform +Usage: codyhouse.co/license + +-------------------------------- */ + +:root { + --menu-bar-button-size: 2.5em; // size of the menu buttons + --menu-bar-icon-size: 1em; // size of the icons inside the buttons + --menu-bar-horizontal-gap: var(--space-xxs); // horizontal gap between buttons + --menu-bar-vertical-gap: 4px; // vertical gap between buttons and labels (tooltips) + --menu-bar-label-size: var(--text-xs); // label font size +} + +.menu-bar { + list-style: none; + display: inline-flex; + align-items: center; +} + +.menu-bar__item { // menu button + position: relative; + display: inline-block; // flex fallback + display: flex; + align-items: center; + justify-content: center; + height: var(--menu-bar-button-size); + width: var(--menu-bar-button-size); + border-radius: 50%; + cursor: pointer; + + &:not(:last-child) { + margin-right: var(--menu-bar-horizontal-gap); + } + + &:hover, + &.menu-control--active { + background-color: alpha(var(--color-contrast-higher), 0.1); + + > .menu-bar__icon { + color: var(--color-contrast-higher); + } + + > .menu-bar__label { // show label + clip: auto; + clip-path: none; + height: auto; + width: auto; + } + } + + &:focus { + outline: none; + background-color: alpha(var(--color-primary), 0.1); + } + + &:active { + background-color: var(--color-contrast-low); + } + + &:focus:active { + background-color: alpha(var(--color-primary), 0.2); + } +} + +.menu-bar__item--trigger { // button used to show hidden actions - visibile only if menu = collapsed + display: none; +} + +.menu-bar__icon { + display: block; + color: var(--color-contrast-high); + font-size: var(--menu-bar-icon-size); // set icon size +} + +.menu-bar__label { // label visible on :hover + // hide + position: absolute; + z-index: var(--z-index-popover, 5); + clip: rect(1px, 1px, 1px, 1px); + clip-path: inset(50%); + width: 1px; + height: 1px; + overflow: hidden; + white-space: nowrap; + // style + top: 100%; + left: 50%; + transform: translateX(-50%) translateY(var(--menu-bar-vertical-gap)); + padding: var(--space-xxs) var(--space-xs); + color: var(--color-bg); + background-color: alpha(var(--color-contrast-higher), 0.95); + border-radius: var(--radius-md); + font-size: var(--menu-bar-label-size); + @include fontSmooth; + pointer-events: none; + user-select: none; +} + +.menu-bar--collapsed { // mobile layout style + .menu-bar__item--hide { // hide buttons + display: none; + } + + .menu-bar__item--trigger { // show submenu trigger + display: inline-block; // flex fallback + display: flex; + } +} + +// detect when the menu needs to switch from the mobile layout to an expanded one - used in JS +.js { + .menu-bar { + opacity: 0; // hide menu bar while it is initialized in JS + + &::before { + display: none; + content: 'collapsed'; + } + } + + .menu-bar--loaded { + opacity: 1; + } + + @each $breakpoint, $value in $breakpoints { + .menu-bar--expanded\@#{$breakpoint}::before { + @include breakpoint(#{$breakpoint}) { + content: 'expanded'; + } + } + } +} diff --git a/old-apps/web-shared/src/styles/components/menu.scss b/old-apps/web-shared/src/styles/components/menu.scss new file mode 100644 index 0000000..8e211a5 --- /dev/null +++ b/old-apps/web-shared/src/styles/components/menu.scss @@ -0,0 +1,81 @@ +@use '../base' as *; + +/* -------------------------------- + +File#: _1_menu +Title: Menu +Descr: Application menu that provides access to a set of functionalities +Usage: codyhouse.co/license + +-------------------------------- */ + +.menu { + --menu-vertical-gap: 5px; // vertical gap between the Menu element and its control + --menu-item-padding: var(--space-xxxs) var(--space-xs); + list-style: none; + position: fixed; // top/left position set in JS + background-color: var(--color-bg-light); + //padding: var(--space-xxs) 0; + border-radius: var(--radius-md); + z-index: var(--z-index-popover, 5); + user-select: none; + margin-top: var(--menu-vertical-gap); + margin-bottom: var(--menu-vertical-gap); + overflow: auto; + + + // use rem units + @include spaceUnit(1rem); + @include textUnit(1rem); + + visibility: hidden; + opacity: 0; +} + +.menu--is-visible { + visibility: visible; + opacity: 1; +} + +.menu--overlay { + z-index: var(--z-index-overlay, 15); +} + +.menu__content { + display: block; // fallback + display: flex; + align-items: center; + text-decoration: none; // reset link style + padding: var(--menu-item-padding); + color: var(--color-contrast-high); + cursor: pointer; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + &:hover { + background-color: alpha(var(--color-contrast-higher), 0.075); + } + + &:focus { + outline: none; + background-color: alpha(var(--color-primary), 0.15); + } +} + +.menu__label { + padding: var(--menu-item-padding); + font-size: var(--text-sm); + color: var(--color-contrast-medium); +} + +.menu__separator { + height: 1px; + background-color: var(--color-contrast-lower); + margin: var(--menu-item-padding); +} + +.menu__icon { + color: alpha(var(--color-contrast-higher), 0.5); + margin-right: var(--space-xxs); +} diff --git a/old-apps/web-shared/src/styles/components/modal.scss b/old-apps/web-shared/src/styles/components/modal.scss new file mode 100644 index 0000000..1beec76 --- /dev/null +++ b/old-apps/web-shared/src/styles/components/modal.scss @@ -0,0 +1,105 @@ +@use '../base' as *; + +/* -------------------------------- + +File#: _1_modal-window +Title: Modal Window +Descr: A modal dialog used to display critical information +Usage: codyhouse.co/license + +-------------------------------- */ + +.modal { + position: fixed; + z-index: var(--z-index-overlay, 15); + width: 100%; + height: 100%; + left: 0; + top: 0; + opacity: 0; + visibility: hidden; + + &:not(.modal--is-visible) { + pointer-events: none; + background-color: transparent; + } +} + +.modal--is-visible { + opacity: 1; + visibility: visible; +} + +// close button +.modal__close-btn { + display: flex; + flex-shrink: 0; + border-radius: 50%; + cursor: pointer; + + .icon { + display: block; + margin: auto; + } +} + +.modal__close-btn--outer { // close button - outside the modal__content + width: 48px; + height: 48px; + position: fixed; + top: var(--space-sm); + right: var(--space-sm); + z-index: var(--z-index-fixed-element, 10); + background-color: alpha(var(--color-black), 0.9); + + .icon { + color: var(--color-white); // icon color + } + + &:hover { + background-color: alpha(var(--color-black), 1); + + .icon { + transform: scale(1.1); + } + } +} + +.modal__close-btn--inner { // close button - inside the modal__content + width: 2em; + height: 2em; + background-color: var(--color-bg-light); + box-shadow: var(--inner-glow), var(--shadow-sm); + + .icon { + color: inherit; // icon color + } + + &:hover { + background-color: var(--color-bg-lighter); + box-shadow: var(--inner-glow), var(--shadow-md); + } +} + +// load content - optional +.modal--is-loading { + .modal__content { + visibility: hidden; + } + + .modal__loader { + display: flex; + } +} + +.modal__loader { // loader icon + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + justify-content: center; + align-items: center; + display: none; + pointer-events: none; +} diff --git a/old-apps/web-shared/src/styles/components/pagination.scss b/old-apps/web-shared/src/styles/components/pagination.scss new file mode 100644 index 0000000..0a09210 --- /dev/null +++ b/old-apps/web-shared/src/styles/components/pagination.scss @@ -0,0 +1,77 @@ +@use '../base' as *; + +/* -------------------------------- + +File#: _1_pagination +Title: Pagination +Descr: Component used to navigate through pages of related content +Usage: codyhouse.co/license + +-------------------------------- */ + +.pagination {} + +.pagination__list > li { + display: inline-block; // flex fallback +} + +// --split - push first + last item to sides +.pagination--split { + .pagination__list { + width: 100%; + + > *:first-child { + margin-right: auto; + } + + > *:last-child { + margin-left: auto; + } + } +} + +.pagination__item { + display: inline-block; // flex fallback + display: inline-flex; + height: 100%; + align-items: center; + padding: var(--space-xs) calc(1.355 * var(--space-xs)); + + white-space: nowrap; + line-height: 1; + border-radius: var(--radius-md); + + text-decoration: none; + color: var(--color-contrast-high); + @include fontSmooth; + + will-change: transform; + + &:hover:not(.pagination__item--selected):not(.pagination__item--ellipsis) { + background-color: alpha(var(--color-contrast-higher), 0.1); + } +} + +.pagination__item--selected { + background-color: var(--color-contrast-higher); + color: var(--color-bg); + box-shadow: var(--shadow-sm); +} + +.pagination__item--disabled { + opacity: 0.5; + pointer-events: none; +} + +// --jumper +.pagination__jumper { + .form-control { + width: 3em; + margin-right: var(--space-xs); + } + + em { + flex-shrink: 0; + white-space: nowrap; + } +} diff --git a/old-apps/web-shared/src/styles/components/popover.scss b/old-apps/web-shared/src/styles/components/popover.scss new file mode 100644 index 0000000..7f423a0 --- /dev/null +++ b/old-apps/web-shared/src/styles/components/popover.scss @@ -0,0 +1,38 @@ +@use '../base'as *; + +/* -------------------------------- + +File#: _1_popover +Title: Popover +Descr: A pop-up box controlled by a trigger element +Usage: codyhouse.co/license + +-------------------------------- */ +:root { + --popover-width: 250px; + --popover-control-gap: 4px; // ⚠️ use px units - vertical gap between the popover and its control + --popover-viewport-gap: 20px; // ⚠️ use px units - vertical gap between the popover and the viewport - visible if popover height > viewport height + --popover-transition-duration: 0.2s; +} + +.popover { + position: fixed; // top/left position set in JS + width: var(--popover-width); + z-index: var(--z-index-popover, 5); + margin-top: var(--popover-control-gap); + margin-bottom: var(--popover-control-gap); + overflow: auto; + -webkit-overflow-scrolling: touch; + + visibility: hidden; + opacity: 0; +} + +.popover--is-visible { + visibility: visible; + opacity: 1; +} + +.popover-control--active { + // class added to the trigger when popover is visible +} diff --git a/old-apps/web-shared/src/styles/components/pre-header.scss b/old-apps/web-shared/src/styles/components/pre-header.scss new file mode 100644 index 0000000..1e803e7 --- /dev/null +++ b/old-apps/web-shared/src/styles/components/pre-header.scss @@ -0,0 +1,46 @@ +@use '../base' as *; + +/* -------------------------------- + +File#: _1_pre-header +Title: Pre-header +Descr: Pre-header (top) banner +Usage: codyhouse.co/license + +-------------------------------- */ + +.pre-header { + display: block; + background-color: var(--color-contrast-higher); + color: var(--color-bg); + @include fontSmooth; +} + +.pre-header--is-hidden { + display: none; +} + +.pre-header__close-btn { + position: absolute; + right: 0; + top: calc(50% - 0.5em); + will-change: transform; + + &:hover { + transform: scale(1.1); + } + + .icon { + display: block; + } +} + +// --link +a.pre-header { + text-decoration: none; + + &:hover { + text-decoration: underline; + background-color: var(--color-contrast-high); + } +} diff --git a/old-apps/web-shared/src/styles/components/radios-checkboxes.scss b/old-apps/web-shared/src/styles/components/radios-checkboxes.scss new file mode 100644 index 0000000..c4009f9 --- /dev/null +++ b/old-apps/web-shared/src/styles/components/radios-checkboxes.scss @@ -0,0 +1,134 @@ +@use '../base' as *; + +/* -------------------------------- + +File#: _1_radios-checkboxes +Title: Radios and Checkboxes +Descr: Custom radio and checkbox buttons +Usage: codyhouse.co/license + +-------------------------------- */ + +:root { + // radios and checkboxes + --checkbox-radio-size: 18px; + --checkbox-radio-gap: var(--space-xxs); // gap between button and label + --checkbox-radio-border-width: 1px; + --checkbox-radio-line-height: var(--body-line-height); + + // radio buttons + --radio-marker-size: 8px; + + // checkboxes + --checkbox-marker-size: 12px; + --checkbox-radius: 4px; +} + +// hide native buttons +.radio, +.checkbox { + position: absolute; + padding: 0; + margin: 0; + margin-top: calc((1em * var(--checkbox-radio-line-height) - var(--checkbox-radio-size)) / 2); + opacity: 0; + height: var(--checkbox-radio-size); + width: var(--checkbox-radio-size); + pointer-events: none; +} + +// label +.radio + label, +.checkbox + label { + display: inline-block; + line-height: var(--checkbox-radio-line-height); + user-select: none; + cursor: pointer; + padding-left: calc(var(--checkbox-radio-size) + var(--checkbox-radio-gap)); +} + +// custom inputs - basic style +.radio + label::before, +.checkbox + label::before { + content: ''; + box-sizing: border-box; + display: inline-block; + position: relative; + vertical-align: middle; + top: -0.1em; + margin-left: calc(-1 * (var(--checkbox-radio-size) + var(--checkbox-radio-gap))); + flex-shrink: 0; + width: var(--checkbox-radio-size); + height: var(--checkbox-radio-size); + background-color: var(--color-bg); + border-width: var(--checkbox-radio-border-width); + border-color: alpha(var(--color-contrast-low), 0.65); + border-style: solid; + box-shadow: var(--shadow-xs); + background-repeat: no-repeat; + background-position: center; + margin-right: var(--checkbox-radio-gap); +} + +// :hover +.radio:not(:checked):not(:focus) + label:hover::before, +.checkbox:not(:checked):not(:focus) + label:hover::before { + border-color: alpha(var(--color-contrast-low), 1); +} + +// radio only style +.radio + label::before { + border-radius: 50%; +} + +// checkbox only style +.checkbox + label::before { + border-radius: var(--checkbox-radius); +} + +// :checked +.radio:checked + label::before, +.checkbox:checked + label::before { + background-color: var(--color-primary); + box-shadow: var(--shadow-xs); + border-color: var(--color-primary); +} + +// radio button icon +.radio:checked + label::before { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cg class='nc-icon-wrapper' fill='%23ffffff'%3E%3Ccircle cx='8' cy='8' r='8' fill='%23ffffff'%3E%3C/circle%3E%3C/g%3E%3C/svg%3E"); + background-size: var(--radio-marker-size); +} + +// checkbox button icon +.checkbox:checked + label::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpolyline points='1 6.5 4 9.5 11 2.5' fill='none' stroke='%23FFFFFF' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'/%3E%3C/svg%3E"); + background-size: var(--checkbox-marker-size); +} + +// :focus +.radio:checked:active + label::before, +.checkbox:checked:active + label::before, +.radio:focus + label::before, +.checkbox:focus + label::before { + border-color: var(--color-primary); + box-shadow: 0 0 0 3px alpha(var(--color-primary), 0.2); +} + +// --radio--bg, --checkbox--bg -> variation with background color +.radio--bg + label, .checkbox--bg + label { + padding: var(--space-xxxxs) var(--space-xxxs); + padding-left: calc(var(--checkbox-radio-size) + var(--checkbox-radio-gap) + var(--space-xxxs)); + border-radius: var(--radius-md); +} + +.radio--bg + label:hover, .checkbox--bg + label:hover { + background-color: alpha(var(--color-contrast-higher), 0.075); +} + +.radio--bg:active + label, +.checkbox--bg:active + label, +.radio--bg:focus + label, +.checkbox--bg:focus + label { + background-color: alpha(var(--color-primary), 0.1); +} diff --git a/old-apps/web-shared/src/styles/components/responsive-sidebar.scss b/old-apps/web-shared/src/styles/components/responsive-sidebar.scss new file mode 100644 index 0000000..71c86da --- /dev/null +++ b/old-apps/web-shared/src/styles/components/responsive-sidebar.scss @@ -0,0 +1,139 @@ +@use '../base' as *; + +/* -------------------------------- + +File#: _1_responsive-sidebar +Title: Responsive Sidebar +Descr: Responsive sidebar container +Usage: codyhouse.co/license + +-------------------------------- */ + +/* mobile version only (--default) 👇 */ +.sidebar:not(.sidebar--static) { + position: fixed; + top: 0; + left: 0; + z-index: var(--z-index-fixed-element, 10); + width: 100%; + height: 100%; + visibility: hidden; + transition: visibility 0s 0.3s; + + &::after { /* overlay layer */ + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: alpha(var(--color-black), 0); + transition: background-color .3s; + z-index: 1; + } + + .sidebar__panel { /* content */ + position: absolute; + top: 0; + left: 0; + z-index: 2; + width: 100%; + max-width: 380px; + height: 100%; + overflow: auto; + -webkit-overflow-scrolling: touch; + background-color: var(--color-bg); + transform: translateX(-100%); + transition: box-shadow 0.3s,transform 0.3s; + } + + &.sidebar--right-on-mobile { + .sidebar__panel { + left: auto; + right: 0; + transform: translateX(100%); + } + } + + &.sidebar--is-visible { + visibility: visible; + transition: none; + + &::after { + background-color: alpha(var(--color-black), 0.85); + } + + .sidebar__panel { + transform: translateX(0); + box-shadow: var(--shadow-md); + } + } +} +/* end mobile version */ + +.sidebar__header { + display: flex; + align-items: center; + justify-content: space-between; + position: sticky; + top: 0; +} + +.sidebar__close-btn { + --size: 32px; + width: var(--size); + height: var(--size); + display: flex; + border-radius: 50%; + background-color: var(--color-bg-light); + box-shadow: var(--inner-glow), var(--shadow-sm); + transition: .2s; + flex-shrink: 0; + + .icon { + display: block; + margin: auto; + } + + &:hover { + background-color: var(--color-bg-lighter); + box-shadow: var(--inner-glow), var(--shadow-md); + } +} + +/* desktop version only (--static) 👇 */ +.sidebar--static { + flex-shrink: 0; + flex-grow: 1; + + .sidebar__header { + display: none; + } +} + +.sidebar--sticky-on-desktop { + position: sticky; + top: var(--space-sm); + max-height: calc(100vh - var(--space-sm)); + overflow: auto; + -webkit-overflow-scrolling: touch; +} +/* end desktop version */ + +.sidebar--loaded { + opacity: 1; +} + + +.sidebar--static::before { + content: 'static'; +} + +@each $breakpoint, $value in $breakpoints { + .sidebar--static\@#{$breakpoint}::before { + content: 'mobile'; + @include breakpoint(#{$breakpoint}) { + content: 'static'; + } + } +}
\ No newline at end of file diff --git a/old-apps/web-shared/src/styles/components/select-autocomplete.scss b/old-apps/web-shared/src/styles/components/select-autocomplete.scss new file mode 100644 index 0000000..d5360ff --- /dev/null +++ b/old-apps/web-shared/src/styles/components/select-autocomplete.scss @@ -0,0 +1,176 @@ +@use '../base' as *; +@use 'autocomplete.scss' as *; + +/* -------------------------------- + +File#: _3_select-autocomplete +Title: Select Autocomplete +Descr: Selection dropdown with autocomplete +Usage: codyhouse.co/license + +-------------------------------- */ + +.select-auto { + &.autocomplete { + --autocomplete-dropdown-vertical-gap: 4px; // gap between input and results list + --autocomplete-dropdown-max-height: 250px; + --autocomplete-dropdown-scrollbar-width: 6px; // custom scrollbar - webkit browsers + } +} + +// input +.select-auto__input-wrapper { + --input-btn-size: 1.25em; // btn/icon size + --input-btn-icon-size: 16px; // btn/icon size + --input-btn-text-gap: var(--space-xxs); // gap between button/icon and text + + position: relative; + background: var(--color-bg-dark); + line-height: 1.2; + box-shadow: inset 0 0 0 1px var(--color-contrast-lower); + + &.multiple { + display: flex; + flex-direction: row; + flex-flow: wrap; + + .chip { + white-space: nowrap; + margin-right: 1px; + } + + input[type="text"] { + width: auto; + } + + @media (max-width: 756px) { + flex-flow: column !important; + + &.has-selection { + input[type="text"] { + margin-top: 5px; + } + + .chip { + justify-content: space-between; + + .chip__btn { + margin-right: 0 !important;; + } + } + } + } + } + + &::placeholder { + opacity: 1; + color: var(--color-contrast-low); + } + + &:focus-within, + .focus { + background: var(--color-bg); + box-shadow: inset 0 0 0 1px alpha(var(--color-contrast-lower), 0), 0px 0px 0px 1px var(--color-primary); + outline: none; + } + + .form-control { + width: 100%; + height: 100%; + padding-right: calc(var(--form-control-padding-x) + var(--input-btn-size) + var(--input-btn-text-gap)); + } +} + + +.select-auto__input-icon-wrapper { + width: var(--input-btn-size); + height: var(--input-btn-size); + + position: absolute; + bottom: calc(var(--input-btn-size) / 3); + right: var(--form-control-padding-x); + display: flex; + pointer-events: none; + + .icon { + display: block; + margin: auto; + width: var(--input-btn-icon-size, 16px); + height: var(--input-btn-icon-size, 16px); + } +} + +.select-auto__input-btn { + display: none; + justify-content: center; + align-items: center; + width: inherit; + height: inherit; + pointer-events: auto; + cursor: pointer; + color: var(--color-contrast-medium); // icon color + + &:hover { + color: var(--color-contrast-high); + } +} + +.select-auto--selection-done { + .select-auto__input-icon-wrapper > .icon { + display: none; + } + + .select-auto__input-btn { + display: flex; + } +} + +// dropdown +.select-auto__results { + // reset spacing and typography + @include spaceUnit(1rem); + @include textUnit(1rem); +} + +// single result item +.select-auto__option { + position: relative; + cursor: pointer; + + &:hover { + background-color: alpha(var(--color-contrast-higher), 0.05); + } + + &:focus, + &.focus { + outline: none; + background-color: alpha(var(--color-primary), 0.12); + } + + &.select-auto__option--selected { + background-color: var(--color-primary); + color: var(--color-white); + padding-right: calc(1em + var(--space-sm)); + @include fontSmooth; + + &:focus, + .focus { + background-color: var(--color-primary-dark); + } + + &::after { + content: ''; + position: absolute; + right: var(--space-sm); + top: calc(50% - 0.5em); + height: 1em; + width: 1em; + background-color: currentColor; + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpolyline stroke-width='2' stroke='%23ffffff' fill='none' stroke-linecap='round' stroke-linejoin='round' points='1,9 5,13 15,3 '/%3E%3C/svg%3E"); + } + } +} + +.select-auto__group-title, .select-auto__no-results-msg { + outline: none; +} diff --git a/old-apps/web-shared/src/styles/components/side-navigation-v4.scss b/old-apps/web-shared/src/styles/components/side-navigation-v4.scss new file mode 100644 index 0000000..c2c13d2 --- /dev/null +++ b/old-apps/web-shared/src/styles/components/side-navigation-v4.scss @@ -0,0 +1,237 @@ +@use '../base' as *; +@use 'responsive-sidebar.scss' as *; + +/* -------------------------------- + +File#: _2_side-navigation-v4 +Title: Side Navigation v4 +Descr: Main, side navigation +Usage: codyhouse.co/license + +-------------------------------- */ + +.sidenav-v4 { + --sidenav-v4-icon-size: 20px; + --sidenav-v4-icon-margin-right: var(--space-xxs); +} + +.sidenav-v4__item { + position: relative; +} + +.sidenav-v4__link, +.sidenav-v4__sub-link, +.sidenav-v4__separator { + padding: var(--space-sm); +} + +.sidenav-v4__link, .sidenav-v4__sub-link { + display: flex; + align-items: center; + + width: 100%; + border-radius: var(--radius-md); + + text-decoration: none; + color: inherit; + line-height: 1; + font-size: var(--text-md); + + transition: .2s; + + &:hover { + color: var(--color-primary); + background-color: alpha(var(--color-contrast-higher), 0.075); + } + + &[aria-current="page"] { + color: var(--color-primary); + } +} + +.sidenav-v4__sub-link { + position: relative; + color: var(--color-contrast-medium); + + /* dot indicator */ + &::before { + content: ''; + display: block; + --size: 6px; + width: var(--size); + height: var(--size); + background: currentColor; + border-radius: 50%; + margin-left: calc(var(--sidenav-v4-icon-size)/2 - var(--size)/2); + margin-right: calc(var(--sidenav-v4-icon-size)/2 - var(--size)/2 + var(--sidenav-v4-icon-margin-right)); + + opacity: 0; /* visible only if current */ + } + + &[aria-current="page"] { + &::before { /* show dot indicator */ + opacity: 1; + } + } +} + +.sidenav-v4__notification-marker { + margin-left: auto; + background-color: var(--color-accent); + border-radius: var(--radius-md); + + height: 16px; + line-height: 16px; + padding: 0 4px; + color: var(--color-white); + font-size: 12px; + + /* hide - visible only on desktop */ + display: none; +} + +/* label icon */ +.sidenav-v4__icon { + --size: var(--sidenav-v4-icon-size); + margin-right: var(--sidenav-v4-icon-margin-right); +} + +/* arrow icon - visible on mobile if item is expandable */ +.sidenav-v4__arrow-icon { + --size: 20px; + + .icon__group { + will-change: transform; + transform-origin: 50% 50%; + transform: rotate(-90deg); + transition: transform .3s var(--ease-out); + + > * { + transform-origin: 50% 50%; + stroke-dasharray: 20; + stroke-dashoffset: 0; + transform: translateY(0px); + transition: transform .3s, stroke-dashoffset .3s; + transition-timing-function: var(--ease-out); + } + + .sidenav-v4__item--collapsed & { + transform: rotate(0deg); + + > * { + transform: translateY(4px); + } + + > *:first-child { + stroke-dashoffset: 10.15; + } + + > *:last-child { + stroke-dashoffset: 10.15; + } + } + } + + /* hide icon for links - show only for buttons created in JS */ + .sidenav-v4__link--href & { + display: none; + } +} + +/* current item */ +.sidenav-v4__item--current { + .sidenav-v4__sub-list { + display: block; /* show sublist */ + } +} + +/* separator */ +.sidenav-v4__separator { + span { + display: block; + width: var(--sidenav-v4-icon-size); + height: 1px; + background-color: var(--color-contrast-lower); + } +} + +/* mobile only */ +@include breakpoint(md, "not all") { + .sidenav-v4__item--collapsed { + .sidenav-v4__sub-list { + display: none; + } + } + + .sidenav-v4__link--href { + display: none; /* hide link -> show button */ + } +} + +/* desktop */ +@include breakpoint(md) { + .sidenav-v4__sub-list { + display: none; + } + + .sidenav-v4__link, + .sidenav-v4__sub-link, + .sidenav-v4__separator { + padding: var(--space-xs); + } + + .sidenav-v4__link, + .sidenav-v4__sub-link { + font-size: var(--text-sm); + } + + .sidenav-v4__link--btn { + display: none; /* hide button -> show link */ + } + + /* tooltip */ + .sidenav-v4__item:not(.sidenav-v4__item--current) { + .sidenav-v4__sub-list { + width: 220px; + position: absolute; + z-index: var(--z-index-overlay); + left: 100%; + top: 0; + + background-color: var(--color-bg-light); + box-shadow: var(--inner-glow), var(--shadow-md); + border-radius: var(--radius-md); + + overflow: hidden; + } + + .sidenav-v4__sub-link { + border-radius: 0; + color: var(--color-contrast-high); + + &::before { + display: none; /* remove dot indicator */ + } + + &:hover { + color: var(--color-primary); + } + } + + &.sidenav-v4__item--hover, &:focus-within { + .sidenav-v4__sub-list { + display: block; + } + } + + &:hover .sidenav-v4__link { /* highlight main link if tooltip is visible */ + color: var(--color-primary); + background-color: alpha(var(--color-contrast-higher), 0.075); + } + } + + /* notification marker */ + .sidenav-v4__notification-marker { + display: block; + } +}
\ No newline at end of file diff --git a/old-apps/web-shared/src/styles/components/tabbed-navigation.scss b/old-apps/web-shared/src/styles/components/tabbed-navigation.scss new file mode 100644 index 0000000..4090fca --- /dev/null +++ b/old-apps/web-shared/src/styles/components/tabbed-navigation.scss @@ -0,0 +1,133 @@ +@use '../base' as *; + +/* -------------------------------- + +File#: _1_tabbed-navigation-v2 +Title: Tabbed Navigation v2 +Descr: Tabbed (secondary) navigation +Usage: codyhouse.co/license + +-------------------------------- */ + +.tabs-nav-v2 { + display: flex; + flex-wrap: wrap; + + .tab-v2 { + display: inline-block; // flexbox fallback + display: inline-flex; + align-items: center; + } +} + +.tabs-nav-v2__item { + display: inline-block; + padding: var(--space-xxs) var(--space-sm); + color: inherit; + white-space: nowrap; + text-decoration: none; +} + +.tabs-nav-v2__item--selected, +.tabs-nav-v2__item[aria-selected="true"] { + color: var(--color-bg); + background-color: var(--color-contrast-higher); +} + +@include breakpoint(md) { + .tabs-nav-v2 { + .tab-v2 { + margin: 0; + } + } + + .tabs-nav-v2__item { + background-color: transparent; + margin: var(--space-xxs) var(--space-sm); + padding: var(--space-xxxs) var(--space-xxs) !important; + border-radius: var(--radius-md); + + &:hover { + background-color: alpha(var(--color-primary), 0.035); + color: var(--color-primary); + } + } + + .tabs-nav-v2__item--selected, + .tabs-nav-v2__item[aria-selected="true"] { + + background-color: alpha(var(--color-primary), 0.075); + color: var(--color-primary); + position: relative; + + &::after { + content: ''; + position: absolute; + bottom: calc(var(--tabs-nav-border-width) * -1); + left: 0; + width: 100%; + height: var(--tabs-nav-border-width); + background-color: var(--color-bg); + } + + &:hover { + background-color: alpha(var(--color-primary), 0.075); + } + } +} + +:root { + --s-tabs-border-bottom-width: 1px; + --s-tabs-selected-item-border-bottom-width: 1px; +} + +.s-tabs { + position: relative; + + &::after { /* gradient - truncate text */ + content: ''; + position: absolute; + right: -1px; + top: 0; + height: calc(100% - var(--s-tabs-border-bottom-width)); + width: 2em; + pointer-events: none; + z-index: 1; + } +} + +.s-tabs__list { + display: flex; + overflow: auto; + -webkit-overflow-scrolling: auto; + + &::after { /* border bottom */ + content: ''; + position: absolute; + width: 100%; + height: var(--s-tabs-border-bottom-width); + left: 0; + bottom: 0; + background-color: var(--color-contrast-lower); + } +} + +.s-tabs__link { + color: var(--color-contrast-medium); + text-decoration: none; + display: inline-block; + padding: var(--space-xs) var(--space-sm); + white-space: nowrap; + border-bottom: var(--s-tabs-selected-item-border-bottom-width) solid transparent; + z-index: 1; + + &:hover:not(.s-tabs__link--current) { + color: var(--color-contrast-high); + } +} + +.s-tabs__link--current { + position: relative; + color: var(--color-primary); + border-bottom-color: var(--color-primary); +} diff --git a/old-apps/web-shared/src/styles/components/table.scss b/old-apps/web-shared/src/styles/components/table.scss new file mode 100644 index 0000000..af8207f --- /dev/null +++ b/old-apps/web-shared/src/styles/components/table.scss @@ -0,0 +1,147 @@ +@use '../base'as *; + +/* -------------------------------- + +File#: _1_table +Title: Table +Descr: Data tables used to organize and display information in rows and columns +Usage: codyhouse.co/license + +-------------------------------- */ + +// >>> style affecting all (block + expanded) versions 👇 +.table { + position: relative; + z-index: 1; +} + +// <<< end style affecting all versions + +// >>> block version only (mobile) 👇 +.table:not(.table--expanded) { + border-collapse: separate; + border-spacing: 0 var(--space-md); // row gap + margin-top: calc(-2 * var(--space-md)); // set spacing variable = row gap ☝️ + + .table__header { + // hide table header - but keep it accessible to SR + @include srHide; + } + + .table__row { + .table__cell:first-child { + border-radius: var(--radius-md) var(--radius-md) 0 0; + } + + .table__cell:last-child { + border-radius: 0 0 var(--radius-md) var(--radius-md); + + &::after { + // hide border bottom + display: none; + } + } + } + + .table__cell { + position: relative; + display: flex; + justify-content: space-between; + width: 100%; + text-align: right; + padding: var(--space-md); + background-color: var(--color-bg-light); + + &::after { + // border bottom + content: ''; + position: absolute; + bottom: 0; + left: var(--space-md); + width: calc(100% - (2 * var(--space-md))); + height: 1px; + background-color: var(--color-contrast-lower); + } + } + + .table__label { + // inline labels -> visible when table header is hidden + font-weight: bold; + text-align: left; + color: var(--color-contrast-higher); + margin-right: var(--space-md); + } +} + +// <<< end block version + +// >>> expanded version only (desktop) 👇 -> show standard rows and cols +.table--expanded { + border-bottom: 1px solid var(--color-contrast-lower); // table border bottom + + .table__header { + .table__cell { + // header cell style + position: relative; + z-index: 10; + background-color: var(--color-bg); + border-bottom: 1px solid var(--color-contrast-lower); // header border bottom + font-weight: bold; + color: var(--color-contrast-higher); + } + } + + .table__body { + .table__row { + &:nth-child(odd) { + background-color: alpha(var(--color-bg-dark), 0.85); + } + } + } + + .table__cell { + padding: var(--space-xxxs); + } + + .table__label { + // hide inline labels + display: none; + } + + // --header-sticky + .table__header--sticky { + .table__cell { + // header cell style + position: sticky; + top: 0; + } + } +} + +// <<< end expanded version + +.js { + .table { + opacity: 0; // hide table while it is initialized in JS + } + + .table--loaded { + opacity: 1; + } +} + +// detect when the table needs to switch from the mobile layout to an expanded one - used in JS +[class*="table--expanded"]::before { + display: none; +} + +@each $breakpoint, +$value in $breakpoints { + .table--expanded\@#{$breakpoint}::before { + content: 'collapsed'; + + @include breakpoint(#{$breakpoint}) { + content: 'expanded'; + } + } +} diff --git a/old-apps/web-shared/src/styles/components/user-menu.scss b/old-apps/web-shared/src/styles/components/user-menu.scss new file mode 100644 index 0000000..416655f --- /dev/null +++ b/old-apps/web-shared/src/styles/components/user-menu.scss @@ -0,0 +1,81 @@ +@use '../base' as *; +@use 'menu.scss' as *; + +/* -------------------------------- + +File#: _2_user-menu +Title: User Menu +Descr: A menu controlled by the user profile image +Usage: codyhouse.co/license + +-------------------------------- */ + +.user-menu-control { + --profile-figure-size: 40px; + + cursor: pointer; + display: inline-flex; + align-items: center; + text-align: left; + + &:hover { + .user-menu-control__img-wrapper { + opacity: 0.8; + } + + .user-menu__meta-title { + color: var(--color-primary); + } + } + + &:focus, &.menu-control--active { + outline: none; + + .user-menu-control__img-wrapper::after { + opacity: 1; + transform: scale(1); + } + } +} + +.user-menu-control__img-wrapper { + width: var(--profile-figure-size); + height: var(--profile-figure-size); + position: relative; + //transition: opacity 0.2s; + + &::after { + content: ''; + position: absolute; + z-index: -1; + left: -4px; + top: -4px; + width: 100%; + height: 100%; + border-radius: inherit; + width: calc(var(--profile-figure-size) + 8px); + height: calc(var(--profile-figure-size) + 8px); + border: 2px solid var(--color-primary); + pointer-events: none; + + opacity: 0; + transform: scale(0.8); + + //transition: all 0.2s; + } +} + +.user-menu-control__img { + display: block; + width: 100%; + object-fit: cover; + border-radius: inherit; +} + +.user-menu__meta { + //max-width: 100px; +} + +.user-menu__meta-title { + //transition: color 0.2s; +} diff --git a/old-apps/web-shared/src/styles/components/vanilla-responsive-sidebar.scss b/old-apps/web-shared/src/styles/components/vanilla-responsive-sidebar.scss new file mode 100644 index 0000000..735cc1e --- /dev/null +++ b/old-apps/web-shared/src/styles/components/vanilla-responsive-sidebar.scss @@ -0,0 +1,146 @@ +@use '../base' as *; + +/* -------------------------------- + +File#: _1_responsive-sidebar +Title: Responsive Sidebar +Descr: Responsive sidebar container +Usage: codyhouse.co/license + +-------------------------------- */ + +/* mobile version only (--default) 👇 */ +.sidebar:not(.sidebar--static) { + position: fixed; + top: 0; + left: 0; + z-index: var(--z-index-fixed-element, 10); + width: 100%; + height: 100%; + visibility: hidden; + transition: visibility 0s 0.3s; + + &::after { /* overlay layer */ + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: alpha(var(--color-black), 0); + transition: background-color .3s; + z-index: 1; + } + + .sidebar__panel { /* content */ + position: absolute; + top: 0; + left: 0; + z-index: 2; + width: 100%; + max-width: 380px; + height: 100%; + overflow: auto; + -webkit-overflow-scrolling: touch; + background-color: var(--color-bg); + transform: translateX(-100%); + transition: box-shadow 0.3s, transform 0.3s; + } + + &.sidebar--right-on-mobile { + .sidebar__panel { + left: auto; + right: 0; + transform: translateX(100%); + } + } + + &.sidebar--is-visible { + visibility: visible; + transition: none; + + &::after { + background-color: alpha(var(--color-black), 0.85); + } + + .sidebar__panel { + transform: translateX(0); + box-shadow: var(--shadow-md); + } + } +} + +/* end mobile version */ + +.sidebar__header { + display: flex; + align-items: center; + justify-content: space-between; + position: sticky; + top: 0; +} + +.sidebar__close-btn { + --size: 32px; + width: var(--size); + height: var(--size); + display: flex; + border-radius: 50%; + background-color: var(--color-bg-light); + box-shadow: var(--inner-glow), var(--shadow-sm); + transition: .2s; + flex-shrink: 0; + + .icon { + display: block; + margin: auto; + } + + &:hover { + background-color: var(--color-bg-lighter); + box-shadow: var(--inner-glow), var(--shadow-md); + } +} + +.sidebar__footer { + background-color: var(--color-bg); +} + +/* desktop version only (--static) 👇 */ +.sidebar--static { + flex-shrink: 0; + flex-grow: 1; + width: 100%; + max-width: 320px; + + .sidebar__header { + display: none; + } + + .sidebar__footer { + background-color: var(--color-bg-dark); + } +} + +.sidebar--sticky-on-desktop { + position: sticky; + top: var(--space-sm); + max-height: calc(100vh - var(--space-sm)); + overflow: auto; + -webkit-overflow-scrolling: touch; +} + +/* end desktop version */ + +.sidebar--static::before { + content: 'static'; +} + +@each $breakpoint, $value in $breakpoints { + .sidebar--static\@#{$breakpoint}::before { + content: 'mobile'; + @include breakpoint(#{$breakpoint}) { + content: 'static'; + } + } +} diff --git a/old-apps/web-shared/src/styles/custom-style/_buttons.scss b/old-apps/web-shared/src/styles/custom-style/_buttons.scss new file mode 100644 index 0000000..2145acc --- /dev/null +++ b/old-apps/web-shared/src/styles/custom-style/_buttons.scss @@ -0,0 +1,89 @@ +@use '../base' as *; + +// -------------------------------- + +// (START) Global editor code https://codyhouse.co/ds/globals/buttons + +// -------------------------------- + +:root { + --btn-font-size: 1rem; + --btn-padding-x: var(--space-xs); + --btn-padding-y: var(--space-xxxs); + --btn-radius: 5px; +} + +.btn { + background: var(--color-bg-dark); + color: var(--color-contrast-higher); + cursor: pointer; + font-family: var(--font-primary); + font-weight: 400; + text-decoration: none; + line-height: 0.8; + @include fontSmooth; + outline: none; + + &:hover { + background: var(--color-bg-darker); + } + + &:focus { + outline: 1px solid var(--color-contrast-lower); + } +} + +// themes +.btn--primary { + background: var(--color-primary); + color: var(--color-white); + + &:hover { + background: var(--color-primary-light); + box-shadow: inset 0px 1px 0px alpha(var(--color-white), 0.15), 0px 1px 2px alpha(var(--color-primary-darker), 0.25), 0px 1px 4px alpha(var(--color-primary-darker), 0.1), 0px 3px 6px -2px alpha(var(--color-primary-darker), 0.25); + } +} + +.btn--subtle { + background: var(--color-bg-lighter); + color: var(--color-contrast-higher); + + &:hover { + background: var(--color-bg-light); + } +} + +.btn--accent { + background: var(--color-accent); + color: var(--color-white); + + &:hover { + background: var(--color-accent-light); + box-shadow: inset 0px 1px 0px alpha(var(--color-white), 0.15), 0px 1px 2px alpha(var(--color-accent-darker), 0.25), 0px 1px 4px alpha(var(--color-accent-darker), 0.1), 0px 3px 6px -2px alpha(var(--color-accent-darker), 0.1); + } +} + +// feedback +.btn--disabled, .btn[disabled], .btn[readonly] { + opacity: 0.6; + cursor: not-allowed; +} + +// size +.btn--sm { + font-size: 0.8rem; +} + +.btn--md { + font-size: 1.2rem; +} + +.btn--lg { + font-size: 1.4rem; +} + +// -------------------------------- + +// (END) Global editor code + +// --------------------------------
\ No newline at end of file diff --git a/old-apps/web-shared/src/styles/custom-style/_colors.scss b/old-apps/web-shared/src/styles/custom-style/_colors.scss new file mode 100644 index 0000000..97b620f --- /dev/null +++ b/old-apps/web-shared/src/styles/custom-style/_colors.scss @@ -0,0 +1,119 @@ +@use '../base' as *; + +// -------------------------------- + +// (START) Global editor code https://codyhouse.co/ds/globals/colors + +// -------------------------------- + +:root, [data-theme="default"] { + // main + @include defineColorHSL(--color-primary-darker, 210, 92%, 32%); + @include defineColorHSL(--color-primary-dark, 210, 92%, 38%); + @include defineColorHSL(--color-primary, 210, 92%, 44%); + @include defineColorHSL(--color-primary-light, 210, 92%, 50%); + @include defineColorHSL(--color-primary-lighter, 210, 92%, 56%); + + @include defineColorHSL(--color-accent-darker, 350, 47%, 30%); + @include defineColorHSL(--color-accent-dark, 350, 47%, 36%); + @include defineColorHSL(--color-accent, 350, 47%, 42%); + @include defineColorHSL(--color-accent-light, 350, 47%, 48%); + @include defineColorHSL(--color-accent-lighter, 350, 47%, 54%); + + @include defineColorHSL(--color-black, 0, 0%, 10%); + @include defineColorHSL(--color-white, 0, 0%, 100%); + + // feedback + @include defineColorHSL(--color-warning-darker, 25, 75%, 60%); + @include defineColorHSL(--color-warning-dark, 25, 75%, 66%); + @include defineColorHSL(--color-warning, 25, 75%, 72%); + @include defineColorHSL(--color-warning-light, 25, 75%, 78%); + @include defineColorHSL(--color-warning-lighter, 25, 75%, 84%); + + @include defineColorHSL(--color-success-darker, 143, 45%, 59%); + @include defineColorHSL(--color-success-dark, 143, 45%, 65%); + @include defineColorHSL(--color-success, 143, 45%, 71%); + @include defineColorHSL(--color-success-light, 143, 45%, 77%); + @include defineColorHSL(--color-success-lighter, 143, 45%, 83%); + + @include defineColorHSL(--color-error-darker, 342, 89%, 38%); + @include defineColorHSL(--color-error-dark, 342, 89%, 43%); + @include defineColorHSL(--color-error, 342, 89%, 48%); + @include defineColorHSL(--color-error-light, 342, 89%, 56%); + @include defineColorHSL(--color-error-lighter, 342, 89%, 62%); + + // background + @include defineColorHSL(--color-bg-darker, 240, 2%, 90%); + @include defineColorHSL(--color-bg-dark, 240, 3%, 94%); + @include defineColorHSL(--color-bg, 0, 0%, 100%); + @include defineColorHSL(--color-bg-light, 240, 3%, 100%); + @include defineColorHSL(--color-bg-lighter, 240, 2%, 100%); + + // color contrasts + @include defineColorHSL(--color-contrast-lower, 240, 1%, 85%); + @include defineColorHSL(--color-contrast-low, 240, 2%, 66%); + @include defineColorHSL(--color-contrast-medium, 230, 2%, 48%); + @include defineColorHSL(--color-contrast-high, 223, 6%, 25%); + @include defineColorHSL(--color-contrast-higher, 225, 14%, 11%); +} + +[data-theme="dark"] { + // main + @include defineColorHSL(--color-primary-darker, 250, 100%, 60%); + @include defineColorHSL(--color-primary-dark, 250, 100%, 64%); + @include defineColorHSL(--color-primary, 250, 100%, 69%); + @include defineColorHSL(--color-primary-light, 250, 100%, 72%); + @include defineColorHSL(--color-primary-lighter, 250, 100%, 76%); + + @include defineColorHSL(--color-accent-darker, 342, 92%, 41%); + @include defineColorHSL(--color-accent-dark, 342, 92%, 47%); + @include defineColorHSL(--color-accent, 342, 92%, 54%); + @include defineColorHSL(--color-accent-light, 342, 92%, 60%); + @include defineColorHSL(--color-accent-lighter, 342, 92%, 65%); + + @include defineColorHSL(--color-black, 230, 13%, 9%); + @include defineColorHSL(--color-white, 0, 0%, 100%); + + // feedback + @include defineColorHSL(--color-warning-darker, 35, 79%, 48%); + @include defineColorHSL(--color-warning-dark, 35, 79%, 56%); + @include defineColorHSL(--color-warning, 35, 79%, 66%); + @include defineColorHSL(--color-warning-light, 35, 79%, 74%); + @include defineColorHSL(--color-warning-lighter, 35, 79%, 82%); + + @include defineColorHSL(--color-success-darker, 170, 78%, 26%); + @include defineColorHSL(--color-success-dark, 170, 78%, 31%); + @include defineColorHSL(--color-success, 170, 78%, 36%); + @include defineColorHSL(--color-success-light, 170, 78%, 42%); + @include defineColorHSL(--color-success-lighter, 170, 78%, 47%); + + @include defineColorHSL(--color-error-darker, 342, 92%, 41%); + @include defineColorHSL(--color-error-dark, 342, 92%, 47%); + @include defineColorHSL(--color-error, 342, 92%, 54%); + @include defineColorHSL(--color-error-light, 342, 92%, 60%); + @include defineColorHSL(--color-error-lighter, 342, 92%, 65%); + + // background + @include defineColorHSL(--color-bg-darker, 232, 7%, 8%); + @include defineColorHSL(--color-bg-dark, 233, 8%, 11%); + @include defineColorHSL(--color-bg, 232, 11%, 15%); + @include defineColorHSL(--color-bg-light, 233, 8%, 19%); + @include defineColorHSL(--color-bg-lighter, 232, 7%, 22%); + + // color contrasts + @include defineColorHSL(--color-contrast-lower, 240, 6%, 26%); + @include defineColorHSL(--color-contrast-low, 240, 3%, 41%); + @include defineColorHSL(--color-contrast-medium, 213, 3%, 57%); + @include defineColorHSL(--color-contrast-high, 240, 5%, 82%); + @include defineColorHSL(--color-contrast-higher, 240, 100%, 99%); + + // font rendering + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +// -------------------------------- + +// (END) Global editor code + +// --------------------------------
\ No newline at end of file diff --git a/old-apps/web-shared/src/styles/custom-style/_forms.scss b/old-apps/web-shared/src/styles/custom-style/_forms.scss new file mode 100644 index 0000000..871e405 --- /dev/null +++ b/old-apps/web-shared/src/styles/custom-style/_forms.scss @@ -0,0 +1,58 @@ +@use '../base' as *; + +// -------------------------------- + +// (START) Global editor code https://codyhouse.co/ds/globals/forms + +// -------------------------------- + +:root { + --form-control-font-size: 1rem; + --form-control-padding-x: var(--space-xs); + --form-control-padding-y: var(--space-xxs); + --form-control-radius: 3px; +} + +.form-control { + background: var(--color-bg-dark); + cursor: auto; + font-family: var(--font-primary); + font-weight: 400; + line-height: 1; + @include fontSmooth; + outline: 1px solid var(--color-contrast-high); + + &::placeholder { + opacity: 1; + color: var(--color-contrast-low); + } + + &:focus, &:focus-within { + background: var(--color-bg); + box-shadow: inset 0px 0px 0px 1px alpha(var(--color-contrast-lower), 0), 0px 0px 0px 2px var(--color-primary), 0px 1px 3px alpha(var(--color-black), 0.2); + outline: none; + } +} + +.form-control--disabled, .form-control[disabled], .form-control[readonly] { + opacity: 0.3; + cursor: not-allowed; +} + +.form-control[aria-invalid="true"], .form-control.form-control--error { + outline: 1px solid var(--color-error-dark); + + &:focus, &:focus-within { + box-shadow: inset 0px 0px 0px 1px alpha(var(--color-contrast-lower), 0), 0px 0px 0px 2px var(--color-error), 0px 1px 3px var(--color-error); + } +} + +.form-legend {} + +.form-label {} + +// -------------------------------- + +// (END) Global editor code + +// --------------------------------
\ No newline at end of file diff --git a/old-apps/web-shared/src/styles/custom-style/_icons.scss b/old-apps/web-shared/src/styles/custom-style/_icons.scss new file mode 100644 index 0000000..a9fcb46 --- /dev/null +++ b/old-apps/web-shared/src/styles/custom-style/_icons.scss @@ -0,0 +1,19 @@ +@use '../base' as *; + +:root { + // size - 👇 uncomment to modify default icon sizes + // --icon-xxxs: 8px; + // --icon-xxs: 12px; + // --icon-xs: 16px; + // --icon-sm: 24px; + // --icon-md: 32px; + // --icon-lg: 48px; + // --icon-xl: 64px; + // --icon-xxl: 96px; + // --icon-xxxl: 128px; +} + +.icon { + // 👇 include the font-family declaration here if you are using an icon font + // font-family: 'fontName'; +}
\ No newline at end of file diff --git a/old-apps/web-shared/src/styles/custom-style/_shared-styles.scss b/old-apps/web-shared/src/styles/custom-style/_shared-styles.scss new file mode 100644 index 0000000..313fc69 --- /dev/null +++ b/old-apps/web-shared/src/styles/custom-style/_shared-styles.scss @@ -0,0 +1,70 @@ +@use '../base' as *; + +:root { + // radius + --radius: 0.375em; // border radius base size + // 👇 uncomment to modify default radius values + // --radius-sm: calc(var(--radius)/2); + // --radius-md: var(--radius); + // --radius-lg: calc(var(--radius)*2); + + // box shadow - 👇 uncomment to modify default shadow values + // --shadow-xs: 0 0.1px 0.3px rgba(0, 0, 0, 0.06), + // 0 1px 2px rgba(0, 0, 0, 0.12); + // --shadow-sm: 0 0.3px 0.4px rgba(0, 0, 0, 0.025), + // 0 0.9px 1.5px rgba(0, 0, 0, 0.05), + // 0 3.5px 6px rgba(0, 0, 0, 0.1); + // --shadow-md: 0 0.9px 1.5px rgba(0, 0, 0, 0.03), + // 0 3.1px 5.5px rgba(0, 0, 0, 0.08), + // 0 14px 25px rgba(0, 0, 0, 0.12); + // --shadow-lg: 0 1.2px 1.9px -1px rgba(0, 0, 0, 0.014), + // 0 3.3px 5.3px -1px rgba(0, 0, 0, 0.038), + // 0 8.5px 12.7px -1px rgba(0, 0, 0, 0.085), + // 0 30px 42px -1px rgba(0, 0, 0, 0.15); + // --shadow-xl: 0 1.5px 2.1px -6px rgba(0, 0, 0, 0.012), + // 0 3.6px 5.2px -6px rgba(0, 0, 0, 0.035), + // 0 7.3px 10.6px -6px rgba(0, 0, 0, 0.07), + // 0 16.2px 21.9px -6px rgba(0, 0, 0, 0.117), + // 0 46px 60px -6px rgba(0, 0, 0, 0.2); + // --inner-glow: inset 0 0 0.5px 1px hsla(0, 0%, 100%, 0.075); + // --inner-glow-top: inset 0 1px 0.5px hsla(0, 0%, 100%, 0.075); +} + +// -------------------------------- + +// (START) Global editor code https://codyhouse.co/ds/globals/shared-styles + +// -------------------------------- + +.hover\:reduce-opacity { + opacity: 1; + + &:hover { + opacity: 0.8; + } +} + +.hover\:elevate { + box-shadow: var(--shadow-sm); + + &:hover { + box-shadow: var(--shadow-md); + } +} + +// text styles +.link-subtle { + color: inherit; + cursor: pointer; + text-decoration: none; + + &:hover { + color: var(--color-primary); + } +} + +// -------------------------------- + +// (END) Global editor code + +// --------------------------------
\ No newline at end of file diff --git a/old-apps/web-shared/src/styles/custom-style/_spacing.scss b/old-apps/web-shared/src/styles/custom-style/_spacing.scss new file mode 100644 index 0000000..d78d897 --- /dev/null +++ b/old-apps/web-shared/src/styles/custom-style/_spacing.scss @@ -0,0 +1,49 @@ +@use '../base' as *; + +// -------------------------------- + +// (START) Global editor code https://codyhouse.co/ds/globals/spacing + +// -------------------------------- + +// 👇 uncomment to modify default spacing scale +// :root { +// --space-unit: 1rem; +// } + +// :root, * { +// --space-xxxxs: calc(0.125 * var(--space-unit)); +// --space-xxxs: calc(0.25 * var(--space-unit)); +// --space-xxs: calc(0.375 * var(--space-unit)); +// --space-xs: calc(0.5 * var(--space-unit)); +// --space-sm: calc(0.75 * var(--space-unit)); +// --space-md: calc(1.25 * var(--space-unit)); +// --space-lg: calc(2 * var(--space-unit)); +// --space-xl: calc(3.25 * var(--space-unit)); +// --space-xxl: calc(5.25 * var(--space-unit)); +// --space-xxxl: calc(8.5 * var(--space-unit)); +// --space-xxxxl: calc(13.75 * var(--space-unit)); +// --component-padding: var(--space-md); +// } + +@include breakpoint(md) { + :root, * { + --space-xxxxs: calc(0.1875 * var(--space-unit)); + --space-xxxs: calc(0.375 * var(--space-unit)); + --space-xxs: calc(0.5625 * var(--space-unit)); + --space-xs: calc(0.75 * var(--space-unit)); + --space-sm: calc(1.125 * var(--space-unit)); + --space-md: calc(2 * var(--space-unit)); + --space-lg: calc(3.125 * var(--space-unit)); + --space-xl: calc(5.125 * var(--space-unit)); + --space-xxl: calc(8.25 * var(--space-unit)); + --space-xxxl: calc(13.25 * var(--space-unit)); + --space-xxxxl: calc(21.5 * var(--space-unit)); + } +} + +// -------------------------------- + +// (END) Global editor code + +// --------------------------------
\ No newline at end of file diff --git a/old-apps/web-shared/src/styles/custom-style/_typography.scss b/old-apps/web-shared/src/styles/custom-style/_typography.scss new file mode 100644 index 0000000..7fa98c5 --- /dev/null +++ b/old-apps/web-shared/src/styles/custom-style/_typography.scss @@ -0,0 +1,95 @@ +@use '../base' as *; + +// -------------------------------- + +// (START) Global editor code https://codyhouse.co/ds/globals/typography + +// -------------------------------- + +:root { + // font family + --font-primary: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + + // font size + --text-base-size: 1rem; // body font-size + --text-scale-ratio: 1.2; // multiplier used to generate the type scale values 👇 + + // line-height + --body-line-height: 1; + --heading-line-height: 1; + + // capital letters - used in combo with the lhCrop mixin + --font-primary-capital-letter: 1; + + // unit - don't modify unless you want to change the typography unit (e.g., from Rem to Em units) + --text-unit: var(--text-base-size); // if Em units → --text-unit: 1em; +} + +:root, * { + // type scale + --text-xs: calc((var(--text-unit) / var(--text-scale-ratio)) / var(--text-scale-ratio)); + --text-sm: calc(var(--text-xs) * var(--text-scale-ratio)); + --text-md: calc(var(--text-sm) * var(--text-scale-ratio) * var(--text-scale-ratio)); + --text-lg: calc(var(--text-md) * var(--text-scale-ratio)); + --text-xl: calc(var(--text-lg) * var(--text-scale-ratio)); + --text-xxl: calc(var(--text-xl) * var(--text-scale-ratio)); + --text-xxxl: calc(var(--text-xxl) * var(--text-scale-ratio)); + --text-xxxxl: calc(var(--text-xxxl) * var(--text-scale-ratio)); +} + +body { + font-family: var(--font-primary); +} + +h1, h2, h3, h4 { + font-family: var(--font-primary); + --heading-font-weight: 600; +} + +// font family +.font-primary { font-family: var(--font-primary); } + +// -------------------------------- + +// (END) Global editor code + +// -------------------------------- + +// link style +a, .link {} + +mark { + background-color: alpha(var(--color-accent), 0.2); + color: inherit; +} + +.text-component { + --line-height-multiplier: 1; + --text-space-y-multiplier: 1; + + > * { // use Em units + --text-unit: 1em; + --space-unit: 1em; + } + + blockquote { + padding-left: 1em; + border-left: 4px solid var(--color-contrast-lower); + font-style: italic; + } + + hr { + background: var(--color-contrast-lower); + height: 1px; + } + + figcaption { + font-size: var(--text-sm); + color: var(--color-contrast-low); + } +} + +.article { // e.g., blog posts + --body-line-height: 1.58; // set body line-height + --text-space-y-multiplier: 1.2; // control vertical spacing +}
\ No newline at end of file diff --git a/old-apps/web-shared/src/styles/custom-style/_util.scss b/old-apps/web-shared/src/styles/custom-style/_util.scss new file mode 100644 index 0000000..4b86e73 --- /dev/null +++ b/old-apps/web-shared/src/styles/custom-style/_util.scss @@ -0,0 +1,34 @@ +@use '../base' as *; + +.border-none { + border: none !important; +} + +// 👇 (optional) create responsive variations - edit only [my-util-class, property, value] +// @each $breakpoint, $value in $breakpoints { +// @include breakpoint(#{$breakpoint}) { +// .my-util-class\@#{$breakpoint} { +// property: value; +// } +// } +.left-unset { + left: unset !important; +} + +.cursor-wait { + cursor: wait !important; +} + +.bg-error-lighter\@hover { + &:hover, + &:active { + background-color: var(--color-error-lighter) !important; + } +} + +.color-white\@hover { + &:hover, + &:active { + color: var(--color-white) !important; + } +}
\ No newline at end of file diff --git a/old-apps/web-shared/tsconfig.json b/old-apps/web-shared/tsconfig.json new file mode 100644 index 0000000..7716e44 --- /dev/null +++ b/old-apps/web-shared/tsconfig.json @@ -0,0 +1,24 @@ +{ + "include": [ + "./src/**/*.d.ts", + "./src/**/*.ts", + "./src/**/*.js", + "./src/**/*.svelte" + ], + "exclude": [ + "./node_modules" + ], + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "moduleResolution": "node", + "allowJs": true, + "checkJs": false, + "paths": { + "$shared/*": [ + "./src/*" + ] + } + } +} |
