Merge pull request 'feat(ui): Sidebar nav' (#4018) from ui/sidebar into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4018
@@ -8,7 +8,6 @@ const config: StorybookConfig = {
|
||||
"@storybook/addon-links",
|
||||
"@storybook/addon-docs",
|
||||
"@storybook/addon-a11y",
|
||||
"@storybook/addon-onboarding",
|
||||
],
|
||||
async viteFinal(config) {
|
||||
return mergeConfig(config, {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||
<path d="M27 38H6V17H10V13H13.5V9H37.5V13H41V24H27V27H34V31H30.5V34.5H27V38ZM16.5 20.5H20V17H16.5V20.5Z" fill="black"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="currentColor">
|
||||
<path d="M27 38H6V17H10V13H13.5V9H37.5V13H41V24H27V27H34V31H30.5V34.5H27V38ZM16.5 20.5H20V17H16.5V20.5Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 226 B After Width: | Height: | Size: 221 B |
@@ -1,25 +1,25 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="35" height="42" viewBox="0 0 35 42" fill="none">
|
||||
<rect y="6" width="6" height="6" fill="black"/>
|
||||
<rect x="6" y="6" width="6" height="6" fill="black"/>
|
||||
<rect x="12" y="6" width="6" height="6" fill="black"/>
|
||||
<rect x="6" y="12" width="6" height="6" fill="black"/>
|
||||
<rect x="18" y="18" width="6" height="6" fill="black"/>
|
||||
<rect x="18" y="12" width="6" height="6" fill="black"/>
|
||||
<rect x="12" y="24" width="6" height="6" fill="black"/>
|
||||
<rect x="12" y="18" width="6" height="6" fill="black"/>
|
||||
<rect x="12" y="12" width="6" height="6" fill="black"/>
|
||||
<rect width="6" height="6" fill="black"/>
|
||||
<rect x="6" width="6" height="6" fill="black"/>
|
||||
<rect x="24" y="18" width="6" height="6" fill="black"/>
|
||||
<rect y="12" width="6" height="6" fill="black"/>
|
||||
<rect x="6" y="18" width="6" height="6" fill="black"/>
|
||||
<rect y="18" width="6" height="6" fill="black"/>
|
||||
<rect y="24" width="6" height="6" fill="black"/>
|
||||
<rect y="30" width="6" height="6" fill="black"/>
|
||||
<rect y="36" width="6" height="6" fill="black"/>
|
||||
<rect x="6" y="24" width="6" height="6" fill="black"/>
|
||||
<rect x="18" y="24" width="6" height="6" fill="black"/>
|
||||
<rect x="24" y="24" width="6" height="6" fill="black"/>
|
||||
<rect x="29" y="24" width="6" height="6" fill="black"/>
|
||||
<rect x="6" y="30" width="6" height="6" fill="black"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="35" height="42" viewBox="0 0 35 42" fill="currentColor">
|
||||
<rect y="6" width="6" height="6"/>
|
||||
<rect x="6" y="6" width="6" height="6"/>
|
||||
<rect x="12" y="6" width="6" height="6"/>
|
||||
<rect x="6" y="12" width="6" height="6"/>
|
||||
<rect x="18" y="18" width="6" height="6"/>
|
||||
<rect x="18" y="12" width="6" height="6"/>
|
||||
<rect x="12" y="24" width="6" height="6"/>
|
||||
<rect x="12" y="18" width="6" height="6"/>
|
||||
<rect x="12" y="12" width="6" height="6"/>
|
||||
<rect width="6" height="6"/>
|
||||
<rect x="6" width="6" height="6"/>
|
||||
<rect x="24" y="18" width="6" height="6"/>
|
||||
<rect y="12" width="6" height="6"/>
|
||||
<rect x="6" y="18" width="6" height="6"/>
|
||||
<rect y="18" width="6" height="6"/>
|
||||
<rect y="24" width="6" height="6"/>
|
||||
<rect y="30" width="6" height="6"/>
|
||||
<rect y="36" width="6" height="6"/>
|
||||
<rect x="6" y="24" width="6" height="6"/>
|
||||
<rect x="18" y="24" width="6" height="6"/>
|
||||
<rect x="24" y="24" width="6" height="6"/>
|
||||
<rect x="29" y="24" width="6" height="6"/>
|
||||
<rect x="6" y="30" width="6" height="6"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,3 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||
<path d="M20.2002 12.7998H23V15.5996H25.8008V14.2002H28.6006V10H34.2002V12.7998H37V15.5996H39.8008V24H37V26.7998H34.2002V29.5996H31.4004V32.4004H28.6006V35.2002H25.8008V38H23V35.2002H20.2002V32.4004H17.4004V29.5996H14.6006V26.7998H11.8008V24H9V15.5996H11.8008V12.7998H14.6006V10H20.2002V12.7998Z" fill="black"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="currentColor">
|
||||
<path d="M20.2002 12.7998H23V15.5996H25.8008V14.2002H28.6006V10H34.2002V12.7998H37V15.5996H39.8008V24H37V26.7998H34.2002V29.5996H31.4004V32.4004H28.6006V35.2002H25.8008V38H23V35.2002H20.2002V32.4004H17.4004V29.5996H14.6006V26.7998H11.8008V24H9V15.5996H11.8008V12.7998H14.6006V10H20.2002V12.7998Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 418 B After Width: | Height: | Size: 413 B |
@@ -1,3 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||
<path d="M38.666 5V39.667H38.667V5H43V44H4V5H38.666ZM12.666 35.334H16.999V31H12.666V35.334ZM29.999 35.334H34.333V31H29.999V35.334ZM21.333 26.667H25.666V22.334H21.333V26.667ZM12.666 18H16.999V13.667H12.666V18ZM29.999 18H34.333V13.667H29.999V18Z" fill="black"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="currentColor">
|
||||
<path d="M38.666 5V39.667H38.667V5H43V44H4V5H38.666ZM12.666 35.334H16.999V31H12.666V35.334ZM29.999 35.334H34.333V31H29.999V35.334ZM21.333 26.667H25.666V22.334H21.333V26.667ZM12.666 18H16.999V13.667H12.666V18ZM29.999 18H34.333V13.667H29.999V18Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 366 B After Width: | Height: | Size: 361 B |
@@ -1,3 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||
<path d="M38 42H10V38H6V10H10V6H38V10H42V38H38V42ZM18 32H30V28H18V32ZM14 28H18V24H14V28ZM30 28H34V24H30V28ZM16 20H20V16H16V20ZM28 20H32V16H28V20Z" fill="black"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="currentColor">
|
||||
<path d="M38 42H10V38H6V10H10V6H38V10H42V38H38V42ZM18 32H30V28H18V32ZM14 28H18V24H14V28ZM30 28H34V24H30V28ZM16 20H20V16H16V20ZM28 20H32V16H28V20Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 268 B After Width: | Height: | Size: 263 B |
@@ -1,3 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||
<path d="M42 42H14V38H38V14H42V42ZM34 6V34H6V6H34ZM18 18H14V22H18V26H22V22H26V18H22V14H18V18Z" fill="black"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="currentColor">
|
||||
<path d="M42 42H14V38H38V14H42V42ZM34 6V34H6V6H34ZM18 18H14V22H18V26H22V22H26V18H22V14H18V18Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 216 B After Width: | Height: | Size: 211 B |
@@ -1,13 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="38" height="27" viewBox="0 0 38 27" fill="none">
|
||||
<rect x="4.46155" y="4.15381" width="4.15385" height="4.15385" fill="black"/>
|
||||
<rect x="29.3846" y="4.15381" width="4.15385" height="4.15385" fill="black"/>
|
||||
<rect x="8.61539" width="4.15385" height="4.15385" fill="black"/>
|
||||
<rect x="33.5385" width="4.15385" height="4.15385" fill="black"/>
|
||||
<rect x="0.307678" width="4.15385" height="4.15385" fill="black"/>
|
||||
<rect x="25.2308" width="4.15385" height="4.15385" fill="black"/>
|
||||
<rect x="0.307678" y="8.30762" width="4.15385" height="4.15385" fill="black"/>
|
||||
<rect x="25.2308" y="8.30762" width="4.15385" height="4.15385" fill="black"/>
|
||||
<rect x="8.61539" y="8.30762" width="4.15385" height="4.15385" fill="black"/>
|
||||
<rect x="33.5385" y="8.30762" width="4.15385" height="4.15385" fill="black"/>
|
||||
<rect x="4" y="23" width="30" height="4" fill="black"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="38" height="27" viewBox="0 0 38 27" fill="currentColor">
|
||||
<rect x="4.46155" y="4.15381" width="4.15385" height="4.15385"/>
|
||||
<rect x="29.3846" y="4.15381" width="4.15385" height="4.15385"/>
|
||||
<rect x="8.61539" width="4.15385" height="4.15385"/>
|
||||
<rect x="33.5385" width="4.15385" height="4.15385"/>
|
||||
<rect x="0.307678" width="4.15385" height="4.15385"/>
|
||||
<rect x="25.2308" width="4.15385" height="4.15385"/>
|
||||
<rect x="0.307678" y="8.30762" width="4.15385" height="4.15385"/>
|
||||
<rect x="25.2308" y="8.30762" width="4.15385" height="4.15385"/>
|
||||
<rect x="8.61539" y="8.30762" width="4.15385" height="4.15385"/>
|
||||
<rect x="33.5385" y="8.30762" width="4.15385" height="4.15385"/>
|
||||
<rect x="4" y="23" width="30" height="4"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 936 B After Width: | Height: | Size: 801 B |
@@ -1,3 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||
<path d="M39.2002 39.2002H43V43H39.2002V39.2021H35.3994V35.4014H39.2002V39.2002ZM27.7998 8.80078H31.5996V31.6016H27.7998V35.4004H12.6006V12.6016H20.2002V8.80078H12.6006V5H27.7998V8.80078ZM35.4004 35.4004H31.6006V31.6006H35.4004V35.4004ZM12.5996 12.5996H8.7998V20.2002H12.5996V31.6016H8.7998V27.8008H5V12.5996H8.7998V8.80078H12.5996V12.5996ZM35.4004 27.8008H31.6006V12.5996H35.4004V27.8008Z" fill="black"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="currentColor">
|
||||
<path d="M39.2002 39.2002H43V43H39.2002V39.2021H35.3994V35.4014H39.2002V39.2002ZM27.7998 8.80078H31.5996V31.6016H27.7998V35.4004H12.6006V12.6016H20.2002V8.80078H12.6006V5H27.7998V8.80078ZM35.4004 35.4004H31.6006V31.6006H35.4004V35.4004ZM12.5996 12.5996H8.7998V20.2002H12.5996V31.6016H8.7998V27.8008H5V12.5996H8.7998V8.80078H12.5996V12.5996ZM35.4004 27.8008H31.6006V12.5996H35.4004V27.8008Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 512 B After Width: | Height: | Size: 507 B |
@@ -1,8 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="23" viewBox="0 0 36 23" fill="none">
|
||||
<rect x="27" width="22.5" height="4.5" transform="rotate(90 27 0)" fill="black"/>
|
||||
<rect x="31.5" y="4.5" width="13.5" height="4.5" transform="rotate(90 31.5 4.5)" fill="black"/>
|
||||
<rect x="36" y="9" width="4.5" height="4.5" transform="rotate(90 36 9)" fill="black"/>
|
||||
<rect width="22.5" height="4.5" transform="matrix(-4.37114e-08 1 1 4.37114e-08 9 0)" fill="black"/>
|
||||
<rect width="13.5" height="4.5" transform="matrix(-4.37114e-08 1 1 4.37114e-08 4.5 4.5)" fill="black"/>
|
||||
<rect width="4.5" height="4.5" transform="matrix(-4.37114e-08 1 1 4.37114e-08 0 9)" fill="black"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="23" viewBox="0 0 36 23" fill="currentColor">
|
||||
<rect x="27" width="22.5" height="4.5" transform="rotate(90 27 0)"/>
|
||||
<rect x="31.5" y="4.5" width="13.5" height="4.5" transform="rotate(90 31.5 4.5)"/>
|
||||
<rect x="36" y="9" width="4.5" height="4.5" transform="rotate(90 36 9)"/>
|
||||
<rect width="22.5" height="4.5" transform="matrix(-4.37114e-08 1 1 4.37114e-08 9 0)"/>
|
||||
<rect width="13.5" height="4.5" transform="matrix(-4.37114e-08 1 1 4.37114e-08 4.5 4.5)"/>
|
||||
<rect width="4.5" height="4.5" transform="matrix(-4.37114e-08 1 1 4.37114e-08 0 9)"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 694 B After Width: | Height: | Size: 624 B |
@@ -1,3 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||
<path d="M39.4004 31.2002H35.7998V34.7998H32.2002V38.3994H28.5996V42H25V38.3994H21.4004V34.7998H17.7998V31.2002H14.2002V27.6006H39.4004V31.2002ZM28.5996 13.2002H32.2002V16.7998H35.7998V20.3994H39.4004V24H43V27.5996H10.5996V24H7V9.60059H28.5996V13.2002ZM14.2002 13.2002V16.7998H17.7998V13.2002H14.2002ZM25 9.59961H7V6H25V9.59961Z" fill="black"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="currentColor">
|
||||
<path d="M39.4004 31.2002H35.7998V34.7998H32.2002V38.3994H28.5996V42H25V38.3994H21.4004V34.7998H17.7998V31.2002H14.2002V27.6006H39.4004V31.2002ZM28.5996 13.2002H32.2002V16.7998H35.7998V20.3994H39.4004V24H43V27.5996H10.5996V24H7V9.60059H28.5996V13.2002ZM14.2002 13.2002V16.7998H17.7998V13.2002H14.2002ZM25 9.59961H7V6H25V9.59961Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 451 B After Width: | Height: | Size: 446 B |
@@ -1,3 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.8 0H28.4V9.6H18.8V0ZM11.6 12H35.6V16.8H30.8V33.6V48H26V33.6H21.2V48H16.4V33.6V16.8H11.6V12ZM6.8 7.2V12H11.6V7.2H6.8ZM6.8 7.2L2 7.2V2.4H6.8V7.2ZM40.4 7.2V12H35.6V7.2H40.4ZM40.4 7.2L40.4 2.4H45.2V7.2L40.4 7.2Z" fill="black"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="currentColor">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.8 0H28.4V9.6H18.8V0ZM11.6 12H35.6V16.8H30.8V33.6V48H26V33.6H21.2V48H16.4V33.6V16.8H11.6V12ZM6.8 7.2V12H11.6V7.2H6.8ZM6.8 7.2L2 7.2V2.4H6.8V7.2ZM40.4 7.2V12H35.6V7.2H40.4ZM40.4 7.2L40.4 2.4H45.2V7.2L40.4 7.2Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 383 B After Width: | Height: | Size: 378 B |
35
pkgs/clan-app/ui/package-lock.json
generated
@@ -15,6 +15,7 @@
|
||||
"@modular-forms/solid": "^0.25.1",
|
||||
"@solid-primitives/storage": "^4.3.2",
|
||||
"@solidjs/router": "^0.15.3",
|
||||
"@solidjs/testing-library": "^0.8.10",
|
||||
"@tanstack/eslint-plugin-query": "^5.51.12",
|
||||
"@tanstack/solid-query": "^5.76.0",
|
||||
"solid-js": "^1.9.7",
|
||||
@@ -126,7 +127,6 @@
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
||||
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.27.1",
|
||||
@@ -268,7 +268,6 @@
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
|
||||
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -366,7 +365,6 @@
|
||||
"version": "7.27.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
|
||||
"integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -2037,6 +2035,27 @@
|
||||
"solid-js": "^1.8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@solidjs/testing-library": {
|
||||
"version": "0.8.10",
|
||||
"resolved": "https://registry.npmjs.org/@solidjs/testing-library/-/testing-library-0.8.10.tgz",
|
||||
"integrity": "sha512-qdeuIerwyq7oQTIrrKvV0aL9aFeuwTd86VYD3afdq5HYEwoox1OBTJy4y8A3TFZr8oAR0nujYgCzY/8wgHGfeQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@testing-library/dom": "^10.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@solidjs/router": ">=0.9.0",
|
||||
"solid-js": ">=1.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@solidjs/router": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/addon-a11y": {
|
||||
"version": "9.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-9.0.12.tgz",
|
||||
@@ -2305,7 +2324,6 @@
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
|
||||
"integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
@@ -2409,7 +2427,6 @@
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
@@ -3161,7 +3178,6 @@
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
|
||||
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"dequal": "^2.0.3"
|
||||
@@ -3989,7 +4005,6 @@
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -4011,7 +4026,6 @@
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dom-serializer": {
|
||||
@@ -5323,7 +5337,6 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
@@ -5644,7 +5657,6 @@
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"lz-string": "bin/bin.js"
|
||||
@@ -6505,7 +6517,6 @@
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
|
||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1",
|
||||
@@ -6520,7 +6531,6 @@
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
@@ -6622,7 +6632,6 @@
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
"@modular-forms/solid": "^0.25.1",
|
||||
"@solid-primitives/storage": "^4.3.2",
|
||||
"@solidjs/router": "^0.15.3",
|
||||
"@solidjs/testing-library": "^0.8.10",
|
||||
"@tanstack/eslint-plugin-query": "^5.51.12",
|
||||
"@tanstack/solid-query": "^5.76.0",
|
||||
"solid-js": "^1.9.7",
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||
import { Button, ButtonProps } from "./Button";
|
||||
import { Component } from "solid-js";
|
||||
import { expect, fn, waitFor } from "storybook/test";
|
||||
import { PlayFunctionContext } from "storybook/internal/csf";
|
||||
import { StoryContext } from "@kachurun/storybook-solid-vite";
|
||||
|
||||
const getCursorStyle = (el: Element) => window.getComputedStyle(el).cursor;
|
||||
@@ -150,7 +149,7 @@ export const Primary: Story = {
|
||||
hierarchy: "primary",
|
||||
onAction: fn(async () => {
|
||||
// wait 500 ms to simulate an action
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
// randomly fail to check that the loading state still returns to normal
|
||||
if (Math.random() > 0.5) {
|
||||
throw new Error("Action failure");
|
||||
@@ -159,6 +158,7 @@ export const Primary: Story = {
|
||||
},
|
||||
parameters: {
|
||||
test: {
|
||||
// increase test timeout to allow for the loading action
|
||||
mockTimers: true,
|
||||
},
|
||||
},
|
||||
@@ -205,14 +205,17 @@ export const Primary: Story = {
|
||||
});
|
||||
|
||||
// wait for the action handler to finish
|
||||
await waitFor(async () => {
|
||||
// the loading class should be removed
|
||||
await expect(button).not.toHaveClass("loading");
|
||||
// the loader should be hidden
|
||||
await expect(loader.clientWidth).toEqual(0);
|
||||
// the pointer should be normal
|
||||
await expect(getCursorStyle(button)).toEqual("pointer");
|
||||
});
|
||||
await waitFor(
|
||||
async () => {
|
||||
// the loading class should be removed
|
||||
await expect(button).not.toHaveClass("loading");
|
||||
// the loader should be hidden
|
||||
await expect(loader.clientWidth).toEqual(0);
|
||||
// the pointer should be normal
|
||||
await expect(getCursorStyle(button)).toEqual("pointer");
|
||||
},
|
||||
{ timeout: 1500 },
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { splitProps, type JSX, createSignal, Show } from "solid-js";
|
||||
import { splitProps, type JSX, createSignal } from "solid-js";
|
||||
import cx from "classnames";
|
||||
import { Typography } from "../Typography/Typography";
|
||||
import { Button as KobalteButton } from "@kobalte/core/button";
|
||||
|
||||
15
pkgs/clan-app/ui/src/components/v2/Divider/Divider.css
Normal file
@@ -0,0 +1,15 @@
|
||||
div.divider {
|
||||
@apply bg-inv-2;
|
||||
|
||||
&.inverted {
|
||||
@apply bg-def-3;
|
||||
}
|
||||
|
||||
&.horizontal {
|
||||
@apply w-full h-px;
|
||||
}
|
||||
|
||||
&.vertical {
|
||||
@apply h-full w-px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||
import { Divider, DividerProps } from "@/src/components/v2/Divider/Divider";
|
||||
|
||||
const meta: Meta<DividerProps> = {
|
||||
title: "Components/Divider",
|
||||
component: Divider,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<DividerProps>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Horizontal: Story = {
|
||||
args: {
|
||||
orientation: "horizontal",
|
||||
},
|
||||
};
|
||||
|
||||
export const HorizontalInverted: Story = {
|
||||
args: {
|
||||
inverted: true,
|
||||
...Horizontal.args,
|
||||
},
|
||||
};
|
||||
|
||||
export const Vertical: Story = {
|
||||
args: {
|
||||
orientation: "vertical",
|
||||
},
|
||||
decorators: [
|
||||
(Story: Story) => (
|
||||
<div class="h-32 w-full">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export const VerticalInverted: Story = {
|
||||
args: {
|
||||
inverted: true,
|
||||
...Vertical.args,
|
||||
},
|
||||
decorators: [...Vertical.decorators],
|
||||
};
|
||||
16
pkgs/clan-app/ui/src/components/v2/Divider/Divider.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import "./Divider.css";
|
||||
import cx from "classnames";
|
||||
|
||||
export type Orientation = "horizontal" | "vertical";
|
||||
|
||||
export interface DividerProps {
|
||||
inverted?: boolean;
|
||||
orientation?: Orientation;
|
||||
}
|
||||
|
||||
export const Divider = (props: DividerProps) => {
|
||||
const inverted = props.inverted || false;
|
||||
const orientation = props.orientation || "horizontal";
|
||||
|
||||
return <div class={cx("divider", orientation, { inverted: inverted })} />;
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||
import type { Meta, StoryObj, StoryContext } from "@kachurun/storybook-solid";
|
||||
import { Component, For } from "solid-js";
|
||||
import Icon, { IconProps, IconVariant } from "./Icon";
|
||||
import cx from "classnames";
|
||||
|
||||
const iconVariants: IconVariant[] = [
|
||||
"ClanIcon",
|
||||
@@ -59,6 +60,13 @@ const IconExamples: Component<IconProps> = (props) => (
|
||||
const meta: Meta<IconProps> = {
|
||||
title: "Components/Icon",
|
||||
component: IconExamples,
|
||||
decorators: [
|
||||
(Story: StoryObj, context: StoryContext<IconProps>) => (
|
||||
<div class={cx(context.args.inverted || false ? "bg-inv-acc-3" : "")}>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
@@ -67,6 +75,64 @@ type Story = StoryObj<IconProps>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
color: "primary",
|
||||
},
|
||||
};
|
||||
|
||||
export const Secondary: Story = {
|
||||
args: {
|
||||
color: "secondary",
|
||||
},
|
||||
};
|
||||
|
||||
export const Tertiary: Story = {
|
||||
args: {
|
||||
color: "tertiary",
|
||||
},
|
||||
};
|
||||
|
||||
export const Quaternary: Story = {
|
||||
args: {
|
||||
color: "quaternary",
|
||||
},
|
||||
};
|
||||
|
||||
export const PrimaryInverted: Story = {
|
||||
args: {
|
||||
...Primary.args,
|
||||
inverted: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const SecondaryInverted: Story = {
|
||||
args: {
|
||||
...Secondary.args,
|
||||
inverted: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const TertiaryInverted: Story = {
|
||||
args: {
|
||||
...Tertiary.args,
|
||||
inverted: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const QuaternaryInverted: Story = {
|
||||
args: {
|
||||
...Quaternary.args,
|
||||
inverted: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const Inverted: Story = {
|
||||
args: {
|
||||
inverted: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const Large: Story = {
|
||||
args: {
|
||||
width: "2rem",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import cx from "classnames";
|
||||
import { Component, JSX, Show, splitProps } from "solid-js";
|
||||
import { Component, JSX, splitProps } from "solid-js";
|
||||
import ArrowBottom from "@/icons/arrow-bottom.svg";
|
||||
import ArrowLeft from "@/icons/arrow-left.svg";
|
||||
import ArrowRight from "@/icons/arrow-right.svg";
|
||||
@@ -45,9 +45,10 @@ import Offline from "@/icons/offline.svg";
|
||||
import Switch from "@/icons/switch.svg";
|
||||
import Tag from "@/icons/tag.svg";
|
||||
import Machine from "@/icons/machine.svg";
|
||||
import Loader from "@/icons/loader.svg";
|
||||
import { Dynamic } from "solid-js/web";
|
||||
|
||||
import { Color, fgClass } from "../colors";
|
||||
|
||||
const icons = {
|
||||
AI,
|
||||
ArrowBottom,
|
||||
@@ -98,24 +99,43 @@ const icons = {
|
||||
|
||||
export type IconVariant = keyof typeof icons;
|
||||
|
||||
const viewBoxes: Partial<Record<IconVariant, string>> = {
|
||||
ClanIcon: "0 0 72 89",
|
||||
Offline: "0 0 38 27",
|
||||
};
|
||||
|
||||
export interface IconProps extends JSX.SvgSVGAttributes<SVGElement> {
|
||||
icon: IconVariant;
|
||||
class?: string;
|
||||
size?: number | string;
|
||||
color?: Color;
|
||||
inverted?: boolean;
|
||||
}
|
||||
|
||||
const Icon: Component<IconProps> = (props) => {
|
||||
const [local, iconProps] = splitProps(props, ["icon", "class"]);
|
||||
const [local, iconProps] = splitProps(props, [
|
||||
"icon",
|
||||
"color",
|
||||
"class",
|
||||
"size",
|
||||
"inverted",
|
||||
]);
|
||||
|
||||
const IconComponent = () => icons[local.icon];
|
||||
|
||||
// we need to adjust the view box for certain icons
|
||||
const viewBox = () => viewBoxes[local.icon] ?? "0 0 48 48";
|
||||
|
||||
return IconComponent() ? (
|
||||
<Dynamic
|
||||
component={IconComponent()}
|
||||
class={cx("icon", local.class)}
|
||||
width={iconProps.size || "1em"}
|
||||
height={iconProps.size || "1em"}
|
||||
viewBox="0 0 48 48"
|
||||
class={cx("icon", local.class, fgClass(local.color, local.inverted), {
|
||||
inverted: local.inverted,
|
||||
})}
|
||||
data-icon-name={local.icon}
|
||||
width={local.size || "1em"}
|
||||
height={local.size || "1em"}
|
||||
viewBox={viewBox()}
|
||||
ref={iconProps.ref}
|
||||
{...iconProps}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
span.tag-status {
|
||||
@apply flex items-center gap-1;
|
||||
|
||||
.indicator {
|
||||
@apply w-1.5 h-1.5 rounded-full m-1.5;
|
||||
}
|
||||
|
||||
&.online > .indicator {
|
||||
background-color: #0ae856; /* todo get from theme */
|
||||
}
|
||||
|
||||
&.offline > .indicator {
|
||||
background-color: #ff2c78; /* todo get from theme */
|
||||
}
|
||||
|
||||
&.installed > .indicator {
|
||||
background-color: var(--clr-fg-inv-3);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import {
|
||||
MachineStatus,
|
||||
TagStatusProps,
|
||||
} from "@/src/components/v2/MachineStatus/MachineStatus";
|
||||
import { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||
|
||||
const meta: Meta<TagStatusProps> = {
|
||||
title: "Components/MachineStatus",
|
||||
component: MachineStatus,
|
||||
decorators: [
|
||||
(Story: StoryObj) => (
|
||||
<div class="p-5 bg-inv-1">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<TagStatusProps>;
|
||||
|
||||
export const Online: Story = {
|
||||
args: {
|
||||
status: "Online",
|
||||
},
|
||||
};
|
||||
|
||||
export const Offline: Story = {
|
||||
args: {
|
||||
status: "Offline",
|
||||
},
|
||||
};
|
||||
|
||||
export const Installed: Story = {
|
||||
args: {
|
||||
status: "Installed",
|
||||
},
|
||||
};
|
||||
|
||||
export const NotInstalled: Story = {
|
||||
args: {
|
||||
status: "Not Installed",
|
||||
},
|
||||
};
|
||||
|
||||
export const OnlineWithLabel: Story = {
|
||||
args: {
|
||||
...Online.args,
|
||||
label: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const OfflineWithLabel: Story = {
|
||||
args: {
|
||||
...Offline.args,
|
||||
label: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const InstalledWithLabel: Story = {
|
||||
args: {
|
||||
...Installed.args,
|
||||
label: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const NotInstalledWithLabel: Story = {
|
||||
args: {
|
||||
...NotInstalled.args,
|
||||
label: true,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import "./MachineStatus.css";
|
||||
|
||||
import { Badge } from "@kobalte/core/badge";
|
||||
import cx from "classnames";
|
||||
import { Show } from "solid-js";
|
||||
import Icon from "../Icon/Icon";
|
||||
import { Typography } from "@/src/components/v2/Typography/Typography";
|
||||
|
||||
export type MachineStatus =
|
||||
| "Online"
|
||||
| "Offline"
|
||||
| "Installed"
|
||||
| "Not Installed";
|
||||
|
||||
export interface TagStatusProps {
|
||||
label?: boolean;
|
||||
status: MachineStatus;
|
||||
}
|
||||
|
||||
export const MachineStatus = (props: TagStatusProps) => (
|
||||
<Badge
|
||||
class={cx("tag-status", {
|
||||
online: props.status == "Online",
|
||||
offline: props.status == "Offline",
|
||||
installed: props.status == "Installed",
|
||||
"not-installed": props.status == "Not Installed",
|
||||
})}
|
||||
textValue={props.status}
|
||||
>
|
||||
{props.label && (
|
||||
<Typography hierarchy="label" size="xs" weight="medium" inverted={true}>
|
||||
{props.status}
|
||||
</Typography>
|
||||
)}
|
||||
<Show
|
||||
when={props.status == "Not Installed"}
|
||||
fallback={<div class="indicator" />}
|
||||
>
|
||||
<Icon icon="Offline" inverted={true} />
|
||||
</Show>
|
||||
</Badge>
|
||||
);
|
||||
10
pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNav.css
Normal file
@@ -0,0 +1,10 @@
|
||||
div.sidebar {
|
||||
@apply h-full w-auto max-w-60 border-none;
|
||||
|
||||
& > div.header {
|
||||
}
|
||||
|
||||
& > div.body {
|
||||
@apply pt-4 pb-3 px-2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||
import {
|
||||
createMemoryHistory,
|
||||
MemoryRouter,
|
||||
Route,
|
||||
RouteSectionProps,
|
||||
} from "@solidjs/router";
|
||||
import {
|
||||
SidebarNav,
|
||||
SidebarNavProps,
|
||||
} from "@/src/components/v2/Sidebar/SidebarNav";
|
||||
import { Suspense } from "solid-js";
|
||||
import { StoryContext } from "@kachurun/storybook-solid-vite";
|
||||
|
||||
const sidebarNavProps: SidebarNavProps = {
|
||||
clanLinks: [
|
||||
{ label: "Brian's Clan", path: "/clan/1" },
|
||||
{ label: "Dave's Clan", path: "/clan/2" },
|
||||
{ label: "Mic92's Clan", path: "/clan/3" },
|
||||
],
|
||||
clanDetail: {
|
||||
label: "Brian's Clan",
|
||||
settingsPath: "/clan/1/settings",
|
||||
machines: [
|
||||
{
|
||||
label: "Backup & Home",
|
||||
path: "/clan/1/machine/backup",
|
||||
serviceCount: 3,
|
||||
status: "Online",
|
||||
},
|
||||
{
|
||||
label: "Raspberry Pi",
|
||||
path: "/clan/1/machine/pi",
|
||||
serviceCount: 1,
|
||||
status: "Offline",
|
||||
},
|
||||
{
|
||||
label: "Mom's Laptop",
|
||||
path: "/clan/1/machine/moms-laptop",
|
||||
serviceCount: 2,
|
||||
status: "Installed",
|
||||
},
|
||||
{
|
||||
label: "Dad's Laptop",
|
||||
path: "/clan/1/machine/dads-laptop",
|
||||
serviceCount: 4,
|
||||
status: "Not Installed",
|
||||
},
|
||||
],
|
||||
},
|
||||
extraSections: [
|
||||
{
|
||||
label: "Tools",
|
||||
links: [
|
||||
{ label: "Borgbackup", path: "/clan/1/service/borgbackup" },
|
||||
{ label: "Syncthing", path: "/clan/1/service/syncthing" },
|
||||
{ label: "Mumble", path: "/clan/1/service/mumble" },
|
||||
{ label: "Minecraft", path: "/clan/1/service/minecraft" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Links",
|
||||
links: [
|
||||
{ label: "GitHub", path: "https://github.com/brian-the-dev" },
|
||||
{ label: "Twitter", path: "https://twitter.com/brian_the_dev" },
|
||||
{
|
||||
label: "LinkedIn",
|
||||
path: "https://www.linkedin.com/in/brian-the-dev/",
|
||||
},
|
||||
{
|
||||
label: "Instagram",
|
||||
path: "https://www.instagram.com/brian_the_dev/",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const meta: Meta<RouteSectionProps> = {
|
||||
title: "Components/Sidebar/Nav",
|
||||
component: SidebarNav,
|
||||
render: (_: never, context: StoryContext<SidebarNavProps>) => {
|
||||
const history = createMemoryHistory();
|
||||
history.set({ value: "/clan/1/machine/backup" });
|
||||
|
||||
return (
|
||||
<div style="height: 670px;">
|
||||
<MemoryRouter
|
||||
history={history}
|
||||
root={(props) => (
|
||||
<Suspense>
|
||||
<SidebarNav {...sidebarNavProps} />
|
||||
</Suspense>
|
||||
)}
|
||||
>
|
||||
<Route path="/clan/1/machine/backup" component={(props) => <></>} />
|
||||
</MemoryRouter>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<RouteSectionProps>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {},
|
||||
};
|
||||
47
pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNav.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import "./SidebarNav.css";
|
||||
import { SidebarNavHeader } from "@/src/components/v2/Sidebar/SidebarNavHeader";
|
||||
import { SidebarNavBody } from "@/src/components/v2/Sidebar/SidebarNavBody";
|
||||
import { MachineStatus } from "@/src/components/v2/MachineStatus/MachineStatus";
|
||||
|
||||
export interface LinkProps {
|
||||
path: string;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface SectionProps {
|
||||
label: string;
|
||||
links: LinkProps[];
|
||||
}
|
||||
|
||||
export interface MachineProps {
|
||||
label: string;
|
||||
path: string;
|
||||
status: MachineStatus;
|
||||
serviceCount: number;
|
||||
}
|
||||
|
||||
export interface ClanLinkProps {
|
||||
label: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface ClanProps {
|
||||
label: string;
|
||||
settingsPath: string;
|
||||
machines: MachineProps[];
|
||||
}
|
||||
|
||||
export interface SidebarNavProps {
|
||||
clanDetail: ClanProps;
|
||||
clanLinks: ClanLinkProps[];
|
||||
extraSections: SectionProps[];
|
||||
}
|
||||
|
||||
export const SidebarNav = (props: SidebarNavProps) => {
|
||||
return (
|
||||
<div class="sidebar">
|
||||
<SidebarNavHeader {...props} />
|
||||
<SidebarNavBody {...props} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
127
pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNavBody.css
Normal file
@@ -0,0 +1,127 @@
|
||||
div.sidebar-body {
|
||||
@apply py-4 px-2 h-full;
|
||||
@apply border border-inv-3 rounded-bl-md rounded-br-md;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
|
||||
scrollbar-color: theme(colors.primary.700) theme(colors.primary.600);
|
||||
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
var(--clr-bg-inv-1) 0%,
|
||||
var(--clr-bg-inv-3) 100%
|
||||
);
|
||||
|
||||
@apply backdrop-blur-sm;
|
||||
|
||||
.accordion {
|
||||
@apply w-full mb-4;
|
||||
|
||||
&:last-child {
|
||||
@apply mb-0;
|
||||
}
|
||||
|
||||
& > .item {
|
||||
@apply py-3 px-1.5 bg-inv-3 rounded-md mb-4;
|
||||
|
||||
&:last-child {
|
||||
@apply mb-0;
|
||||
}
|
||||
|
||||
& > .header {
|
||||
@apply flex mb-4 px-2;
|
||||
|
||||
& > .trigger {
|
||||
@apply inline-flex items-center justify-between w-full;
|
||||
|
||||
&:focus-visible {
|
||||
@apply z-10;
|
||||
outline: 2px solid hsl(200 98% 39%);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
& > .icon {
|
||||
transition: transform 300ms cubic-bezier(0.87, 0, 0.13, 1);
|
||||
}
|
||||
|
||||
&[data-expanded] > .icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
@apply uppercase;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& > .content {
|
||||
@apply overflow-hidden flex flex-col;
|
||||
animation: slideAccordionUp 300ms cubic-bezier(0.87, 0, 0.13, 1);
|
||||
|
||||
&[data-expanded] {
|
||||
animation: slideAccordionDown 300ms cubic-bezier(0.87, 0, 0.13, 1);
|
||||
}
|
||||
|
||||
nav * {
|
||||
@apply outline-none;
|
||||
}
|
||||
|
||||
nav > a {
|
||||
@apply block w-full px-2 py-1.5 min-h-7 my-2 rounded-md;
|
||||
|
||||
&:first-child {
|
||||
@apply mt-0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
@apply mb-0;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
theme(colors.secondary.900),
|
||||
60%,
|
||||
theme(colors.secondary.600) 100%
|
||||
);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@apply bg-inv-acc-2;
|
||||
}
|
||||
|
||||
&:active {
|
||||
@apply bg-inv-acc-3;
|
||||
}
|
||||
|
||||
&.active {
|
||||
@apply bg-inv-acc-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideAccordionDown {
|
||||
from {
|
||||
height: 0;
|
||||
}
|
||||
to {
|
||||
height: var(--kb-accordion-content-height);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideAccordionUp {
|
||||
from {
|
||||
height: var(--kb-accordion-content-height);
|
||||
}
|
||||
to {
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
138
pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNavBody.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
import "./SidebarNavBody.css";
|
||||
import { A } from "@solidjs/router";
|
||||
import { Accordion } from "@kobalte/core/accordion";
|
||||
import Icon from "../Icon/Icon";
|
||||
import { Typography } from "@/src/components/v2/Typography/Typography";
|
||||
import {
|
||||
MachineProps,
|
||||
SidebarNavProps,
|
||||
} from "@/src/components/v2/Sidebar/SidebarNav";
|
||||
import { For } from "solid-js";
|
||||
import { MachineStatus } from "@/src/components/v2/MachineStatus/MachineStatus";
|
||||
|
||||
const MachineRoute = (props: MachineProps) => (
|
||||
<div class="flex w-full flex-col gap-2">
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<Typography
|
||||
hierarchy="label"
|
||||
size="xs"
|
||||
weight="bold"
|
||||
color="primary"
|
||||
inverted={true}
|
||||
>
|
||||
{props.label}
|
||||
</Typography>
|
||||
<MachineStatus status={props.status} />
|
||||
</div>
|
||||
<div class="flex w-full flex-row items-center gap-1">
|
||||
<Icon icon="Flash" size="0.75rem" inverted={true} color="tertiary" />
|
||||
<Typography
|
||||
hierarchy="label"
|
||||
family="mono"
|
||||
size="s"
|
||||
inverted={true}
|
||||
color="primary"
|
||||
>
|
||||
{props.serviceCount}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const SidebarNavBody = (props: SidebarNavProps) => {
|
||||
const sectionLabels = props.extraSections.map((section) => section.label);
|
||||
|
||||
// controls which sections are open by default
|
||||
// we want them all to be open by default
|
||||
const defaultAccordionValues = ["your-machines", ...sectionLabels];
|
||||
|
||||
return (
|
||||
<div class="sidebar-body">
|
||||
<Accordion
|
||||
class="accordion"
|
||||
multiple
|
||||
defaultValue={defaultAccordionValues}
|
||||
>
|
||||
<Accordion.Item class="item" value="your-machines">
|
||||
<Accordion.Header class="header">
|
||||
<Accordion.Trigger class="trigger">
|
||||
<Typography
|
||||
class="section-title"
|
||||
hierarchy="label"
|
||||
family="mono"
|
||||
size="xs"
|
||||
inverted={true}
|
||||
color="tertiary"
|
||||
>
|
||||
Your Machines
|
||||
</Typography>
|
||||
<Icon
|
||||
icon="CaretDown"
|
||||
color="tertiary"
|
||||
inverted={true}
|
||||
size="0.75rem"
|
||||
/>
|
||||
</Accordion.Trigger>
|
||||
</Accordion.Header>
|
||||
<Accordion.Content class="content">
|
||||
<nav>
|
||||
<For each={props.clanDetail.machines}>
|
||||
{(machine) => (
|
||||
<A href={machine.path}>
|
||||
<MachineRoute {...machine} />
|
||||
</A>
|
||||
)}
|
||||
</For>
|
||||
</nav>
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
|
||||
<For each={props.extraSections}>
|
||||
{(section) => (
|
||||
<Accordion.Item class="item" value={section.label}>
|
||||
<Accordion.Header class="header">
|
||||
<Accordion.Trigger class="trigger">
|
||||
<Typography
|
||||
class="section-title"
|
||||
hierarchy="label"
|
||||
family="mono"
|
||||
size="xs"
|
||||
inverted={true}
|
||||
color="tertiary"
|
||||
>
|
||||
{section.label}
|
||||
</Typography>
|
||||
<Icon
|
||||
icon="CaretDown"
|
||||
color="tertiary"
|
||||
inverted={true}
|
||||
size="0.75rem"
|
||||
/>
|
||||
</Accordion.Trigger>
|
||||
</Accordion.Header>
|
||||
<Accordion.Content class="content">
|
||||
<nav>
|
||||
<For each={section.links || []}>
|
||||
{(link) => (
|
||||
<A href={link.path}>
|
||||
<Typography
|
||||
hierarchy="body"
|
||||
size="xs"
|
||||
weight="bold"
|
||||
color="primary"
|
||||
inverted={true}
|
||||
>
|
||||
{link.label}
|
||||
</Typography>
|
||||
</A>
|
||||
)}
|
||||
</For>
|
||||
</nav>
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
)}
|
||||
</For>
|
||||
</Accordion>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
div.sidebar-header {
|
||||
@apply flex items-center justify-center w-full px-1 py-1;
|
||||
@apply border border-inv-3 rounded-md rounded-bl-none rounded-br-none;
|
||||
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--clr-bg-inv-3) 0%,
|
||||
var(--clr-bg-inv-4) 100%
|
||||
);
|
||||
|
||||
& > .dropdown-trigger {
|
||||
@apply flex items-center justify-between flex-grow px-1 py-1;
|
||||
@apply rounded-tl-md rounded-tr-md;
|
||||
@apply border border-transparent border-b-0;
|
||||
|
||||
transition: all 250ms ease-in-out;
|
||||
|
||||
div.title {
|
||||
@apply flex items-center gap-2 justify-start;
|
||||
|
||||
& > .clan-icon {
|
||||
@apply rounded-full bg-inv-4 w-7 h-7;
|
||||
}
|
||||
}
|
||||
|
||||
.icon[data-icon-name="CaretDown"] {
|
||||
transition: transform 300ms cubic-bezier(0.87, 0, 0.13, 1);
|
||||
}
|
||||
|
||||
&[data-expanded] {
|
||||
@apply bg-def-1 border-def-2;
|
||||
|
||||
.icon[data-icon-name="CaretDown"] {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-dropdown-content {
|
||||
@apply flex flex-col w-full px-2 py-1.5;
|
||||
@apply bg-def-1 rounded-bl-md rounded-br-md;
|
||||
@apply border border-def-2;
|
||||
|
||||
animation: sidebarNavContentHide 250ms ease-in forwards;
|
||||
|
||||
.dropdown-item {
|
||||
@apply flex items-center justify-start w-full px-1.5 py-2 gap-2 rounded;
|
||||
|
||||
&:hover {
|
||||
@apply bg-def-acc-2 cursor-pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-group {
|
||||
@apply flex flex-col gap-2;
|
||||
@apply px-1;
|
||||
|
||||
.dropdown-group-label {
|
||||
}
|
||||
|
||||
.dropdown-group-items {
|
||||
@apply rounded px-1 py-1.5 bg-def-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-dropdown-content[data-expanded] {
|
||||
animation: sidebarNavContentShow 250ms ease-out;
|
||||
}
|
||||
|
||||
@keyframes sidebarNavContentShow {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.96);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes sidebarNavContentHide {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(0.96);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import "./SidebarNavHeader.css";
|
||||
import Icon from "@/src/components/v2/Icon/Icon";
|
||||
import { DropdownMenu } from "@kobalte/core/dropdown-menu";
|
||||
import { useNavigate } from "@solidjs/router";
|
||||
import { Typography } from "../Typography/Typography";
|
||||
import { createSignal, For } from "solid-js";
|
||||
import {
|
||||
ClanLinkProps,
|
||||
ClanProps,
|
||||
} from "@/src/components/v2/Sidebar/SidebarNav";
|
||||
|
||||
export interface SidebarHeaderProps {
|
||||
clanDetail: ClanProps;
|
||||
clanLinks: ClanLinkProps[];
|
||||
}
|
||||
|
||||
export const SidebarNavHeader = (props: SidebarHeaderProps) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [open, setOpen] = createSignal(false);
|
||||
|
||||
const firstChar = props.clanDetail.label.charAt(0);
|
||||
|
||||
return (
|
||||
<div class="sidebar-header">
|
||||
<DropdownMenu open={open()} onOpenChange={setOpen} sameWidth={true}>
|
||||
<DropdownMenu.Trigger class="dropdown-trigger">
|
||||
<div class="title">
|
||||
<div class="clan-icon">
|
||||
<Typography
|
||||
hierarchy="label"
|
||||
size="s"
|
||||
weight="bold"
|
||||
inverted={true}
|
||||
>
|
||||
{firstChar.toUpperCase()}
|
||||
</Typography>
|
||||
</div>
|
||||
<Typography
|
||||
hierarchy="label"
|
||||
size="s"
|
||||
weight="bold"
|
||||
inverted={!open()}
|
||||
>
|
||||
{props.clanDetail.label}
|
||||
</Typography>
|
||||
</div>
|
||||
<DropdownMenu.Icon>
|
||||
<Icon icon={"CaretDown"} inverted={!open()} size="0.75rem" />
|
||||
</DropdownMenu.Icon>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content class="sidebar-dropdown-content">
|
||||
<DropdownMenu.Item
|
||||
class="dropdown-item"
|
||||
onSelect={() => navigate(props.clanDetail.settingsPath)}
|
||||
>
|
||||
<Icon
|
||||
icon="Settings"
|
||||
size="0.75rem"
|
||||
inverted={true}
|
||||
color="tertiary"
|
||||
/>
|
||||
<Typography hierarchy="label" size="xs" weight="medium">
|
||||
Settings
|
||||
</Typography>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Group class="dropdown-group">
|
||||
<DropdownMenu.GroupLabel class="dropdown-group-label">
|
||||
<Typography
|
||||
hierarchy="label"
|
||||
family="mono"
|
||||
size="xs"
|
||||
color="tertiary"
|
||||
>
|
||||
YOUR CLANS
|
||||
</Typography>
|
||||
</DropdownMenu.GroupLabel>
|
||||
<div class="dropdown-group-items">
|
||||
<For each={props.clanLinks}>
|
||||
{(clan) => (
|
||||
<DropdownMenu.Item
|
||||
class="dropdown-item"
|
||||
onSelect={() => navigate(clan.path)}
|
||||
>
|
||||
<Typography hierarchy="label" size="xs" weight="medium">
|
||||
{clan.label}
|
||||
</Typography>
|
||||
</DropdownMenu.Item>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</DropdownMenu.Group>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,151 +1,151 @@
|
||||
/* Body */
|
||||
.typography {
|
||||
&.font-weight-normal {
|
||||
&.weight-normal {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&.font-weight-medium {
|
||||
&.weight-medium {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&.font-weight-bold {
|
||||
&.weight-bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&.font-body {
|
||||
&.font-family-regular {
|
||||
&.body {
|
||||
&.family-regular {
|
||||
font-family: "Archivo", sans-serif;
|
||||
}
|
||||
|
||||
&.font-family-condensed {
|
||||
&.family-condensed {
|
||||
font-family: "Archivo SemiCondensed", sans-serif;
|
||||
}
|
||||
|
||||
&.font-size-default {
|
||||
&.size-default {
|
||||
font-size: 1rem;
|
||||
line-height: 1.32;
|
||||
letter-spacing: 0.02rem;
|
||||
}
|
||||
|
||||
&.font-size-s {
|
||||
&.size-s {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.32;
|
||||
letter-spacing: 0.0175rem;
|
||||
}
|
||||
|
||||
&.font-size-xs {
|
||||
&.size-xs {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.32;
|
||||
letter-spacing: 0.0225rem;
|
||||
}
|
||||
|
||||
&.font-size-xxs {
|
||||
&.size-xxs {
|
||||
font-size: 0.6875rem;
|
||||
line-height: 1.32;
|
||||
letter-spacing: 0.00688rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.font-label {
|
||||
&.font-family-condensed {
|
||||
&.label {
|
||||
&.family-condensed {
|
||||
font-family: "Archivo SemiCondensed", sans-serif;
|
||||
|
||||
&.font-size-default {
|
||||
&.size-default {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.32;
|
||||
line-height: 1;
|
||||
letter-spacing: 0.0175rem;
|
||||
}
|
||||
|
||||
&.font-size-s {
|
||||
&.size-s {
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.32;
|
||||
line-height: 1;
|
||||
letter-spacing: 0.0175rem;
|
||||
}
|
||||
|
||||
&.font-size-xs {
|
||||
&.size-xs {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.32;
|
||||
line-height: 1;
|
||||
letter-spacing: 0.0075rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.font-family-mono {
|
||||
&.family-mono {
|
||||
font-family: "Commit Mono", monospace;
|
||||
|
||||
&.font-size-default {
|
||||
&.size-default {
|
||||
font-size: 0.8125rem;
|
||||
line-height: 0;
|
||||
line-height: 1;
|
||||
letter-spacing: normal;
|
||||
}
|
||||
|
||||
&.font-size-s {
|
||||
&.size-s {
|
||||
font-size: 0.75rem;
|
||||
line-height: 0;
|
||||
line-height: 1;
|
||||
letter-spacing: normal;
|
||||
}
|
||||
|
||||
&.font-size-xs {
|
||||
&.size-xs {
|
||||
font-size: 0.6875rem;
|
||||
line-height: 0;
|
||||
line-height: 1;
|
||||
letter-spacing: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.font-title {
|
||||
&.font-family-regular {
|
||||
&.title {
|
||||
&.family-regular {
|
||||
font-family: "Archivo", sans-serif;
|
||||
}
|
||||
|
||||
&.font-size-default {
|
||||
&.size-default {
|
||||
font-size: 1.125rem;
|
||||
line-height: 124%;
|
||||
letter-spacing: 0.03375rem;
|
||||
}
|
||||
|
||||
&.font-size-m {
|
||||
&.size-m {
|
||||
font-size: 1.25rem;
|
||||
line-height: 124%;
|
||||
letter-spacing: 0.0375rem;
|
||||
}
|
||||
|
||||
&.font-size-l {
|
||||
&.size-l {
|
||||
font-size: 1.375rem;
|
||||
line-height: 124%;
|
||||
letter-spacing: 0.04125rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.font-headline {
|
||||
&.font-family-regular {
|
||||
&.headline {
|
||||
&.family-regular {
|
||||
font-family: "Archivo", sans-serif;
|
||||
}
|
||||
|
||||
&.font-size-default {
|
||||
&.size-default {
|
||||
font-size: 1.5rem;
|
||||
line-height: 116%;
|
||||
letter-spacing: 0.015rem;
|
||||
}
|
||||
|
||||
&.font-size-m {
|
||||
&.size-m {
|
||||
font-size: 1.75rem;
|
||||
line-height: 116%;
|
||||
letter-spacing: 0.0175rem;
|
||||
}
|
||||
|
||||
&.font-size-l {
|
||||
&.size-l {
|
||||
font-size: 2rem;
|
||||
line-height: 116%;
|
||||
letter-spacing: 0.06rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.font-teaser {
|
||||
&.font-family-regular {
|
||||
&.teaser {
|
||||
&.family-regular {
|
||||
font-family: "Archivo", sans-serif;
|
||||
}
|
||||
|
||||
&.font-size-default {
|
||||
&.size-default {
|
||||
font-size: 3rem;
|
||||
line-height: normal;
|
||||
letter-spacing: -0.06rem;
|
||||
|
||||
@@ -14,9 +14,7 @@ There are two fonts being used within our typography system:
|
||||
## UI Components
|
||||
|
||||
When creating UI components that a user will interact with,
|
||||
you must use the condensed form of `Body`, `Label` and `Label Mono`.
|
||||
|
||||
<DocsStory of={TypographyStories.BodyCondensed} />
|
||||
you **must use** `Label` or `Label Mono`.
|
||||
|
||||
<DocsStory of={TypographyStories.LabelCondensed} />
|
||||
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||
|
||||
import {
|
||||
AllowedSizes,
|
||||
Color,
|
||||
Family,
|
||||
Hierarchy,
|
||||
Typography,
|
||||
Weight,
|
||||
} from "./Typography";
|
||||
import { Family, Hierarchy, Typography, Weight } from "./Typography";
|
||||
import { Component, For, Show } from "solid-js";
|
||||
import { AllColors } from "@/src/components/v2/colors";
|
||||
|
||||
interface TypographyExamplesProps {
|
||||
weights: Weight[];
|
||||
@@ -19,14 +13,6 @@ interface TypographyExamplesProps {
|
||||
inverted?: boolean;
|
||||
}
|
||||
|
||||
const colors: (Color | "inherit")[] = [
|
||||
"inherit",
|
||||
"primary",
|
||||
"secondary",
|
||||
"tertiary",
|
||||
"quaternary",
|
||||
];
|
||||
|
||||
const TypographyExamples: Component<TypographyExamplesProps> = (props) => (
|
||||
<table
|
||||
class="w-full min-w-max table-auto text-left"
|
||||
@@ -59,7 +45,7 @@ const TypographyExamples: Component<TypographyExamplesProps> = (props) => (
|
||||
</Typography>
|
||||
</Show>
|
||||
<Show when={props.colors}>
|
||||
<For each={colors}>
|
||||
<For each={AllColors}>
|
||||
{(color) => (
|
||||
<>
|
||||
<Typography
|
||||
|
||||
@@ -1,36 +1,14 @@
|
||||
import { type JSX, mergeProps } from "solid-js";
|
||||
import { type JSX } from "solid-js";
|
||||
import { Dynamic } from "solid-js/web";
|
||||
import cx from "classnames";
|
||||
import "./Typography.css";
|
||||
import { Color, fgClass } from "@/src/components/v2/colors";
|
||||
|
||||
export type Tag = "span" | "p" | "h1" | "h2" | "h3" | "h4" | "div";
|
||||
export type Color = "primary" | "secondary" | "tertiary" | "quaternary";
|
||||
export type Hierarchy = "body" | "title" | "headline" | "label" | "teaser";
|
||||
export type Weight = "normal" | "medium" | "bold";
|
||||
export type Family = "regular" | "condensed" | "mono";
|
||||
|
||||
const colorMap: Record<Color, string> = {
|
||||
primary: cx("fg-def-1"),
|
||||
secondary: cx("fg-def-2"),
|
||||
tertiary: cx("fg-def-3"),
|
||||
quaternary: cx("fg-def-4"),
|
||||
};
|
||||
|
||||
const invertedColorMap: Record<Color, string> = {
|
||||
primary: cx("fg-inv-1"),
|
||||
secondary: cx("fg-inv-2"),
|
||||
tertiary: cx("fg-inv-3"),
|
||||
quaternary: cx("fg-inv-4"),
|
||||
};
|
||||
|
||||
const colorFor = (color: Color | "inherit" = "primary", inverted = false) => {
|
||||
if (color === "inherit") {
|
||||
return "text-inherit";
|
||||
}
|
||||
|
||||
return inverted ? invertedColorMap[color] : colorMap[color];
|
||||
};
|
||||
|
||||
// type Size = "default" | "xs" | "s" | "m" | "l";
|
||||
interface SizeForHierarchy {
|
||||
body: {
|
||||
@@ -63,30 +41,30 @@ export type AllowedSizes<H extends Hierarchy> = keyof SizeForHierarchy[H];
|
||||
|
||||
const sizeHierarchyMap: SizeForHierarchy = {
|
||||
body: {
|
||||
default: cx("font-size-default"),
|
||||
s: cx("font-size-s"),
|
||||
xs: cx("font-size-xs"),
|
||||
xxs: cx("font-size-xxs"),
|
||||
default: cx("size-default"),
|
||||
s: cx("size-s"),
|
||||
xs: cx("size-xs"),
|
||||
xxs: cx("size-xxs"),
|
||||
},
|
||||
headline: {
|
||||
default: cx("font-size-default"),
|
||||
m: cx("font-size-m"),
|
||||
l: cx("font-size-l"),
|
||||
default: cx("size-default"),
|
||||
m: cx("size-m"),
|
||||
l: cx("size-l"),
|
||||
},
|
||||
title: {
|
||||
default: cx("font-size-default"),
|
||||
// xs: cx("font-size-xs"),
|
||||
// s: cx("font-size-s"),
|
||||
m: cx("font-size-m"),
|
||||
l: cx("font-size-l"),
|
||||
default: cx("size-default"),
|
||||
// xs: cx("size-xs"),
|
||||
// s: cx("size-s"),
|
||||
m: cx("size-m"),
|
||||
l: cx("size-l"),
|
||||
},
|
||||
label: {
|
||||
default: cx("font-size-default"),
|
||||
s: cx("font-size-s"),
|
||||
xs: cx("font-size-xs"),
|
||||
default: cx("size-default"),
|
||||
s: cx("size-s"),
|
||||
xs: cx("size-xs"),
|
||||
},
|
||||
teaser: {
|
||||
default: cx("font-size-default"),
|
||||
default: cx("size-default"),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -99,50 +77,43 @@ const defaultFamilyMap: Record<Hierarchy, Family> = {
|
||||
};
|
||||
|
||||
const weightMap: Record<Weight, string> = {
|
||||
normal: cx("font-weight-normal"),
|
||||
medium: cx("font-weight-medium"),
|
||||
bold: cx("font-weight-bold"),
|
||||
normal: cx("weight-normal"),
|
||||
medium: cx("weight-medium"),
|
||||
bold: cx("weight-bold"),
|
||||
};
|
||||
|
||||
interface _TypographyProps<H extends Hierarchy> {
|
||||
hierarchy: H;
|
||||
size: AllowedSizes<H>;
|
||||
color?: Color | "inherit";
|
||||
color?: Color;
|
||||
children: JSX.Element;
|
||||
weight?: Weight;
|
||||
family?: Family;
|
||||
inverted?: boolean;
|
||||
tag?: Tag;
|
||||
class?: string;
|
||||
classList?: Record<string, boolean>;
|
||||
}
|
||||
|
||||
export const Typography = <H extends Hierarchy>(props: _TypographyProps<H>) => {
|
||||
const family = () =>
|
||||
`font-family-${props.family || defaultFamilyMap[props.hierarchy]}`;
|
||||
|
||||
const color = () => colorFor(props.color, props.inverted);
|
||||
|
||||
const classList = mergeProps(props.classList, {
|
||||
"font-body": props.hierarchy === "body" || !props.hierarchy,
|
||||
"font-label": props.hierarchy === "label",
|
||||
"font-title": props.hierarchy === "title",
|
||||
"font-headline": props.hierarchy === "headline",
|
||||
"font-teaser": props.hierarchy === "teaser",
|
||||
});
|
||||
`family-${props.family || defaultFamilyMap[props.hierarchy]}`;
|
||||
const hierarchy = () => props.hierarchy || "body";
|
||||
const size = () => sizeHierarchyMap[props.hierarchy][props.size] as string;
|
||||
const weight = () => weightMap[props.weight || "normal"];
|
||||
const color = () => fgClass(props.color, props.inverted);
|
||||
|
||||
return (
|
||||
<Dynamic
|
||||
class={cx(
|
||||
"typography",
|
||||
color(),
|
||||
hierarchy(),
|
||||
family(),
|
||||
weightMap[props.weight || "normal"],
|
||||
sizeHierarchyMap[props.hierarchy][props.size] as string,
|
||||
weight(),
|
||||
size(),
|
||||
color(),
|
||||
props.class,
|
||||
)}
|
||||
component={props.tag || "span"}
|
||||
classList={classList}
|
||||
>
|
||||
{props.children}
|
||||
</Dynamic>
|
||||
|
||||
41
pkgs/clan-app/ui/src/components/v2/colors.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
export type Color =
|
||||
| "primary"
|
||||
| "secondary"
|
||||
| "tertiary"
|
||||
| "quaternary"
|
||||
| "inherit";
|
||||
|
||||
export const AllColors: Color[] = [
|
||||
"primary",
|
||||
"secondary",
|
||||
"tertiary",
|
||||
"quaternary",
|
||||
"inherit",
|
||||
];
|
||||
|
||||
const colorMap: Record<Color, string> = {
|
||||
primary: "fg-def-1",
|
||||
secondary: "fg-def-2",
|
||||
tertiary: "fg-def-3",
|
||||
quaternary: "fg-def-4",
|
||||
inherit: "text-inherit",
|
||||
};
|
||||
|
||||
const invertedColorMap: Record<Color, string> = {
|
||||
primary: "fg-inv-1",
|
||||
secondary: "fg-inv-2",
|
||||
tertiary: "fg-inv-3",
|
||||
quaternary: "fg-inv-4",
|
||||
inherit: "text-inherit",
|
||||
};
|
||||
|
||||
export const fgClass = (
|
||||
color: Color | "inherit" = "primary",
|
||||
inverted = false,
|
||||
) => {
|
||||
if (color === "inherit") {
|
||||
return "text-inherit";
|
||||
}
|
||||
|
||||
return inverted ? invertedColorMap[color] : colorMap[color];
|
||||
};
|
||||
@@ -102,3 +102,13 @@ html {
|
||||
user-select: none;
|
||||
/* Standard */
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import typography from "@tailwindcss/typography";
|
||||
import kobalte from "@kobalte/tailwindcss";
|
||||
import core from "./tailwind/core-plugin";
|
||||
|
||||
@@ -6,7 +5,7 @@ import core from "./tailwind/core-plugin";
|
||||
const config = {
|
||||
content: ["./src/**/*.{js,jsx,ts,tsx}"],
|
||||
theme: {},
|
||||
plugins: [typography, core, kobalte],
|
||||
plugins: [core, kobalte],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import plugin from "tailwindcss/plugin";
|
||||
import { typography } from "./typography";
|
||||
// @ts-expect-error: lib of tailwind has no types
|
||||
import { parseColor } from "tailwindcss/lib/util/color";
|
||||
|
||||
@@ -154,7 +153,7 @@ export default plugin.withOptions(
|
||||
backgroundColor: theme("colors.secondary.700"),
|
||||
},
|
||||
".bg-inv-acc-4": {
|
||||
backgroundColor: theme("colors.secondary.900"),
|
||||
backgroundColor: theme("colors.primary.950"),
|
||||
},
|
||||
|
||||
// bg inverse accent
|
||||
@@ -252,7 +251,7 @@ export default plugin.withOptions(
|
||||
500: toRGB("#526f6f"),
|
||||
600: toRGB("#4b6767"),
|
||||
700: toRGB("#345253"),
|
||||
800: toRGB("#2b4647"),
|
||||
800: toRGB("#2e4a4b"),
|
||||
900: toRGB("#203637"),
|
||||
950: toRGB("#162324"),
|
||||
},
|
||||
@@ -316,7 +315,6 @@ export default plugin.withOptions(
|
||||
"0px 0px 0px 1px white, 0px 0px 0px 2px var(--clr-bg-inv-acc-4, #203637), 2px 2px 0px 0px var(--clr-bg-inv-acc-2, #4F747A) inset",
|
||||
},
|
||||
},
|
||||
...typography,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import defaultTheme from "tailwindcss/defaultTheme";
|
||||
import type { Config } from "tailwindcss";
|
||||
|
||||
export const typography: Partial<Config["theme"]> = {
|
||||
fontFamily: {
|
||||
sans: ["Archivo SemiCondensed", ...defaultTheme.fontFamily.sans],
|
||||
},
|
||||
fontSize: {
|
||||
...defaultTheme.fontSize,
|
||||
title: ["1.125rem", { lineHeight: "124%" }],
|
||||
"title-m": ["1.25rem", { lineHeight: "124%" }],
|
||||
"title-l": ["1.375rem", { lineHeight: "124%" }],
|
||||
label: ["0.8125rem", { lineHeight: "100%" }],
|
||||
"label-s": ["0.75rem", { lineHeight: "100%" }],
|
||||
"label-xs": ["0.6875rem", { lineHeight: "124%" }],
|
||||
},
|
||||
// textColor: {
|
||||
// ...defaultTheme.textColor,
|
||||
// primary: "#0D1416",
|
||||
// secondary: "#2C4347",
|
||||
// },
|
||||
} as const;
|
||||