@bluerobotics/bluevue 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -18
- package/bin/create-blueos-extension.mjs +72 -0
- package/dist/bluevue.js +2464 -778
- package/dist/components/BlueApp.vue.d.ts +50 -0
- package/dist/components/BlueBanner.vue.d.ts +49 -0
- package/dist/components/BlueBannerGroup.vue.d.ts +20 -0
- package/dist/components/BlueButton.vue.d.ts +59 -0
- package/dist/components/BlueCard.vue.d.ts +45 -0
- package/dist/components/BlueCheckbox.vue.d.ts +34 -0
- package/dist/components/BlueChip.vue.d.ts +47 -0
- package/dist/components/BlueConfirmDialog.vue.d.ts +193 -0
- package/dist/components/BlueDialog.vue.d.ts +60 -0
- package/dist/components/BlueFileDrop.vue.d.ts +32 -0
- package/dist/components/BlueIcon.vue.d.ts +26 -0
- package/dist/components/BlueInput.vue.d.ts +1 -0
- package/dist/components/BlueProgressBar.vue.d.ts +28 -0
- package/dist/components/BlueRadioGroup.vue.d.ts +40 -0
- package/dist/components/BlueSection.vue.d.ts +36 -0
- package/dist/components/BlueSnackbar.vue.d.ts +19 -0
- package/dist/components/BlueSpinner.vue.d.ts +25 -0
- package/dist/components/BlueStat.vue.d.ts +21 -0
- package/dist/components/BlueStepsDialog.vue.d.ts +62 -0
- package/dist/components/BlueTable.vue.d.ts +62 -0
- package/dist/components/BlueTabs.vue.d.ts +37 -0
- package/dist/components/BlueTextarea.vue.d.ts +40 -0
- package/dist/components/BlueTooltip.vue.d.ts +44 -0
- package/dist/components/BlueWindRose.vue.d.ts +37 -0
- package/dist/composables/useBlueLoading.d.ts +22 -0
- package/dist/composables/useBlueOs.d.ts +39 -0
- package/dist/composables/useBlueSnackbar.d.ts +30 -0
- package/dist/index.d.ts +30 -0
- package/dist/services/blueos.d.ts +37 -0
- package/dist/style.css +1 -1
- package/dist/types/banner.d.ts +16 -0
- package/dist/utils/files.d.ts +13 -0
- package/dist/utils/theme.d.ts +22 -0
- package/package.json +8 -2
- package/src/components/BlueApp.vue +84 -0
- package/src/components/BlueBanner.vue +196 -0
- package/src/components/BlueBannerGroup.vue +47 -0
- package/src/components/BlueButton.vue +134 -0
- package/src/components/BlueButtonGroup.vue +26 -24
- package/src/components/BlueCard.vue +62 -0
- package/src/components/BlueCheckbox.vue +100 -0
- package/src/components/BlueChip.vue +77 -0
- package/src/components/BlueConfirmDialog.vue +73 -0
- package/src/components/BlueDialog.vue +135 -0
- package/src/components/BlueFileDrop.vue +94 -0
- package/src/components/BlueIcon.vue +37 -0
- package/src/components/BlueInput.vue +21 -11
- package/src/components/BlueLoadingDialog.vue +22 -53
- package/src/components/BlueProgressBar.vue +46 -0
- package/src/components/BluePromptDialog.vue +51 -99
- package/src/components/BlueRadioGroup.vue +104 -0
- package/src/components/BlueSection.vue +33 -0
- package/src/components/BlueSelect.vue +12 -10
- package/src/components/BlueSlider.vue +1 -3
- package/src/components/BlueSnackbar.vue +79 -0
- package/src/components/BlueSpinner.vue +37 -0
- package/src/components/BlueStat.vue +26 -0
- package/src/components/BlueStepsDialog.vue +180 -0
- package/src/components/BlueSwitch.vue +16 -13
- package/src/components/BlueTable.vue +154 -0
- package/src/components/BlueTabs.vue +77 -0
- package/src/components/BlueTextarea.vue +101 -0
- package/src/components/BlueTooltip.vue +88 -0
- package/src/components/BlueWindRose.vue +267 -0
- package/src/composables/useBlueLoading.ts +38 -0
- package/src/composables/useBlueOs.ts +154 -0
- package/src/composables/useBlueSnackbar.ts +89 -0
- package/src/index.ts +40 -0
- package/src/services/blueos.ts +95 -0
- package/src/styles/bluevue.css +136 -0
- package/src/types/banner.ts +17 -0
- package/src/utils/files.ts +44 -0
- package/src/utils/theme.ts +37 -0
- package/templates/extension/.github/workflows/deploy.yml +23 -0
- package/templates/extension/Dockerfile +56 -0
- package/templates/extension/README.md +35 -0
- package/templates/extension/backend/main.py +45 -0
- package/templates/extension/backend/requirements.txt +2 -0
- package/templates/extension/frontend/.eslintrc.cjs +20 -0
- package/templates/extension/frontend/env.d.ts +7 -0
- package/templates/extension/frontend/index.html +12 -0
- package/templates/extension/frontend/package.json +30 -0
- package/templates/extension/frontend/src/App.vue +51 -0
- package/templates/extension/frontend/src/main.ts +8 -0
- package/templates/extension/frontend/src/styles/global.css +11 -0
- package/templates/extension/frontend/tsconfig.json +25 -0
- package/templates/extension/frontend/tsconfig.node.json +11 -0
- package/templates/extension/frontend/vite.config.ts +24 -0
package/dist/style.css
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/*! tailwindcss v4.1.11 | MIT License | https://tailwindcss.com */
|
|
2
|
-
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial;--tw-ease:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--color-gray-100:oklch(96.7% .003 264.542);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-700:oklch(37.3% .034 259.733);--color-black:#000;--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--font-weight-medium:500;--font-weight-bold:700;--tracking-wide:.025em;--leading-tight:1.25;--leading-snug:1.375;--leading-relaxed:1.625;--radius-lg:.5rem;--ease-out:cubic-bezier(0,0,.2,1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1)}}@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-0{inset:calc(var(--spacing)*0)}.inset-x-\[18\%\]{inset-inline:18%}.top-0{top:calc(var(--spacing)*0)}.top-1\/2{top:50%}.top-3{top:calc(var(--spacing)*3)}.top-\[4px\]{top:4px}.right-0{right:calc(var(--spacing)*0)}.right-3{right:calc(var(--spacing)*3)}.right-\[5px\]{right:5px}.bottom-1\/2{bottom:50%}.bottom-\[4px\]{bottom:4px}.bottom-full{bottom:100%}.left-1\/2{left:50%}.left-\[-20px\]{left:-20px}.left-\[5px\]{left:5px}.z-10{z-index:10}.z-50{z-index:50}.z-\[20\]{z-index:20}.z-\[666\]{z-index:666}.z-\[1000\]{z-index:1000}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-3{margin-inline:calc(var(--spacing)*3)}.mx-auto{margin-inline:auto}.my-2{margin-block:calc(var(--spacing)*2)}.my-6{margin-block:calc(var(--spacing)*6)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-\[2px\]{margin-top:2px}.mt-\[15px\]{margin-top:15px}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-2{margin-right:calc(var(--spacing)*2)}.mr-6{margin-right:calc(var(--spacing)*6)}.mr-\[2px\]{margin-right:2px}.mr-\[3px\]{margin-right:3px}.-mb-\[1px\]{margin-bottom:-1px}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.ml-3{margin-left:calc(var(--spacing)*3)}.ml-auto{margin-left:auto}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.h-2{height:calc(var(--spacing)*2)}.h-3\/4{height:75%}.h-7{height:calc(var(--spacing)*7)}.h-\[100px\]{height:100px}.h-full{height:100%}.h-px{height:1px}.max-h-\[60vh\]{max-height:60vh}.min-h-screen{min-height:100vh}.w-2{width:calc(var(--spacing)*2)}.w-\[1px\]{width:1px}.w-\[6px\]{width:6px}.w-\[100px\]{width:100px}.w-\[320px\]{width:320px}.w-\[624px\]{width:624px}.w-\[760px\]{width:760px}.w-\[calc\(50\%-4px\)\]{width:calc(50% - 4px)}.w-full{width:100%}.w-max{width:max-content}.max-w-\[45\%\]{max-width:45%}.max-w-\[150px\]{max-width:150px}.max-w-\[280px\]{max-width:280px}.max-w-full{max-width:100%}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-\[30px\]{min-width:30px}.min-w-\[60px\]{min-width:60px}.min-w-\[90px\]{min-width:90px}.min-w-\[140px\]{min-width:140px}.min-w-\[220px\]{min-width:220px}.flex-1{flex:1}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.shrink-\[1000\]{flex-shrink:1000}.grow{flex-grow:1}.-translate-x-1\/2{--tw-translate-x:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-1\/2{--tw-translate-y:calc(1/2*100%);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-\[0\.22em\]{--tw-translate-y:.22em;translate:var(--tw-translate-x)var(--tw-translate-y)}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.cursor-help{cursor:help}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize{resize:both}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-5{gap:calc(var(--spacing)*5)}.self-center{align-self:center}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-\[4px\]{border-radius:4px}.rounded-\[6px\]{border-radius:6px}.rounded-\[8px\]{border-radius:8px}.rounded-lg{border-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-\[1px\]{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-solid{--tw-border-style:solid;border-style:solid}.border-\[\#555555\]{border-color:#555}.border-\[\#FFFFFF22\]{border-color:#fff2}.border-\[\#dddddd\]{border-color:#ddd}.border-\[\#ffffff0d\]{border-color:#ffffff0d}.border-\[\#ffffff1a\]{border-color:#ffffff1a}.border-\[var\(--bluevue-error\)\]{border-color:var(--bluevue-error)}.border-gray-300{border-color:var(--color-gray-300)}.border-white{border-color:var(--color-white)}.bg-\[\#2d2d2d\]{background-color:#2d2d2d}.bg-\[\#00000011\]{background-color:#0001}.bg-\[\#00000033\]{background-color:#0003}.bg-\[\#6699cc\]{background-color:#69c}.bg-\[\#132029\]{background-color:#132029}.bg-\[\#464646AA\]{background-color:#464646aa}.bg-\[\#FFFFFF22\]{background-color:#fff2}.bg-\[\#ffffff0d\]{background-color:#ffffff0d}.bg-\[\#ffffff11\]{background-color:#fff1}.bg-\[\#ffffff22\]{background-color:#fff2}.bg-\[var\(--bluevue-primary\)\]{background-color:var(--bluevue-primary)}.bg-\[var\(--bluevue-surface\)\]{background-color:var(--bluevue-surface)}.bg-gray-700{background-color:var(--color-gray-700)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.p-6{padding:calc(var(--spacing)*6)}.px-0{padding-inline:calc(var(--spacing)*0)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-5{padding-inline:calc(var(--spacing)*5)}.px-6{padding-inline:calc(var(--spacing)*6)}.px-13{padding-inline:calc(var(--spacing)*13)}.px-\[6px\]{padding-inline:6px}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.pt-4{padding-top:calc(var(--spacing)*4)}.pt-7{padding-top:calc(var(--spacing)*7)}.pr-1{padding-right:calc(var(--spacing)*1)}.pr-4{padding-right:calc(var(--spacing)*4)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.pb-3{padding-bottom:calc(var(--spacing)*3)}.pb-5{padding-bottom:calc(var(--spacing)*5)}.pl-1{padding-left:calc(var(--spacing)*1)}.pl-4{padding-left:calc(var(--spacing)*4)}.text-center{text-align:center}.text-left{text-align:left}.text-start{text-align:start}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[14px\]{font-size:14px}.text-\[16px\]{font-size:16px}.text-\[18px\]{font-size:18px}.text-\[20px\]{font-size:20px}.text-\[22px\]{font-size:22px}.text-\[24px\]{font-size:24px}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.whitespace-nowrap{white-space:nowrap}.text-\[\#42A5F5\]{color:#42a5f5}.text-\[\#00000066\]{color:#0006}.text-\[\#FFFFFF99\]{color:#fff9}.text-\[\#ff6b6b\]{color:#ff6b6b}.text-\[\#ffffff44\]{color:#fff4}.text-\[\#ffffff66\]{color:#fff6}.text-\[\#ffffff88\]{color:#fff8}.text-\[\#ffffff99\]{color:#fff9}.text-\[\#ffffffaa\]{color:#fffa}.text-\[var\(--bluevue-error\)\]{color:var(--bluevue-error)}.text-black{color:var(--color-black)}.text-gray-500{color:var(--color-gray-500)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.opacity-0{opacity:0}.opacity-20{opacity:.2}.opacity-30{opacity:.3}.opacity-40{opacity:.4}.opacity-60{opacity:.6}.opacity-80{opacity:.8}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}@media (hover:hover){.group-hover\:block:is(:where(.group):hover *){display:block}}.placeholder\:text-\[\#00000055\]::placeholder{color:#0005}.placeholder\:text-\[\#ffffff44\]::placeholder{color:#fff4}@media (hover:hover){.hover\:bg-\[\#333333\]:hover{background-color:#333}.hover\:bg-\[\#ffffff11\]:hover{background-color:#fff1}.hover\:bg-\[\#ffffff22\]:hover{background-color:#fff2}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:text-white:hover{color:var(--color-white)}.hover\:brightness-125:hover{--tw-brightness:brightness(125%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-\[\#9e9e9e80\]:disabled{background-color:#9e9e9e80}.disabled\:text-\[\#afafaf\]:disabled{color:#afafaf}.disabled\:opacity-30:disabled{opacity:.3}@media (hover:hover){.disabled\:hover\:brightness-100:disabled:hover{--tw-brightness:brightness(100%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}}}:root{--bluevue-primary:#0b5087;--bluevue-error:#cf6679;--bluevue-surface:#363636;--bluevue-accent:#69c;--bluevue-panel-bg:#1e1e1ef5;--bluevue-hairline:#ffffff14;--bluevue-elevation-1:0 2px 1px -1px #0003,0 1px 1px 0 #00000024,0 1px 3px 0 #0000001f;--bluevue-elevation-5:0 3px 5px -1px #0003,0 5px 8px 0 #00000024,0 1px 14px 0 #0000001f;--bluevue-inset-1:inset 0 1px 2px #00000056,inset 0 .5px .5px #0000003a,inset 0 -.5px 0 #ffffff0b}.bluevue-elevation-1{box-shadow:var(--bluevue-elevation-1)}.bluevue-elevation-5{box-shadow:var(--bluevue-elevation-5)}.bluevue-inset-1{box-shadow:var(--bluevue-inset-1)}.bluevue-panel{background-color:var(--bluevue-panel-bg);-webkit-backdrop-filter:blur(8px);border:1px solid var(--bluevue-hairline);box-shadow:0 4px 4px #0003,0 8px 12px 6px #00000026}.bluevue-dialog{background:0 0;border:none;max-width:min(92vw,624px);max-height:92vh;margin:auto;padding:0;overflow:visible}.bluevue-dialog:focus,.bluevue-dialog:focus-visible,.bluevue-popover:focus,.bluevue-popover:focus-visible{outline:none}.bluevue-dialog::backdrop{-webkit-backdrop-filter:blur(8px);background:#000000e0}.bluevue-popover{background:0 0;border:none;margin:0;padding:0;position:fixed;overflow:visible}.bluevue-expand-enter-active,.bluevue-expand-leave-active{transition:max-height .3s;overflow:hidden}.bluevue-expand-enter-from,.bluevue-expand-leave-to{max-height:0}.bluevue-expand-enter-to,.bluevue-expand-leave-from{max-height:500px}.bluevue-loading__icon{animation:4s infinite bluevue-prop}@keyframes bluevue-prop{0%{animation-timing-function:cubic-bezier(.45,0,.55,1);transform:rotate(0)}50%{animation-timing-function:cubic-bezier(.35,0,.45,1);transform:rotate(80deg)}to{transform:rotate(1440deg)}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-gray-100:oklch(96.7% .003 264.542);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-500:oklch(55.1% .027 264.364);--color-black:#000;--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--font-weight-medium:500;--font-weight-bold:700;--tracking-wide:.025em;--leading-tight:1.25;--leading-snug:1.375;--leading-relaxed:1.625;--radius-lg:.5rem;--ease-out:cubic-bezier(0,0,.2,1);--blur-sm:8px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1)}}@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing)*0)}.inset-x-0{inset-inline:calc(var(--spacing)*0)}.inset-x-\[18\%\]{inset-inline:18%}.top-0{top:calc(var(--spacing)*0)}.top-1\/2{top:50%}.top-3{top:calc(var(--spacing)*3)}.top-4{top:calc(var(--spacing)*4)}.top-\[4px\]{top:4px}.right-3{right:calc(var(--spacing)*3)}.right-4{right:calc(var(--spacing)*4)}.right-\[5px\]{right:5px}.-bottom-px{bottom:-1px}.bottom-1\/2{bottom:50%}.bottom-4{bottom:calc(var(--spacing)*4)}.bottom-\[4px\]{bottom:4px}.left-4{left:calc(var(--spacing)*4)}.left-\[-20px\]{left:-20px}.left-\[5px\]{left:5px}.z-10{z-index:10}.z-50{z-index:50}.z-\[1\]{z-index:1}.z-\[20\]{z-index:20}.z-\[666\]{z-index:666}.z-\[2000\]{z-index:2000}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-3{margin-inline:calc(var(--spacing)*3)}.mx-auto{margin-inline:auto}.my-2{margin-block:calc(var(--spacing)*2)}.my-6{margin-block:calc(var(--spacing)*6)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-8{margin-top:calc(var(--spacing)*8)}.mt-\[1px\]{margin-top:1px}.mt-\[2px\]{margin-top:2px}.mt-\[15px\]{margin-top:15px}.-mr-1{margin-right:calc(var(--spacing)*-1)}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-2{margin-right:calc(var(--spacing)*2)}.mr-6{margin-right:calc(var(--spacing)*6)}.mr-\[2px\]{margin-right:2px}.mr-\[3px\]{margin-right:3px}.-mb-\[1px\]{margin-bottom:-1px}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.mb-8{margin-bottom:calc(var(--spacing)*8)}.ml-3{margin-left:calc(var(--spacing)*3)}.ml-auto{margin-left:auto}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.h-0{height:calc(var(--spacing)*0)}.h-2{height:calc(var(--spacing)*2)}.h-3\/4{height:75%}.h-6{height:calc(var(--spacing)*6)}.h-7{height:calc(var(--spacing)*7)}.h-\[2px\]{height:2px}.h-\[8px\]{height:8px}.h-\[16px\]{height:16px}.h-\[18px\]{height:18px}.h-\[22px\]{height:22px}.h-\[100px\]{height:100px}.h-full{height:100%}.h-px{height:1px}.max-h-\[60vh\]{max-height:60vh}.max-h-\[220px\]{max-height:220px}.\!min-h-full{min-height:100%!important}.min-h-screen{min-height:100vh}.\!w-auto{width:auto!important}.w-0{width:calc(var(--spacing)*0)}.w-1\/2{width:50%}.w-2{width:calc(var(--spacing)*2)}.w-6{width:calc(var(--spacing)*6)}.w-7{width:calc(var(--spacing)*7)}.w-\[1px\]{width:1px}.w-\[6px\]{width:6px}.w-\[8px\]{width:8px}.w-\[16px\]{width:16px}.w-\[18px\]{width:18px}.w-\[84px\]{width:84px}.w-\[100px\]{width:100px}.w-\[170px\]{width:170px}.w-\[190px\]{width:190px}.w-\[320px\]{width:320px}.w-\[340px\]{width:340px}.w-\[420px\]{width:420px}.w-\[440px\]{width:440px}.w-\[560px\]{width:560px}.w-\[calc\(50\%-4px\)\]{width:calc(50% - 4px)}.w-fit{width:fit-content}.w-full{width:100%}.max-w-\[45\%\]{max-width:45%}.max-w-\[150px\]{max-width:150px}.max-w-\[280px\]{max-width:280px}.max-w-\[640px\]{max-width:640px}.max-w-\[680px\]{max-width:680px}.max-w-\[900px\]{max-width:900px}.max-w-\[min\(90vw\,420px\)\]{max-width:min(90vw,420px)}.max-w-full{max-width:100%}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-\[30px\]{min-width:30px}.min-w-\[60px\]{min-width:60px}.min-w-\[90px\]{min-width:90px}.min-w-\[120px\]{min-width:120px}.min-w-\[140px\]{min-width:140px}.min-w-\[220px\]{min-width:220px}.flex-1{flex:1}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.shrink-\[1000\]{flex-shrink:1000}.grow{flex-grow:1}.border-collapse{border-collapse:collapse}.-translate-y-1\/2{--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-1\/2{--tw-translate-y:calc(1/2*100%);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-\[0\.22em\]{--tw-translate-y:.22em;translate:var(--tw-translate-x)var(--tw-translate-y)}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.cursor-context-menu{cursor:context-menu}.cursor-help{cursor:help}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.resize{resize:both}.resize-y{resize:vertical}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing)*1)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-5{gap:calc(var(--spacing)*5)}.gap-6{gap:calc(var(--spacing)*6)}.gap-10{gap:calc(var(--spacing)*10)}.gap-x-5{column-gap:calc(var(--spacing)*5)}.gap-y-2{row-gap:calc(var(--spacing)*2)}.self-center{align-self:center}.self-start{align-self:flex-start}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-\[4px\]{border-radius:4px}.rounded-\[6px\]{border-radius:6px}.rounded-\[8px\]{border-radius:8px}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-t-\[8px\]{border-top-left-radius:8px;border-top-right-radius:8px}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-\[1px\]{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-solid{--tw-border-style:solid;border-style:solid}.border-\[\#0000000a\]{border-color:#0000000a}.border-\[\#00000014\]{border-color:#00000014}.border-\[\#00000033\]{border-color:#0003}.border-\[\#00000044\]{border-color:#0004}.border-\[\#555555\]{border-color:#555}.border-\[\#FFFFFF22\]{border-color:#fff2}.border-\[\#dddddd\]{border-color:#ddd}.border-\[\#ffffff0a\]{border-color:#ffffff0a}.border-\[\#ffffff0d\]{border-color:#ffffff0d}.border-\[\#ffffff1a\]{border-color:#ffffff1a}.border-\[\#ffffff14\]{border-color:#ffffff14}.border-\[\#ffffff33\]{border-color:#fff3}.border-\[\#ffffff44\]{border-color:#fff4}.border-\[var\(--bluevue-accent\)\]{border-color:var(--bluevue-accent)}.border-\[var\(--bluevue-error\)\]{border-color:var(--bluevue-error)}.border-\[var\(--bluevue-primary\)\]{border-color:var(--bluevue-primary)}.border-gray-300{border-color:var(--color-gray-300)}.border-transparent{border-color:#0000}.border-white{border-color:var(--color-white)}.bg-\[\#0C577B\]{background-color:#0c577b}.bg-\[\#1e1e1ecc\]{background-color:#1e1e1ecc}.bg-\[\#2d2d2d\]{background-color:#2d2d2d}.bg-\[\#00000006\]{background-color:#00000006}.bg-\[\#00000008\]{background-color:#00000008}.bg-\[\#9e9e9e80\]{background-color:#9e9e9e80}.bg-\[\#00000011\]{background-color:#0001}.bg-\[\#00000022\]{background-color:#0002}.bg-\[\#00000033\]{background-color:#0003}.bg-\[\#00000044\]{background-color:#0004}.bg-\[\#6699cc\]{background-color:#69c}.bg-\[\#333333f2\]{background-color:#333333f2}.bg-\[\#464646AA\]{background-color:#464646aa}.bg-\[\#15151577\]{background-color:#15151577}.bg-\[\#FFFFFF22\]{background-color:#fff2}.bg-\[\#f5f5f5cc\]{background-color:#f5f5f5cc}.bg-\[\#ffffff0d\]{background-color:#ffffff0d}.bg-\[\#ffffff1a\]{background-color:#ffffff1a}.bg-\[\#ffffff06\]{background-color:#ffffff06}.bg-\[\#ffffff08\]{background-color:#ffffff08}.bg-\[\#ffffff11\]{background-color:#fff1}.bg-\[\#ffffff22\]{background-color:#fff2}.bg-\[var\(--bluevue-accent\)\],.bg-\[var\(--bluevue-accent\)\]\/10{background-color:var(--bluevue-accent)}@supports (color:color-mix(in lab, red, red)){.bg-\[var\(--bluevue-accent\)\]\/10{background-color:color-mix(in oklab,var(--bluevue-accent)10%,transparent)}}.bg-\[var\(--bluevue-primary\)\]{background-color:var(--bluevue-primary)}.bg-\[var\(--bluevue-surface\)\]{background-color:var(--bluevue-surface)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-gradient-to-b{--tw-gradient-position:to bottom in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-\[\#0C577B\]{--tw-gradient-from:#0c577b;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-\[\#07293C\]{--tw-gradient-to:#07293c;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.p-3{padding:calc(var(--spacing)*3)}.p-\[2px\]{padding:2px}.px-0{padding-inline:calc(var(--spacing)*0)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-5{padding-inline:calc(var(--spacing)*5)}.px-6{padding-inline:calc(var(--spacing)*6)}.px-13{padding-inline:calc(var(--spacing)*13)}.px-\[9px\]{padding-inline:9px}.px-\[10px\]{padding-inline:10px}.px-\[11px\]{padding-inline:11px}.px-\[15px\]{padding-inline:15px}.px-\[19px\]{padding-inline:19px}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-5{padding-block:calc(var(--spacing)*5)}.py-6{padding-block:calc(var(--spacing)*6)}.py-10{padding-block:calc(var(--spacing)*10)}.py-\[2px\]{padding-block:2px}.py-\[6px\]{padding-block:6px}.pt-3{padding-top:calc(var(--spacing)*3)}.pt-4{padding-top:calc(var(--spacing)*4)}.pt-7{padding-top:calc(var(--spacing)*7)}.pt-\[6px\]{padding-top:6px}.pr-1{padding-right:calc(var(--spacing)*1)}.pr-2{padding-right:calc(var(--spacing)*2)}.pr-4{padding-right:calc(var(--spacing)*4)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.pb-3{padding-bottom:calc(var(--spacing)*3)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.pb-5{padding-bottom:calc(var(--spacing)*5)}.pl-1{padding-left:calc(var(--spacing)*1)}.pl-2{padding-left:calc(var(--spacing)*2)}.pl-3{padding-left:calc(var(--spacing)*3)}.pl-4{padding-left:calc(var(--spacing)*4)}.text-center{text-align:center}.text-end{text-align:end}.text-left{text-align:left}.text-start{text-align:start}.align-top{vertical-align:top}.font-mono{font-family:var(--font-mono)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[14px\]{font-size:14px}.text-\[15px\]{font-size:15px}.text-\[16px\]{font-size:16px}.text-\[18px\]{font-size:18px}.text-\[20px\]{font-size:20px}.text-\[24px\]{font-size:24px}.text-\[26px\]{font-size:26px}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.tracking-\[0\.14em\]{--tw-tracking:.14em;letter-spacing:.14em}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.whitespace-nowrap{white-space:nowrap}.text-\[\#000000cc\]{color:#000c}.text-\[\#8FD3F4\]{color:#8fd3f4}.text-\[\#00000066\]{color:#0006}.text-\[\#66BB6A\]{color:#66bb6a}.text-\[\#00000088\]{color:#0008}.text-\[\#00000099\]{color:#0009}.text-\[\#D6E9F5\]{color:#d6e9f5}.text-\[\#FFFFFF99\]{color:#fff9}.text-\[\#afafaf\]{color:#afafaf}.text-\[\#ff6b6b\]{color:#ff6b6b}.text-\[\#ffffff44\]{color:#fff4}.text-\[\#ffffff66\]{color:#fff6}.text-\[\#ffffff77\]{color:#fff7}.text-\[\#ffffff88\]{color:#fff8}.text-\[\#ffffff99\]{color:#fff9}.text-\[\#ffffffaa\]{color:#fffa}.text-\[\#ffffffcc\]{color:#fffc}.text-\[var\(--bluevue-error\)\]{color:var(--bluevue-error)}.text-black{color:var(--color-black)}.text-gray-500{color:var(--color-gray-500)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.no-underline{text-decoration-line:none}.underline{text-decoration-line:underline}.decoration-dotted{text-decoration-style:dotted}.opacity-0{opacity:0}.opacity-20{opacity:.2}.opacity-30{opacity:.3}.opacity-40{opacity:.4}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.opacity-80{opacity:.8}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.outline-2{outline-style:var(--tw-outline-style);outline-width:2px}.outline-\[var\(--bluevue-error\)\]{outline-color:var(--bluevue-error)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,visibility,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[width\]{transition-property:width;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.peer-focus-visible\:outline:is(:where(.peer):focus-visible~*){outline-style:var(--tw-outline-style);outline-width:1px}.peer-focus-visible\:outline-2:is(:where(.peer):focus-visible~*){outline-style:var(--tw-outline-style);outline-width:2px}.peer-focus-visible\:outline-offset-2:is(:where(.peer):focus-visible~*){outline-offset:2px}.peer-focus-visible\:outline-\[var\(--bluevue-accent\)\]:is(:where(.peer):focus-visible~*){outline-color:var(--bluevue-accent)}.placeholder\:text-\[\#00000055\]::placeholder{color:#0005}.placeholder\:text-\[\#ffffff44\]::placeholder{color:#fff4}.placeholder\:text-\[\#ffffff55\]::placeholder{color:#fff5}@media (hover:hover){.hover\:bg-\[\#0000000a\]:hover{background-color:#0000000a}.hover\:bg-\[\#00000006\]:hover{background-color:#00000006}.hover\:bg-\[\#00000011\]:hover{background-color:#0001}.hover\:bg-\[\#00000022\]:hover{background-color:#0002}.hover\:bg-\[\#333333\]:hover{background-color:#333}.hover\:bg-\[\#ffffff0a\]:hover{background-color:#ffffff0a}.hover\:bg-\[\#ffffff08\]:hover{background-color:#ffffff08}.hover\:bg-\[\#ffffff11\]:hover{background-color:#fff1}.hover\:bg-\[\#ffffff14\]:hover{background-color:#ffffff14}.hover\:bg-\[\#ffffff22\]:hover{background-color:#fff2}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:text-black:hover{color:var(--color-black)}.hover\:text-white:hover{color:var(--color-white)}.hover\:opacity-100:hover{opacity:1}.hover\:brightness-125:hover{--tw-brightness:brightness(125%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}}.focus\:outline:focus{outline-style:var(--tw-outline-style);outline-width:1px}.focus\:outline-2:focus{outline-style:var(--tw-outline-style);outline-width:2px}.focus\:outline-offset-0:focus{outline-offset:0px}.focus\:outline-\[var\(--bluevue-accent\)\]:focus{outline-color:var(--bluevue-accent)}@media (min-width:40rem){.sm\:inline{display:inline}}}:root{--bluevue-primary:#0b5087;--bluevue-error:#cf6679;--bluevue-surface:#363636;--bluevue-accent:#69c;--bluevue-panel-bg:#1e1e1ef5;--bluevue-hairline:#ffffff14;--bluevue-elevation-1:0 2px 1px -1px #0003,0 1px 1px 0 #00000024,0 1px 3px 0 #0000001f;--bluevue-elevation-5:0 3px 5px -1px #0003,0 5px 8px 0 #00000024,0 1px 14px 0 #0000001f;--bluevue-inset-1:inset 0 1px 2px #00000056,inset 0 .5px .5px #0000003a,inset 0 -.5px 0 #ffffff0b;--bluevue-elevation-1-soft:0 2px 1px -1px #0000001a,0 1px 1px 0 #00000012,0 1px 3px 0 #0000000f}.bluevue-elevation-1{box-shadow:var(--bluevue-elevation-1)}.bluevue-elevation-5{box-shadow:var(--bluevue-elevation-5)}.bluevue-inset-1{box-shadow:var(--bluevue-inset-1)}.bluevue-elevation-1-soft{box-shadow:var(--bluevue-elevation-1-soft)}.bluevue-panel{background-color:var(--bluevue-panel-bg);-webkit-backdrop-filter:blur(8px);border:1px solid var(--bluevue-hairline);box-shadow:0 4px 4px #0003,0 8px 12px 6px #00000026}.bluevue-dialog{background:0 0;border:none;max-width:min(92vw,624px);max-height:92vh;margin:auto;padding:0;overflow:visible}.bluevue-dialog:focus,.bluevue-dialog:focus-visible,.bluevue-popover:focus,.bluevue-popover:focus-visible{outline:none}.bluevue-dialog::backdrop{-webkit-backdrop-filter:blur(8px);background:#000000e0}.bluevue-popover{background:0 0;border:none;margin:0;padding:0;position:fixed;overflow:visible}.mdi.bluevue-icon{flex-shrink:0;justify-content:center;align-items:center;line-height:1;display:inline-flex}.bluevue-icon--spin{animation:1.2s linear infinite bluevue-spin}.bluevue-spinner{box-sizing:border-box;border-style:solid;border-color:#ffffff2e;border-top-color:var(--bluevue-accent);border-radius:9999px;flex-shrink:0;animation:.8s linear infinite bluevue-spin;display:inline-block}@keyframes bluevue-spin{to{transform:rotate(360deg)}}.bluevue-progress__sweep{width:40%;animation:1.4s ease-in-out infinite bluevue-progress-sweep}@keyframes bluevue-progress-sweep{0%{transform:translate(-100%)}to{transform:translate(250%)}}@media (prefers-reduced-motion:reduce){.bluevue-icon--spin,.bluevue-spinner,.bluevue-progress__sweep{animation-duration:3s}}.bluevue-banner{box-sizing:border-box;height:26px;padding:4px;font-size:12px;line-height:16px;overflow:hidden}.bluevue-banner__icon{justify-content:center;align-items:center;width:16px;height:16px;font-size:16px;line-height:16px;display:flex}.bluevue-banner__text{text-overflow:ellipsis;white-space:nowrap;padding:0 8px;overflow:hidden}.bluevue-banner__reveal{transition:width .26s ease-in-out}.bluevue-banner--measuring .bluevue-banner__reveal{transition:none}@media (prefers-reduced-motion:reduce){.bluevue-banner__reveal{transition:none}}.bluevue-notice-enter-active,.bluevue-notice-leave-active,.bluevue-notice-move{transition:opacity .18s,transform .18s}.bluevue-notice-enter-from,.bluevue-notice-leave-to{opacity:0;transform:translateY(8px)}.bluevue-notice-leave-active{position:absolute}.bluevue-expand-enter-active,.bluevue-expand-leave-active{transition:max-height .3s;overflow:hidden}.bluevue-expand-enter-from,.bluevue-expand-leave-to{max-height:0}.bluevue-expand-enter-to,.bluevue-expand-leave-from{max-height:500px}.bluevue-loading__icon{animation:4s infinite bluevue-prop}@keyframes bluevue-prop{0%{animation-timing-function:cubic-bezier(.45,0,.55,1);transform:rotate(0)}50%{animation-timing-function:cubic-bezier(.35,0,.45,1);transform:rotate(80deg)}to{transform:rotate(1440deg)}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type BannerSeverity = 'error' | 'warning' | 'info' | 'success';
|
|
2
|
+
/** A banner's message and look, which is all a group needs to lay one out. */
|
|
3
|
+
export interface BannerContent {
|
|
4
|
+
/** The message, which the banner is only as wide as while it is open. */
|
|
5
|
+
text: string;
|
|
6
|
+
/** Which of the standard looks to take (default 'info'). */
|
|
7
|
+
severity?: BannerSeverity;
|
|
8
|
+
/** An mdi class, in place of the severity's icon. */
|
|
9
|
+
icon?: string;
|
|
10
|
+
/** Icon and text colour, in place of the severity's. */
|
|
11
|
+
color?: string;
|
|
12
|
+
/** Fill, in place of the severity's. */
|
|
13
|
+
background?: string;
|
|
14
|
+
/** Hairline, in place of the severity's. */
|
|
15
|
+
border?: string;
|
|
16
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Saves an object as a JSON file the browser downloads. What an extension's "export configuration"
|
|
3
|
+
* button does.
|
|
4
|
+
* @param {unknown} data Anything JSON can hold.
|
|
5
|
+
* @param {string} filename Suggested name. '.json' is appended when it is missing.
|
|
6
|
+
*/
|
|
7
|
+
export declare function downloadJson(data: unknown, filename: string): void;
|
|
8
|
+
/**
|
|
9
|
+
* Asks for a JSON file and parses it. Resolves to null when the picker is closed without a choice.
|
|
10
|
+
* @param {string} accept File types to offer (default '.json,application/json').
|
|
11
|
+
* @returns {Promise<T | null>} The parsed contents, or null. A malformed file rejects.
|
|
12
|
+
*/
|
|
13
|
+
export declare function pickJsonFile<T = unknown>(accept?: string): Promise<T | null>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Every token the components read, in the names they are written with. */
|
|
2
|
+
export interface BlueTheme {
|
|
3
|
+
/** Fills a committing button, a selected segment, a slider's pill. */
|
|
4
|
+
primary?: string;
|
|
5
|
+
/** The lighter blue used where the primary would disappear into a dark inset, such as a focus ring. */
|
|
6
|
+
accent?: string;
|
|
7
|
+
/** Anything that went wrong, or is about to. */
|
|
8
|
+
error?: string;
|
|
9
|
+
/** The card an extension's page is laid out on. */
|
|
10
|
+
surface?: string;
|
|
11
|
+
/** The frosted panel dialogs and menus are drawn on. */
|
|
12
|
+
panelBg?: string;
|
|
13
|
+
/** The line dividing one part of a panel from the next. */
|
|
14
|
+
hairline?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Repaints the kit in an extension's own colours, or in the colours BlueOS reports for the vehicle.
|
|
18
|
+
* Only the tokens given are touched, so the rest keep the values the stylesheet ships with.
|
|
19
|
+
* @param {BlueTheme} theme The tokens to change.
|
|
20
|
+
* @param {HTMLElement} target Where to set them (default the document root, so they reach everything).
|
|
21
|
+
*/
|
|
22
|
+
export declare function applyBlueTheme(theme: BlueTheme, target?: HTMLElement): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bluerobotics/bluevue",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Blue Robotics Vue components for common UI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -21,8 +21,13 @@
|
|
|
21
21
|
],
|
|
22
22
|
"files": [
|
|
23
23
|
"dist",
|
|
24
|
-
"src"
|
|
24
|
+
"src",
|
|
25
|
+
"bin",
|
|
26
|
+
"templates"
|
|
25
27
|
],
|
|
28
|
+
"bin": {
|
|
29
|
+
"create-blueos-extension": "./bin/create-blueos-extension.mjs"
|
|
30
|
+
},
|
|
26
31
|
"exports": {
|
|
27
32
|
".": {
|
|
28
33
|
"types": "./dist/index.d.ts",
|
|
@@ -35,6 +40,7 @@
|
|
|
35
40
|
"dev": "vite",
|
|
36
41
|
"build": "npm run typecheck && vite build && npm run build:css",
|
|
37
42
|
"build:css": "tailwindcss --input src/styles/bundled.css --output dist/style.css --minify",
|
|
43
|
+
"docs:build": "vite build --mode docs",
|
|
38
44
|
"typecheck": "vue-tsc --noEmit",
|
|
39
45
|
"lint": "eslint . --ext .vue,.ts",
|
|
40
46
|
"prepublishOnly": "npm run build"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { watch } from 'vue'
|
|
3
|
+
|
|
4
|
+
import { useBlueLoading } from '../composables/useBlueLoading'
|
|
5
|
+
import { useBlueOs } from '../composables/useBlueOs'
|
|
6
|
+
import { applyBlueTheme } from '../utils/theme'
|
|
7
|
+
import BlueCard from './BlueCard.vue'
|
|
8
|
+
import BlueLoadingDialog from './BlueLoadingDialog.vue'
|
|
9
|
+
import BlueSnackbar from './BlueSnackbar.vue'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The whole of an extension's page: BlueOS's backdrop, one card in the middle of it, and the two
|
|
13
|
+
* pieces of feedback every extension needs mounted once. Wrap your panels in it and the notices
|
|
14
|
+
* raised through useBlueSnackbar and the waits raised through useBlueLoading appear by themselves.
|
|
15
|
+
*/
|
|
16
|
+
const props = defineProps<{
|
|
17
|
+
title: string
|
|
18
|
+
/** An image beside the title, such as the extension's own mark. */
|
|
19
|
+
logo?: string
|
|
20
|
+
/** An mdi name beside the title, for an extension with no mark of its own. */
|
|
21
|
+
icon?: string
|
|
22
|
+
/** How wide the card is allowed to get (default '615px'). */
|
|
23
|
+
width?: string
|
|
24
|
+
/**
|
|
25
|
+
* What is painted behind the card (default BlueOS's own blue). An extension is served in an
|
|
26
|
+
* iframe whose canvas is opaque, so leaving the page transparent shows black rather than the
|
|
27
|
+
* page behind it.
|
|
28
|
+
*/
|
|
29
|
+
backdrop?: string
|
|
30
|
+
/** Keep the kit's own primary instead of the vehicle's. */
|
|
31
|
+
ignoreHostTheme?: boolean
|
|
32
|
+
}>()
|
|
33
|
+
|
|
34
|
+
const { loading, hideLoading } = useBlueLoading()
|
|
35
|
+
const { primaryColor } = useBlueOs()
|
|
36
|
+
|
|
37
|
+
watch(
|
|
38
|
+
primaryColor,
|
|
39
|
+
(color) => {
|
|
40
|
+
if (!props.ignoreHostTheme && color) applyBlueTheme({ primary: color })
|
|
41
|
+
},
|
|
42
|
+
{ immediate: true }
|
|
43
|
+
)
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<template>
|
|
47
|
+
<div
|
|
48
|
+
class="min-h-screen w-full px-4 py-6"
|
|
49
|
+
:style="{ backgroundColor: backdrop || '#0C577B' }"
|
|
50
|
+
>
|
|
51
|
+
<BlueCard
|
|
52
|
+
:title="title"
|
|
53
|
+
:logo="logo"
|
|
54
|
+
:icon="icon"
|
|
55
|
+
:width="width"
|
|
56
|
+
>
|
|
57
|
+
<template
|
|
58
|
+
v-if="$slots.actions"
|
|
59
|
+
#actions
|
|
60
|
+
>
|
|
61
|
+
<slot name="actions" />
|
|
62
|
+
</template>
|
|
63
|
+
|
|
64
|
+
<slot />
|
|
65
|
+
|
|
66
|
+
<template
|
|
67
|
+
v-if="$slots.footer"
|
|
68
|
+
#footer
|
|
69
|
+
>
|
|
70
|
+
<slot name="footer" />
|
|
71
|
+
</template>
|
|
72
|
+
</BlueCard>
|
|
73
|
+
|
|
74
|
+
<BlueSnackbar />
|
|
75
|
+
|
|
76
|
+
<!-- Dismissing is the only thing the overlay reports, and only a dismissible one can. -->
|
|
77
|
+
<BlueLoadingDialog
|
|
78
|
+
:model-value="loading.show"
|
|
79
|
+
:message="loading.message"
|
|
80
|
+
:dismissible="loading.dismissible"
|
|
81
|
+
@update:model-value="hideLoading"
|
|
82
|
+
/>
|
|
83
|
+
</div>
|
|
84
|
+
</template>
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|
3
|
+
|
|
4
|
+
import type { BannerContent, BannerSeverity } from '../types/banner'
|
|
5
|
+
|
|
6
|
+
interface Look {
|
|
7
|
+
icon: string
|
|
8
|
+
color: string
|
|
9
|
+
background: string
|
|
10
|
+
border: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A note that keeps to a square until it is asked for: shut, it is its icon; open, it is one line
|
|
15
|
+
* of message, and only its width moves between the two, so nothing around it is pushed about.
|
|
16
|
+
*/
|
|
17
|
+
const LOOKS: Record<BannerSeverity, Look> = {
|
|
18
|
+
error: { icon: 'mdi-alert-circle-outline', color: '#EF9A9A', background: '#CF667926', border: '#CF667955' },
|
|
19
|
+
warning: { icon: 'mdi-alert', color: '#FFB74D', background: '#FB8C0022', border: '#FB8C0055' },
|
|
20
|
+
info: { icon: 'mdi-information-outline', color: '#81D4FA', background: '#4FC3F71A', border: '#4FC3F744' },
|
|
21
|
+
success: { icon: 'mdi-check-circle-outline', color: '#A5D6A7', background: '#66BB6A22', border: '#66BB6A55' },
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const props = withDefaults(
|
|
25
|
+
defineProps<
|
|
26
|
+
BannerContent & {
|
|
27
|
+
/** Whether it starts open (default true). */
|
|
28
|
+
expanded?: boolean
|
|
29
|
+
}
|
|
30
|
+
>(),
|
|
31
|
+
// An absent boolean prop is `false` unless a default says otherwise, which would start every
|
|
32
|
+
// banner shut.
|
|
33
|
+
{
|
|
34
|
+
severity: undefined,
|
|
35
|
+
icon: undefined,
|
|
36
|
+
color: undefined,
|
|
37
|
+
background: undefined,
|
|
38
|
+
border: undefined,
|
|
39
|
+
expanded: undefined,
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
const emit = defineEmits<{
|
|
44
|
+
(e: 'update:expanded', value: boolean): void
|
|
45
|
+
}>()
|
|
46
|
+
|
|
47
|
+
const box = ref<HTMLElement | null>(null)
|
|
48
|
+
const reveal = ref<HTMLElement | null>(null)
|
|
49
|
+
const label = ref<HTMLElement | null>(null)
|
|
50
|
+
|
|
51
|
+
const isOpen = ref(props.expanded ?? true)
|
|
52
|
+
const messageWidth = ref(0)
|
|
53
|
+
const measured = ref(false)
|
|
54
|
+
|
|
55
|
+
const look = computed(() => LOOKS[props.severity ?? 'info'])
|
|
56
|
+
|
|
57
|
+
const boxStyle = computed(() => ({
|
|
58
|
+
color: props.color ?? look.value.color,
|
|
59
|
+
backgroundColor: props.background ?? look.value.background,
|
|
60
|
+
borderColor: props.border ?? look.value.border,
|
|
61
|
+
}))
|
|
62
|
+
|
|
63
|
+
// Shut has nothing to show and needs no measurement; open before the first one takes whatever room
|
|
64
|
+
// there is, which is what the measurement will say anyway.
|
|
65
|
+
const revealStyle = computed(() => {
|
|
66
|
+
if (!isOpen.value) {
|
|
67
|
+
return { width: '0px' }
|
|
68
|
+
}
|
|
69
|
+
return measured.value ? { width: `${messageWidth.value}px` } : undefined
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// Held at its measured width in either state, so opening uncovers a line laid out once rather than
|
|
73
|
+
// one relaid on every frame of the way.
|
|
74
|
+
const labelStyle = computed(() => (measured.value ? { width: `${messageWidth.value}px` } : undefined))
|
|
75
|
+
|
|
76
|
+
// The room there is for the message and the width it asks for, read off elements laid out by their
|
|
77
|
+
// content and put back within the one task, so nothing is painted in between.
|
|
78
|
+
function measure(): void {
|
|
79
|
+
const button = box.value
|
|
80
|
+
const pane = reveal.value
|
|
81
|
+
const message = label.value
|
|
82
|
+
if (!button || !pane || !message) {
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
const buttonStyle = button.style.cssText
|
|
86
|
+
const paneStyle = pane.style.cssText
|
|
87
|
+
const messageStyle = message.style.cssText
|
|
88
|
+
const putBack = (): void => {
|
|
89
|
+
button.style.cssText = buttonStyle
|
|
90
|
+
pane.style.cssText = paneStyle
|
|
91
|
+
message.style.cssText = messageStyle
|
|
92
|
+
// Resolve the restored width while transitions are off: `auto` is not a width an animation can
|
|
93
|
+
// start from, and the banner would jump to its new size rather than move to it.
|
|
94
|
+
void pane.getBoundingClientRect()
|
|
95
|
+
button.classList.remove('bluevue-banner--measuring')
|
|
96
|
+
}
|
|
97
|
+
button.classList.add('bluevue-banner--measuring')
|
|
98
|
+
|
|
99
|
+
button.style.width = '100%'
|
|
100
|
+
pane.style.flex = '1 1 auto'
|
|
101
|
+
pane.style.width = 'auto'
|
|
102
|
+
message.style.width = '0px'
|
|
103
|
+
const room = pane.getBoundingClientRect().width
|
|
104
|
+
// Nothing to measure against inside something closed or off screen, and the last measurement
|
|
105
|
+
// beats none.
|
|
106
|
+
if (room <= 0) {
|
|
107
|
+
putBack()
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
message.style.width = 'max-content'
|
|
112
|
+
const wanted = Math.ceil(message.getBoundingClientRect().width)
|
|
113
|
+
|
|
114
|
+
// A message too long for the room keeps its line and loses its tail to the edge, the pointer
|
|
115
|
+
// being where the whole of it is read either way.
|
|
116
|
+
messageWidth.value = Math.min(wanted, Math.floor(room))
|
|
117
|
+
|
|
118
|
+
putBack()
|
|
119
|
+
measured.value = true
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function toggle(): void {
|
|
123
|
+
// A click is proof of being on screen, which a banner mounted inside a closed panel was not.
|
|
124
|
+
measure()
|
|
125
|
+
isOpen.value = !isOpen.value
|
|
126
|
+
emit('update:expanded', isOpen.value)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let observer: ResizeObserver | null = null
|
|
130
|
+
|
|
131
|
+
onMounted(() => {
|
|
132
|
+
measure()
|
|
133
|
+
// The message is text, so the width it asks for changes when the font arrives.
|
|
134
|
+
void document.fonts?.ready.then(measure)
|
|
135
|
+
const parent = box.value?.parentElement
|
|
136
|
+
if (parent) {
|
|
137
|
+
// Width changes only, and on the next frame: laying the message out again from inside the
|
|
138
|
+
// notification resizes what is being reported on, which the browser calls a loop.
|
|
139
|
+
let room = parent.clientWidth
|
|
140
|
+
observer = new ResizeObserver(() => {
|
|
141
|
+
if (parent.clientWidth !== room) {
|
|
142
|
+
room = parent.clientWidth
|
|
143
|
+
requestAnimationFrame(measure)
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
observer.observe(parent)
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
onBeforeUnmount(() => {
|
|
151
|
+
observer?.disconnect()
|
|
152
|
+
observer = null
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
// After the render, since a new message is measured on screen rather than in the props.
|
|
156
|
+
watch(() => props.text, measure, { flush: 'post' })
|
|
157
|
+
|
|
158
|
+
watch(
|
|
159
|
+
() => props.expanded,
|
|
160
|
+
(value) => {
|
|
161
|
+
if (value !== undefined) {
|
|
162
|
+
isOpen.value = value
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
</script>
|
|
167
|
+
|
|
168
|
+
<template>
|
|
169
|
+
<button
|
|
170
|
+
ref="box"
|
|
171
|
+
type="button"
|
|
172
|
+
class="bluevue-banner bluevue-elevation-1-soft flex w-fit items-center rounded-[6px] border text-left cursor-pointer"
|
|
173
|
+
:style="boxStyle"
|
|
174
|
+
:aria-expanded="isOpen"
|
|
175
|
+
:aria-label="text"
|
|
176
|
+
:title="text"
|
|
177
|
+
@click="toggle"
|
|
178
|
+
>
|
|
179
|
+
<span
|
|
180
|
+
class="mdi bluevue-banner__icon shrink-0"
|
|
181
|
+
:class="props.icon ?? look.icon"
|
|
182
|
+
/>
|
|
183
|
+
<span
|
|
184
|
+
ref="reveal"
|
|
185
|
+
class="bluevue-banner__reveal overflow-hidden"
|
|
186
|
+
:class="measured ? 'shrink-0' : ''"
|
|
187
|
+
:style="revealStyle"
|
|
188
|
+
>
|
|
189
|
+
<span
|
|
190
|
+
ref="label"
|
|
191
|
+
class="bluevue-banner__text block whitespace-nowrap"
|
|
192
|
+
:style="labelStyle"
|
|
193
|
+
>{{ text }}</span>
|
|
194
|
+
</span>
|
|
195
|
+
</button>
|
|
196
|
+
</template>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
import type { BannerContent } from '../types/banner'
|
|
5
|
+
import BlueBanner from './BlueBanner.vue'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A row of banners that opens one at a time: the opened one takes the room the others leave, and
|
|
9
|
+
* the ones already read gather at the end of the row.
|
|
10
|
+
*/
|
|
11
|
+
const props = defineProps<{
|
|
12
|
+
/** The banners, in the order they sit in until one of them is opened. Messages identify them. */
|
|
13
|
+
banners: BannerContent[]
|
|
14
|
+
}>()
|
|
15
|
+
|
|
16
|
+
const opened = ref<string | null>(null)
|
|
17
|
+
const promoted = ref<string[]>([])
|
|
18
|
+
|
|
19
|
+
// The row is the banners still to be read followed by the ones that have been, in the order they
|
|
20
|
+
// were opened. Ordering the row rather than reordering it leaves the elements where they are: a
|
|
21
|
+
// banner moved in the DOM loses both the transition it is in the middle of and the focus it holds.
|
|
22
|
+
function orderOf(banner: BannerContent): number {
|
|
23
|
+
const moved = promoted.value.indexOf(banner.text)
|
|
24
|
+
return moved === -1 ? props.banners.indexOf(banner) : props.banners.length + moved
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function onToggle(text: string, open: boolean): void {
|
|
28
|
+
opened.value = open ? text : null
|
|
29
|
+
if (open) {
|
|
30
|
+
promoted.value = [...promoted.value.filter((moved) => moved !== text), text]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<template>
|
|
36
|
+
<div class="flex w-full min-w-0 items-center gap-2">
|
|
37
|
+
<BlueBanner
|
|
38
|
+
v-for="banner in props.banners"
|
|
39
|
+
:key="banner.text"
|
|
40
|
+
v-bind="banner"
|
|
41
|
+
:expanded="opened === banner.text"
|
|
42
|
+
:class="opened === banner.text ? 'min-w-0 flex-1 !w-auto' : 'shrink-0'"
|
|
43
|
+
:style="{ order: orderOf(banner) }"
|
|
44
|
+
@update:expanded="onToggle(banner.text, $event)"
|
|
45
|
+
/>
|
|
46
|
+
</div>
|
|
47
|
+
</template>
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
import BlueIcon from './BlueIcon.vue'
|
|
5
|
+
import BlueSpinner from './BlueSpinner.vue'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The standard action. A button with no colour of its own is a secondary action and reads as a
|
|
9
|
+
* barely-there fill; the filled one is what the panel is actually asking to be done, and a panel
|
|
10
|
+
* asks for one thing at a time.
|
|
11
|
+
*/
|
|
12
|
+
const props = defineProps<{
|
|
13
|
+
/**
|
|
14
|
+
* 'tonal' (default) for a secondary action, 'filled' for the one the panel is asking for,
|
|
15
|
+
* 'text' for a quiet one, 'icon' for a glyph on its own.
|
|
16
|
+
*/
|
|
17
|
+
variant?: 'tonal' | 'filled' | 'text' | 'icon'
|
|
18
|
+
/** The fill of a filled button, and the glyph colour of the others (default the primary token). */
|
|
19
|
+
color?: string
|
|
20
|
+
/** Height and inset (default 'regular', which is the height of a button group beside it). */
|
|
21
|
+
density?: 'compact' | 'regular' | 'comfortable'
|
|
22
|
+
/** An mdi name before the label, or the whole of an icon button. */
|
|
23
|
+
icon?: string
|
|
24
|
+
/** An mdi name after the label. */
|
|
25
|
+
appendIcon?: string
|
|
26
|
+
/** Swaps the icon for a spinner and takes the button out of reach until it is done. */
|
|
27
|
+
loading?: boolean
|
|
28
|
+
disabled?: boolean
|
|
29
|
+
/** Takes the whole width it is given, for a button ending a narrow panel. */
|
|
30
|
+
block?: boolean
|
|
31
|
+
/** Hover text, and the name assistive technology reads on an icon button. */
|
|
32
|
+
tooltip?: string
|
|
33
|
+
theme?: 'light' | 'dark'
|
|
34
|
+
type?: 'button' | 'submit' | 'reset'
|
|
35
|
+
}>()
|
|
36
|
+
|
|
37
|
+
defineEmits<{
|
|
38
|
+
(event: 'click', value: MouseEvent): void
|
|
39
|
+
}>()
|
|
40
|
+
|
|
41
|
+
const HEIGHT = { compact: 24, regular: 30, comfortable: 36 } as const
|
|
42
|
+
const PADDING = { compact: 'px-2', regular: 'px-3', comfortable: 'px-4' } as const
|
|
43
|
+
|
|
44
|
+
const variant = computed(() => props.variant ?? 'tonal')
|
|
45
|
+
const density = computed(() => props.density ?? 'regular')
|
|
46
|
+
const isDark = computed(() => props.theme !== 'light')
|
|
47
|
+
const height = computed(() => HEIGHT[density.value])
|
|
48
|
+
const glyphSize = computed(() => (density.value === 'compact' ? 14 : 16))
|
|
49
|
+
|
|
50
|
+
// A filled or tonal button reads as a chip, so out of reach it becomes a flat grey one. A text or
|
|
51
|
+
// icon button has no chip to grey, and fading it is the whole of the statement.
|
|
52
|
+
const look = computed(() => {
|
|
53
|
+
if (props.disabled) {
|
|
54
|
+
return variant.value === 'text' || variant.value === 'icon'
|
|
55
|
+
? 'opacity-30 cursor-not-allowed'
|
|
56
|
+
: 'opacity-30 cursor-not-allowed bg-[#9e9e9e80] text-[#afafaf]'
|
|
57
|
+
}
|
|
58
|
+
switch (variant.value) {
|
|
59
|
+
case 'filled':
|
|
60
|
+
return 'bluevue-elevation-1 cursor-pointer text-white hover:brightness-125'
|
|
61
|
+
case 'text':
|
|
62
|
+
return isDark.value
|
|
63
|
+
? 'cursor-pointer text-[#ffffffcc] hover:bg-[#ffffff11] hover:text-white'
|
|
64
|
+
: 'cursor-pointer text-[#000000cc] hover:bg-[#00000011] hover:text-black'
|
|
65
|
+
case 'icon':
|
|
66
|
+
return isDark.value
|
|
67
|
+
? 'cursor-pointer text-[#ffffffaa] hover:text-white'
|
|
68
|
+
: 'cursor-pointer text-[#00000099] hover:text-black'
|
|
69
|
+
default:
|
|
70
|
+
return isDark.value
|
|
71
|
+
? 'cursor-pointer bg-[#ffffff11] text-white hover:bg-[#ffffff22]'
|
|
72
|
+
: 'cursor-pointer bg-[#00000011] text-black hover:bg-[#00000022]'
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const fill = computed(() => (variant.value === 'filled' && !props.disabled ? props.color || 'var(--bluevue-primary)' : undefined))
|
|
77
|
+
|
|
78
|
+
// The glyph of a text or icon button follows the colour it was given; a filled one draws its
|
|
79
|
+
// contents in white over the fill, and a tonal one in the theme's own text colour.
|
|
80
|
+
const glyphColor = computed(() =>
|
|
81
|
+
props.color && (variant.value === 'text' || variant.value === 'icon') ? props.color : undefined
|
|
82
|
+
)
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<template>
|
|
86
|
+
<button
|
|
87
|
+
:type="type || 'button'"
|
|
88
|
+
:disabled="disabled"
|
|
89
|
+
:title="tooltip"
|
|
90
|
+
:aria-label="variant === 'icon' ? tooltip : undefined"
|
|
91
|
+
:aria-busy="loading || undefined"
|
|
92
|
+
class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap transition-colors duration-200"
|
|
93
|
+
:class="[
|
|
94
|
+
look,
|
|
95
|
+
variant === 'icon' ? 'rounded-[6px]' : 'rounded-[4px]',
|
|
96
|
+
variant === 'icon' ? '' : PADDING[density],
|
|
97
|
+
block ? 'w-full' : '',
|
|
98
|
+
// A button waiting on its own result stays where it is and reads as busy rather than
|
|
99
|
+
// inviting a second press.
|
|
100
|
+
loading ? 'pointer-events-none' : '',
|
|
101
|
+
]"
|
|
102
|
+
:style="{
|
|
103
|
+
height: `${height}px`,
|
|
104
|
+
width: variant === 'icon' ? `${height}px` : undefined,
|
|
105
|
+
backgroundColor: fill,
|
|
106
|
+
}"
|
|
107
|
+
@click="$emit('click', $event)"
|
|
108
|
+
>
|
|
109
|
+
<BlueSpinner
|
|
110
|
+
v-if="loading"
|
|
111
|
+
:size="glyphSize"
|
|
112
|
+
:color="variant === 'filled' ? '#ffffff' : undefined"
|
|
113
|
+
/>
|
|
114
|
+
<BlueIcon
|
|
115
|
+
v-else-if="icon"
|
|
116
|
+
:name="icon"
|
|
117
|
+
:size="glyphSize"
|
|
118
|
+
:color="glyphColor"
|
|
119
|
+
/>
|
|
120
|
+
<span
|
|
121
|
+
v-if="variant !== 'icon'"
|
|
122
|
+
class="min-w-0 truncate font-medium uppercase tracking-wide"
|
|
123
|
+
:class="density === 'compact' ? 'text-[12px]' : 'text-[13px]'"
|
|
124
|
+
>
|
|
125
|
+
<slot />
|
|
126
|
+
</span>
|
|
127
|
+
<BlueIcon
|
|
128
|
+
v-if="appendIcon && variant !== 'icon'"
|
|
129
|
+
:name="appendIcon"
|
|
130
|
+
:size="glyphSize"
|
|
131
|
+
:color="glyphColor"
|
|
132
|
+
/>
|
|
133
|
+
</button>
|
|
134
|
+
</template>
|