feat: implement mobile PWA card design, refine toolbar and status bar
This commit is contained in:
@@ -7,13 +7,6 @@
|
||||
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||
"Courier New", monospace;
|
||||
--color-red-400: oklch(70.4% 0.191 22.216);
|
||||
--color-red-500: oklch(63.7% 0.237 25.331);
|
||||
--color-red-600: oklch(57.7% 0.245 27.325);
|
||||
--color-red-900: oklch(39.6% 0.141 25.723);
|
||||
--color-yellow-500: oklch(79.5% 0.184 86.047);
|
||||
--color-green-500: oklch(72.3% 0.219 149.579);
|
||||
--color-gray-500: oklch(55.1% 0.027 264.364);
|
||||
--color-black: #000;
|
||||
--color-white: #fff;
|
||||
--spacing: 0.25rem;
|
||||
@@ -25,18 +18,16 @@
|
||||
--text-sm--line-height: calc(1.25 / 0.875);
|
||||
--text-lg: 1.125rem;
|
||||
--text-lg--line-height: calc(1.75 / 1.125);
|
||||
--text-xl: 1.25rem;
|
||||
--text-xl--line-height: calc(1.75 / 1.25);
|
||||
--font-weight-normal: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 700;
|
||||
--tracking-wider: 0.05em;
|
||||
--leading-tight: 1.25;
|
||||
--radius-md: 0.375rem;
|
||||
--radius-lg: 0.5rem;
|
||||
--radius-xl: 0.75rem;
|
||||
--radius-2xl: 1rem;
|
||||
--blur-sm: 8px;
|
||||
--blur-xl: 24px;
|
||||
--default-transition-duration: 150ms;
|
||||
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--default-font-family: var(--font-sans);
|
||||
@@ -514,6 +505,95 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.dropdown {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
position-area: var(--anchor-v, bottom) var(--anchor-h, span-right);
|
||||
& > *:not(:has(~ [class*="dropdown-content"])):focus {
|
||||
--tw-outline-style: none;
|
||||
outline-style: none;
|
||||
@media (forced-colors: active) {
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
.dropdown-content {
|
||||
position: absolute;
|
||||
}
|
||||
&.dropdown-close .dropdown-content, &:not(details, .dropdown-open, .dropdown-hover:hover, :focus-within) .dropdown-content, &.dropdown-hover:not(:hover) [tabindex]:first-child:focus:not(:focus-visible) ~ .dropdown-content {
|
||||
display: none;
|
||||
transform-origin: top;
|
||||
opacity: 0%;
|
||||
scale: 95%;
|
||||
}
|
||||
&[popover], .dropdown-content {
|
||||
z-index: 999;
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
animation: dropdown 0.2s;
|
||||
transition-property: opacity, scale, display;
|
||||
transition-behavior: allow-discrete;
|
||||
transition-duration: 0.2s;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
}
|
||||
@starting-style {
|
||||
&[popover], .dropdown-content {
|
||||
scale: 95%;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
&:not(.dropdown-close) {
|
||||
&.dropdown-open, &:not(.dropdown-hover):focus, &:focus-within {
|
||||
> [tabindex]:first-child {
|
||||
pointer-events: none;
|
||||
}
|
||||
.dropdown-content {
|
||||
opacity: 100%;
|
||||
scale: 100%;
|
||||
}
|
||||
}
|
||||
&.dropdown-hover:hover {
|
||||
.dropdown-content {
|
||||
opacity: 100%;
|
||||
scale: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:is(details) {
|
||||
summary {
|
||||
&::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:where([popover]) {
|
||||
background: #0000;
|
||||
}
|
||||
&[popover] {
|
||||
position: fixed;
|
||||
color: inherit;
|
||||
@supports not (position-area: bottom) {
|
||||
margin: auto;
|
||||
&.dropdown-close, &.dropdown-open:not(:popover-open) {
|
||||
display: none;
|
||||
transform-origin: top;
|
||||
opacity: 0%;
|
||||
scale: 95%;
|
||||
}
|
||||
&::backdrop {
|
||||
background-color: color-mix(in oklab, #000 30%, #0000);
|
||||
}
|
||||
}
|
||||
&.dropdown-close, &:not(.dropdown-open, :popover-open) {
|
||||
display: none;
|
||||
transform-origin: top;
|
||||
opacity: 0%;
|
||||
scale: 95%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.btn {
|
||||
:where(&) {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
@@ -940,6 +1020,20 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.navbar {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
padding: 0.5rem;
|
||||
min-height: 4rem;
|
||||
}
|
||||
:where(&) {
|
||||
@layer daisyui.l1.l2 {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
.drawer {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
position: relative;
|
||||
@@ -948,6 +1042,55 @@
|
||||
grid-auto-columns: max-content auto;
|
||||
}
|
||||
}
|
||||
.card {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: var(--radius-box);
|
||||
outline-width: 2px;
|
||||
transition: outline 0.2s ease-in-out;
|
||||
outline: 0 solid #0000;
|
||||
outline-offset: 2px;
|
||||
&:focus {
|
||||
--tw-outline-style: none;
|
||||
outline-style: none;
|
||||
@media (forced-colors: active) {
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
&:focus-visible {
|
||||
outline-color: currentColor;
|
||||
}
|
||||
:where(figure:first-child) {
|
||||
overflow: hidden;
|
||||
border-start-start-radius: inherit;
|
||||
border-start-end-radius: inherit;
|
||||
border-end-start-radius: unset;
|
||||
border-end-end-radius: unset;
|
||||
}
|
||||
:where(figure:last-child) {
|
||||
overflow: hidden;
|
||||
border-start-start-radius: unset;
|
||||
border-start-end-radius: unset;
|
||||
border-end-start-radius: inherit;
|
||||
border-end-end-radius: inherit;
|
||||
}
|
||||
figure {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
&:has(> input:is(input[type="checkbox"], input[type="radio"])) {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
&:has(> :checked) {
|
||||
outline: 2px solid currentColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
.progress {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
position: relative;
|
||||
@@ -1010,6 +1153,44 @@
|
||||
.inset-0 {
|
||||
inset: calc(var(--spacing) * 0);
|
||||
}
|
||||
.dropdown-end {
|
||||
@layer daisyui.l1.l2 {
|
||||
--anchor-h: span-left;
|
||||
:where(.dropdown-content) {
|
||||
inset-inline-end: calc(0.25rem * 0);
|
||||
translate: 0 0;
|
||||
[dir="rtl"] & {
|
||||
translate: 0 0;
|
||||
}
|
||||
}
|
||||
&.dropdown-left {
|
||||
--anchor-h: left;
|
||||
--anchor-v: span-top;
|
||||
.dropdown-content {
|
||||
top: auto;
|
||||
bottom: calc(0.25rem * 0);
|
||||
}
|
||||
}
|
||||
&.dropdown-right {
|
||||
--anchor-h: right;
|
||||
--anchor-v: span-top;
|
||||
.dropdown-content {
|
||||
top: auto;
|
||||
bottom: calc(0.25rem * 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.dropdown-top {
|
||||
@layer daisyui.l1.l2 {
|
||||
--anchor-v: top;
|
||||
.dropdown-content {
|
||||
top: auto;
|
||||
bottom: 100%;
|
||||
transform-origin: bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
.modal-backdrop {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
grid-column-start: 1;
|
||||
@@ -1027,6 +1208,9 @@
|
||||
.z-40 {
|
||||
z-index: 40;
|
||||
}
|
||||
.z-\[1\] {
|
||||
z-index: 1;
|
||||
}
|
||||
.z-\[100\] {
|
||||
z-index: 100;
|
||||
}
|
||||
@@ -1061,6 +1245,36 @@
|
||||
min-width: calc(0.25rem * 0);
|
||||
}
|
||||
}
|
||||
.divider {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
display: flex;
|
||||
height: calc(0.25rem * 4);
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
white-space: nowrap;
|
||||
margin: var(--divider-m, 1rem 0);
|
||||
--divider-color: var(--color-base-content);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
--divider-color: color-mix(in oklab, var(--color-base-content) 10%, transparent);
|
||||
}
|
||||
&:before, &:after {
|
||||
content: "";
|
||||
height: calc(0.25rem * 0.5);
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
background-color: var(--divider-color);
|
||||
}
|
||||
@media print {
|
||||
&:before, &:after {
|
||||
border: 0.5px solid;
|
||||
}
|
||||
}
|
||||
&:not(:empty) {
|
||||
gap: calc(0.25rem * 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
.filter {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
display: flex;
|
||||
@@ -1118,8 +1332,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.my-1 {
|
||||
margin-block: calc(var(--spacing) * 1);
|
||||
.my-0 {
|
||||
margin-block: calc(var(--spacing) * 0);
|
||||
}
|
||||
.label {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
@@ -1160,15 +1374,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.join-item {
|
||||
&:where(*:not(:first-child, :disabled, [disabled], .btn-disabled)) {
|
||||
margin-inline-start: calc(var(--border, 1px) * -1);
|
||||
margin-block-start: 0;
|
||||
}
|
||||
&:where(*:is(:disabled, [disabled], .btn-disabled)) {
|
||||
border-width: var(--border, 1px) 0 var(--border, 1px) var(--border, 1px);
|
||||
}
|
||||
}
|
||||
.modal-action {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
margin-top: calc(0.25rem * 6);
|
||||
@@ -1183,9 +1388,6 @@
|
||||
.mt-auto {
|
||||
margin-top: auto;
|
||||
}
|
||||
.mb-1 {
|
||||
margin-bottom: calc(var(--spacing) * 1);
|
||||
}
|
||||
.mb-2 {
|
||||
margin-bottom: calc(var(--spacing) * 2);
|
||||
}
|
||||
@@ -1248,71 +1450,50 @@
|
||||
height: var(--size);
|
||||
}
|
||||
}
|
||||
.join {
|
||||
.navbar-end {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
display: inline-flex;
|
||||
align-items: stretch;
|
||||
--join-ss: 0;
|
||||
--join-se: 0;
|
||||
--join-es: 0;
|
||||
--join-ee: 0;
|
||||
:where(.join-item) {
|
||||
border-start-start-radius: var(--join-ss, 0);
|
||||
border-start-end-radius: var(--join-se, 0);
|
||||
border-end-start-radius: var(--join-es, 0);
|
||||
border-end-end-radius: var(--join-ee, 0);
|
||||
* {
|
||||
--join-ss: var(--radius-field);
|
||||
--join-se: var(--radius-field);
|
||||
--join-es: var(--radius-field);
|
||||
--join-ee: var(--radius-field);
|
||||
align-items: center;
|
||||
width: 50%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
> .join-item:where(:first-child) {
|
||||
--join-ss: var(--radius-field);
|
||||
--join-se: 0;
|
||||
--join-es: var(--radius-field);
|
||||
--join-ee: 0;
|
||||
}
|
||||
:first-child:not(:last-child) {
|
||||
:where(.join-item) {
|
||||
--join-ss: var(--radius-field);
|
||||
--join-se: 0;
|
||||
--join-es: var(--radius-field);
|
||||
--join-ee: 0;
|
||||
.navbar-start {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: 50%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
> .join-item:where(:last-child) {
|
||||
--join-ss: 0;
|
||||
--join-se: var(--radius-field);
|
||||
--join-es: 0;
|
||||
--join-ee: var(--radius-field);
|
||||
}
|
||||
:last-child:not(:first-child) {
|
||||
:where(.join-item) {
|
||||
--join-ss: 0;
|
||||
--join-se: var(--radius-field);
|
||||
--join-es: 0;
|
||||
--join-ee: var(--radius-field);
|
||||
.card-body {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
display: flex;
|
||||
flex: auto;
|
||||
flex-direction: column;
|
||||
gap: calc(0.25rem * 2);
|
||||
padding: var(--card-p, 1.5rem);
|
||||
font-size: var(--card-fs, 0.875rem);
|
||||
:where(p) {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
> .join-item:where(:only-child) {
|
||||
--join-ss: var(--radius-field);
|
||||
--join-se: var(--radius-field);
|
||||
--join-es: var(--radius-field);
|
||||
--join-ee: var(--radius-field);
|
||||
}
|
||||
:only-child {
|
||||
:where(.join-item) {
|
||||
--join-ss: var(--radius-field);
|
||||
--join-se: var(--radius-field);
|
||||
--join-es: var(--radius-field);
|
||||
--join-ee: var(--radius-field);
|
||||
}
|
||||
}
|
||||
.line-clamp-2 {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
@@ -1346,6 +1527,9 @@
|
||||
height: var(--size);
|
||||
}
|
||||
}
|
||||
.h-1\.5 {
|
||||
height: calc(var(--spacing) * 1.5);
|
||||
}
|
||||
.h-4 {
|
||||
height: calc(var(--spacing) * 4);
|
||||
}
|
||||
@@ -1385,15 +1569,15 @@
|
||||
.w-5 {
|
||||
width: calc(var(--spacing) * 5);
|
||||
}
|
||||
.w-8 {
|
||||
width: calc(var(--spacing) * 8);
|
||||
}
|
||||
.w-24 {
|
||||
width: calc(var(--spacing) * 24);
|
||||
}
|
||||
.w-48 {
|
||||
width: calc(var(--spacing) * 48);
|
||||
}
|
||||
.w-52 {
|
||||
width: calc(var(--spacing) * 52);
|
||||
}
|
||||
.w-64 {
|
||||
width: calc(var(--spacing) * 64);
|
||||
}
|
||||
@@ -1421,12 +1605,15 @@
|
||||
.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-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.grid-cols-1 {
|
||||
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||
}
|
||||
.grid-cols-3 {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -1436,6 +1623,12 @@
|
||||
.items-end {
|
||||
align-items: flex-end;
|
||||
}
|
||||
.items-start {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
@@ -1472,6 +1665,9 @@
|
||||
.overflow-x-auto {
|
||||
overflow-x: auto;
|
||||
}
|
||||
.overflow-y-auto {
|
||||
overflow-y: auto;
|
||||
}
|
||||
.rounded-box {
|
||||
border-radius: var(--radius-box);
|
||||
}
|
||||
@@ -1481,12 +1677,6 @@
|
||||
.rounded-md {
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
.rounded-none {
|
||||
border-radius: 0;
|
||||
}
|
||||
.rounded-xl {
|
||||
border-radius: var(--radius-xl);
|
||||
}
|
||||
.rounded-t-2xl {
|
||||
border-top-left-radius: var(--radius-2xl);
|
||||
border-top-right-radius: var(--radius-2xl);
|
||||
@@ -1507,6 +1697,10 @@
|
||||
border-bottom-style: var(--tw-border-style);
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
.border-l {
|
||||
border-left-style: var(--tw-border-style);
|
||||
border-left-width: 1px;
|
||||
}
|
||||
.badge-ghost {
|
||||
@layer daisyui.l1.l2 {
|
||||
border-color: var(--color-base-200);
|
||||
@@ -1515,21 +1709,38 @@
|
||||
background-image: none;
|
||||
}
|
||||
}
|
||||
.badge-soft {
|
||||
@layer daisyui.l1.l2 {
|
||||
color: var(--badge-color, var(--color-base-content));
|
||||
background-color: var(--badge-color, var(--color-base-content));
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix( in oklab, var(--badge-color, var(--color-base-content)) 8%, var(--color-base-100) );
|
||||
}
|
||||
border-color: var(--badge-color, var(--color-base-content));
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
border-color: color-mix( in oklab, var(--badge-color, var(--color-base-content)) 10%, var(--color-base-100) );
|
||||
}
|
||||
background-image: none;
|
||||
}
|
||||
}
|
||||
.border-base-200 {
|
||||
border-color: var(--color-base-200);
|
||||
}
|
||||
.border-base-200\/50 {
|
||||
border-color: var(--color-base-200);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
border-color: color-mix(in oklab, var(--color-base-200) 50%, transparent);
|
||||
}
|
||||
}
|
||||
.border-base-300 {
|
||||
border-color: var(--color-base-300);
|
||||
}
|
||||
.border-white\/10 {
|
||||
border-color: color-mix(in srgb, #fff 10%, transparent);
|
||||
.border-white\/5 {
|
||||
border-color: color-mix(in srgb, #fff 5%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
border-color: color-mix(in oklab, var(--color-white) 10%, transparent);
|
||||
border-color: color-mix(in oklab, var(--color-white) 5%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-\[\#111116\]\/95 {
|
||||
background-color: color-mix(in oklab, #111116 95%, transparent);
|
||||
}
|
||||
.bg-base-100 {
|
||||
background-color: var(--color-base-100);
|
||||
}
|
||||
@@ -1539,10 +1750,10 @@
|
||||
.bg-primary {
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
.bg-white\/10 {
|
||||
background-color: color-mix(in srgb, #fff 10%, transparent);
|
||||
.bg-primary\/10 {
|
||||
background-color: var(--color-primary);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-white) 10%, transparent);
|
||||
background-color: color-mix(in oklab, var(--color-primary) 10%, transparent);
|
||||
}
|
||||
}
|
||||
.loading-spinner {
|
||||
@@ -1553,29 +1764,41 @@
|
||||
.stroke-current {
|
||||
stroke: currentcolor;
|
||||
}
|
||||
.checkbox-xs {
|
||||
@layer daisyui.l1.l2 {
|
||||
padding: 0.125rem;
|
||||
--size: calc(var(--size-selector, 0.25rem) * 4);
|
||||
}
|
||||
}
|
||||
.p-0 {
|
||||
padding: calc(var(--spacing) * 0);
|
||||
}
|
||||
.p-2 {
|
||||
padding: calc(var(--spacing) * 2);
|
||||
}
|
||||
.p-3 {
|
||||
padding: calc(var(--spacing) * 3);
|
||||
}
|
||||
.p-4 {
|
||||
padding: calc(var(--spacing) * 4);
|
||||
}
|
||||
.p-6 {
|
||||
padding: calc(var(--spacing) * 6);
|
||||
}
|
||||
.table-xs {
|
||||
.menu-title {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
padding-inline: calc(0.25rem * 3);
|
||||
padding-block: calc(0.25rem * 2);
|
||||
color: var(--color-base-content);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
color: color-mix(in oklab, var(--color-base-content) 40%, transparent);
|
||||
}
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
.table-sm {
|
||||
@layer daisyui.l1.l2 {
|
||||
:not(thead, tfoot) tr {
|
||||
font-size: 0.6875rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
:where(th, td) {
|
||||
padding-inline: calc(0.25rem * 2);
|
||||
padding-block: calc(0.25rem * 1);
|
||||
padding-inline: calc(0.25rem * 3);
|
||||
padding-block: calc(0.25rem * 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1586,26 +1809,33 @@
|
||||
padding-inline: calc(0.25rem * 2.5 - var(--border));
|
||||
}
|
||||
}
|
||||
.px-3 {
|
||||
padding-inline: calc(var(--spacing) * 3);
|
||||
.badge-xs {
|
||||
@layer daisyui.l1.l2 {
|
||||
--size: calc(var(--size-selector, 0.25rem) * 4);
|
||||
font-size: 0.625rem;
|
||||
padding-inline: calc(0.25rem * 2 - var(--border));
|
||||
}
|
||||
}
|
||||
.px-4 {
|
||||
padding-inline: calc(var(--spacing) * 4);
|
||||
}
|
||||
.py-1 {
|
||||
padding-block: calc(var(--spacing) * 1);
|
||||
.py-1\.5 {
|
||||
padding-block: calc(var(--spacing) * 1.5);
|
||||
}
|
||||
.py-2 {
|
||||
padding-block: calc(var(--spacing) * 2);
|
||||
}
|
||||
.py-2\.5 {
|
||||
padding-block: calc(var(--spacing) * 2.5);
|
||||
}
|
||||
.py-4 {
|
||||
padding-block: calc(var(--spacing) * 4);
|
||||
}
|
||||
.text-left {
|
||||
text-align: left;
|
||||
.pt-1 {
|
||||
padding-top: calc(var(--spacing) * 1);
|
||||
}
|
||||
.pb-20 {
|
||||
padding-bottom: calc(var(--spacing) * 20);
|
||||
}
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.text-right {
|
||||
text-align: right;
|
||||
@@ -1621,20 +1851,23 @@
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--tw-leading, var(--text-sm--line-height));
|
||||
}
|
||||
.text-xl {
|
||||
font-size: var(--text-xl);
|
||||
line-height: var(--tw-leading, var(--text-xl--line-height));
|
||||
}
|
||||
.text-xs {
|
||||
font-size: var(--text-xs);
|
||||
line-height: var(--tw-leading, var(--text-xs--line-height));
|
||||
}
|
||||
.text-\[9px\] {
|
||||
font-size: 9px;
|
||||
}
|
||||
.text-\[10px\] {
|
||||
font-size: 10px;
|
||||
}
|
||||
.text-\[11px\] {
|
||||
font-size: 11px;
|
||||
}
|
||||
.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);
|
||||
@@ -1643,6 +1876,10 @@
|
||||
--tw-font-weight: var(--font-weight-medium);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
.font-normal {
|
||||
--tw-font-weight: var(--font-weight-normal);
|
||||
font-weight: var(--font-weight-normal);
|
||||
}
|
||||
.font-semibold {
|
||||
--tw-font-weight: var(--font-weight-semibold);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
@@ -1673,6 +1910,12 @@
|
||||
color: color-mix(in oklab, var(--color-base-content) 50%, transparent);
|
||||
}
|
||||
}
|
||||
.text-base-content\/60 {
|
||||
color: var(--color-base-content);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
color: color-mix(in oklab, var(--color-base-content) 60%, transparent);
|
||||
}
|
||||
}
|
||||
.text-base-content\/70 {
|
||||
color: var(--color-base-content);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
@@ -1682,32 +1925,17 @@
|
||||
.text-error {
|
||||
color: var(--color-error);
|
||||
}
|
||||
.text-gray-500 {
|
||||
color: var(--color-gray-500);
|
||||
}
|
||||
.text-green-500 {
|
||||
color: var(--color-green-500);
|
||||
}
|
||||
.text-primary {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
.text-red-500 {
|
||||
color: var(--color-red-500);
|
||||
}
|
||||
.text-red-600 {
|
||||
color: var(--color-red-600);
|
||||
}
|
||||
.text-success {
|
||||
color: var(--color-success);
|
||||
}
|
||||
.text-warning {
|
||||
color: var(--color-warning);
|
||||
}
|
||||
.text-white {
|
||||
color: var(--color-white);
|
||||
}
|
||||
.text-yellow-500 {
|
||||
color: var(--color-yellow-500);
|
||||
.capitalize {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.uppercase {
|
||||
text-transform: uppercase;
|
||||
@@ -1715,14 +1943,24 @@
|
||||
.opacity-0 {
|
||||
opacity: 0%;
|
||||
}
|
||||
.opacity-10 {
|
||||
opacity: 10%;
|
||||
}
|
||||
.opacity-60 {
|
||||
opacity: 60%;
|
||||
}
|
||||
.opacity-70 {
|
||||
opacity: 70%;
|
||||
}
|
||||
.opacity-80 {
|
||||
opacity: 80%;
|
||||
}
|
||||
.shadow-2xl {
|
||||
--tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25));
|
||||
.shadow {
|
||||
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
}
|
||||
.shadow-sm {
|
||||
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
}
|
||||
.shadow-xl {
|
||||
@@ -1733,6 +1971,13 @@
|
||||
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
}
|
||||
.ring-2 {
|
||||
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
}
|
||||
.ring-primary {
|
||||
--tw-ring-color: var(--color-primary);
|
||||
}
|
||||
.btn-ghost {
|
||||
@layer daisyui.l1 {
|
||||
&:not(.btn-active, :hover, :active:focus, :focus-visible, input:checked:not(.filter .btn)) {
|
||||
@@ -1765,11 +2010,6 @@
|
||||
-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,);
|
||||
}
|
||||
.backdrop-blur-xl {
|
||||
--tw-backdrop-blur: blur(var(--blur-xl));
|
||||
-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-all {
|
||||
transition-property: all;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
@@ -1792,26 +2032,6 @@
|
||||
--tw-duration: 300ms;
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
.btn-outline {
|
||||
@layer daisyui.l1 {
|
||||
&:not( .btn-active, :hover, :active:focus, :focus-visible, input:checked:not(.filter .btn), :disabled, [disabled], .btn-disabled ) {
|
||||
--btn-shadow: "";
|
||||
--btn-bg: #0000;
|
||||
--btn-fg: var(--btn-color);
|
||||
--btn-border: var(--btn-color);
|
||||
--btn-noise: none;
|
||||
}
|
||||
@media (hover: none) {
|
||||
&:not(.btn-active, :active, :focus-visible, input:checked:not(.filter .btn)):hover {
|
||||
--btn-shadow: "";
|
||||
--btn-bg: #0000;
|
||||
--btn-fg: var(--btn-color);
|
||||
--btn-border: var(--btn-color);
|
||||
--btn-noise: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.btn-sm {
|
||||
@layer daisyui.l1.l2 {
|
||||
--fontsize: 0.75rem;
|
||||
@@ -1826,6 +2046,30 @@
|
||||
--size: calc(var(--size-field, 0.25rem) * 6);
|
||||
}
|
||||
}
|
||||
.badge-error {
|
||||
@layer daisyui.l1.l2 {
|
||||
--badge-color: var(--color-error);
|
||||
--badge-fg: var(--color-error-content);
|
||||
}
|
||||
}
|
||||
.badge-primary {
|
||||
@layer daisyui.l1.l2 {
|
||||
--badge-color: var(--color-primary);
|
||||
--badge-fg: var(--color-primary-content);
|
||||
}
|
||||
}
|
||||
.badge-success {
|
||||
@layer daisyui.l1.l2 {
|
||||
--badge-color: var(--color-success);
|
||||
--badge-fg: var(--color-success-content);
|
||||
}
|
||||
}
|
||||
.badge-warning {
|
||||
@layer daisyui.l1.l2 {
|
||||
--badge-color: var(--color-warning);
|
||||
--badge-fg: var(--color-warning-content);
|
||||
}
|
||||
}
|
||||
.btn-primary {
|
||||
@layer daisyui.l1.l2 {
|
||||
--btn-color: var(--color-primary);
|
||||
@@ -1857,6 +2101,16 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:bg-error\/10 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
background-color: var(--color-error);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-error) 10%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:bg-primary\/90 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
@@ -1867,36 +2121,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:bg-red-500\/20 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
background-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 20%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-red-500) 20%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:bg-red-900\/20 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
background-color: color-mix(in srgb, oklch(39.6% 0.141 25.723) 20%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-red-900) 20%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:bg-white\/10 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
background-color: color-mix(in srgb, #fff 10%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-white) 10%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:text-primary {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
@@ -1904,13 +2128,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:text-red-400 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
color: var(--color-red-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
.focus-visible\:ring-2 {
|
||||
&:focus-visible {
|
||||
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
||||
@@ -1929,6 +2146,31 @@
|
||||
outline-style: none;
|
||||
}
|
||||
}
|
||||
.active\:scale-\[0\.99\] {
|
||||
&:active {
|
||||
scale: 0.99;
|
||||
}
|
||||
}
|
||||
.active\:bg-error {
|
||||
&:active {
|
||||
background-color: var(--color-error);
|
||||
}
|
||||
}
|
||||
.active\:bg-primary {
|
||||
&:active {
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
.active\:text-error-content {
|
||||
&:active {
|
||||
color: var(--color-error-content);
|
||||
}
|
||||
}
|
||||
.active\:text-primary-content {
|
||||
&:active {
|
||||
color: var(--color-primary-content);
|
||||
}
|
||||
}
|
||||
.disabled\:pointer-events-none {
|
||||
&:disabled {
|
||||
pointer-events: none;
|
||||
@@ -1963,6 +2205,16 @@
|
||||
padding: calc(var(--spacing) * 4);
|
||||
}
|
||||
}
|
||||
.md\:block {
|
||||
@media (width >= 48rem) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.md\:hidden {
|
||||
@media (width >= 48rem) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.md\:items-center {
|
||||
@media (width >= 48rem) {
|
||||
align-items: center;
|
||||
@@ -1973,11 +2225,6 @@
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
}
|
||||
.md\:p-6 {
|
||||
@media (width >= 48rem) {
|
||||
padding: calc(var(--spacing) * 6);
|
||||
}
|
||||
}
|
||||
.lg\:drawer-open {
|
||||
@media (width >= 64rem) {
|
||||
@layer daisyui.l1.l2 {
|
||||
@@ -2317,6 +2564,10 @@
|
||||
inherits: false;
|
||||
initial-value: solid;
|
||||
}
|
||||
@property --tw-leading {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-font-weight {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
@@ -2493,6 +2744,7 @@
|
||||
--tw-skew-y: initial;
|
||||
--tw-space-y-reverse: 0;
|
||||
--tw-border-style: solid;
|
||||
--tw-leading: initial;
|
||||
--tw-font-weight: initial;
|
||||
--tw-tracking: initial;
|
||||
--tw-shadow: 0 0 #0000;
|
||||
|
||||
@@ -17,7 +17,7 @@ pub fn App() -> impl IntoView {
|
||||
// Toolbar at the top
|
||||
<Toolbar />
|
||||
|
||||
<main class="flex-1 flex flex-col min-w-0 bg-base-100 overflow-hidden p-4 md:p-6 space-y-6">
|
||||
<main class="flex-1 flex flex-col min-w-0 bg-base-100 overflow-hidden space-y-6">
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/" view=move || view! { <TorrentTable /> } />
|
||||
|
||||
@@ -68,38 +68,44 @@ pub fn ContextMenu(
|
||||
view! {
|
||||
<div
|
||||
node_ref=target
|
||||
class="fixed z-[100] bg-[#111116]/95 backdrop-blur-xl border border-white/10 rounded-xl shadow-2xl py-2 min-w-[200px] animate-in fade-in zoom-in-95 duration-100"
|
||||
class="fixed z-[100] min-w-[200px] animate-in fade-in zoom-in-95 duration-100"
|
||||
style=format!("left: {}px; top: {}px", position.0, position.1)
|
||||
on:contextmenu=move |e| e.prevent_default()
|
||||
>
|
||||
<div class="px-3 py-1 text-xs font-bold text-gray-500 uppercase tracking-wider mb-1">"Actions"</div>
|
||||
<ul class="menu bg-base-200 text-base-content rounded-box shadow-xl border border-white/5 p-2 gap-1">
|
||||
<li class="menu-title px-4 py-1.5 text-xs opacity-60 uppercase tracking-wider font-bold">"Actions"</li>
|
||||
|
||||
<li>
|
||||
<button
|
||||
class="w-full text-left px-4 py-2.5 hover:bg-white/10 flex items-center gap-3 transition-colors text-white"
|
||||
class="gap-3 active:bg-primary active:text-primary-content"
|
||||
on:click={
|
||||
let handle_action = handle_action.clone();
|
||||
move |_| handle_action("start")
|
||||
}
|
||||
>
|
||||
<svg class="w-4 h-4 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
<svg class="w-4 h-4 text-success" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
"Resume"
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<button
|
||||
class="w-full text-left px-4 py-2.5 hover:bg-white/10 flex items-center gap-3 transition-colors text-white"
|
||||
class="gap-3 active:bg-primary active:text-primary-content"
|
||||
on:click={
|
||||
let handle_action = handle_action.clone();
|
||||
move |_| handle_action("stop")
|
||||
}
|
||||
>
|
||||
<svg class="w-4 h-4 text-yellow-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 9v6m4-6v6m7-3a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
<svg class="w-4 h-4 text-warning" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 9v6m4-6v6m7-3a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
"Pause"
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<div class="h-px bg-white/10 my-1"></div>
|
||||
<div class="divider my-0 h-px p-0 opacity-10"></div>
|
||||
|
||||
<li>
|
||||
<button
|
||||
class="w-full text-left px-4 py-2.5 hover:bg-red-500/20 text-red-500 hover:text-red-400 flex items-center gap-3 transition-colors"
|
||||
class="gap-3 text-error hover:bg-error/10 active:bg-error active:text-error-content"
|
||||
on:click={
|
||||
let handle_action = handle_action.clone();
|
||||
move |_| handle_action("delete")
|
||||
@@ -108,9 +114,11 @@ pub fn ContextMenu(
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
|
||||
"Delete"
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<button
|
||||
class="w-full text-left px-4 py-2.5 hover:bg-red-900/20 text-red-600 hover:text-red-400 flex items-center gap-3 transition-colors text-xs"
|
||||
class="gap-3 text-error hover:bg-error/10 active:bg-error active:text-error-content text-xs"
|
||||
on:click={
|
||||
let handle_action = handle_action.clone();
|
||||
move |_| handle_action("delete_with_data")
|
||||
@@ -119,6 +127,8 @@ pub fn ContextMenu(
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
|
||||
<span>"Delete with Data"</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
}.into_view()
|
||||
}
|
||||
|
||||
@@ -19,10 +19,10 @@ pub fn Sidebar() -> impl IntoView {
|
||||
};
|
||||
|
||||
view! {
|
||||
<div class="w-64 h-full flex flex-col">
|
||||
<div class="p-4">
|
||||
<h2 class="text-xl font-bold px-4 mb-2 text-primary">"Filters"</h2>
|
||||
<div class="w-64 h-full flex flex-col bg-base-200 border-r border-base-300">
|
||||
<div class="p-2">
|
||||
<ul class="menu w-full rounded-box gap-1">
|
||||
<li class="menu-title text-primary uppercase font-bold px-4">"Filters"</li>
|
||||
<li>
|
||||
<a class={move || filter_class(crate::store::FilterStatus::All)} on:click=move |_| set_filter(crate::store::FilterStatus::All)>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
|
||||
@@ -71,9 +71,9 @@ pub fn Sidebar() -> impl IntoView {
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mt-auto p-4 border-t border-base-300">
|
||||
<h3 class="text-xs font-bold text-base-content/50 uppercase mb-2 px-4">"Trackers"</h3>
|
||||
<div class="mt-auto p-2 border-t border-base-300">
|
||||
<ul class="menu w-full rounded-box gap-1 text-sm">
|
||||
<li class="menu-title text-base-content/50 uppercase font-bold px-4">"Trackers"</li>
|
||||
<li><a>"All Trackers"</a></li>
|
||||
<li><a>"Error"</a></li>
|
||||
</ul>
|
||||
|
||||
@@ -23,6 +23,34 @@ pub fn StatusBar() -> impl IntoView {
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
<div class="dropdown dropdown-top dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost btn-xs btn-square" title="Change Theme">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4.098 19.902a3.75 3.75 0 0 0 5.304 0l6.401-6.402M6.75 21A3.75 3.75 0 0 1 3 17.25V4.125C3 3.504 3.504 3 4.125 3h5.25c.621 0 1.125.504 1.125 1.125v4.072c0 .657.66 1.175 1.312 1.133 3.421-.22 6.187 2.546 6.187 5.965 0 1.595-.572 3.064-1.524 4.195" />
|
||||
</svg>
|
||||
</div>
|
||||
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow bg-base-200 rounded-box w-52 mb-2 border border-base-300">
|
||||
{
|
||||
let themes = vec!["light", "dark", "cupcake", "dracula", "cyberpunk", "emerald", "luxury", "nord"];
|
||||
themes.into_iter().map(|theme| {
|
||||
view! {
|
||||
<li>
|
||||
<button
|
||||
class="text-xs capitalize"
|
||||
on:click=move |_| {
|
||||
let doc = web_sys::window().unwrap().document().unwrap();
|
||||
let _ = doc.document_element().unwrap().set_attribute("data-theme", theme);
|
||||
}
|
||||
>
|
||||
{theme}
|
||||
</button>
|
||||
</li>
|
||||
}
|
||||
}).collect::<Vec<_>>()
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-ghost btn-xs btn-square" title="Settings">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.212 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" />
|
||||
|
||||
@@ -5,51 +5,29 @@ pub fn Toolbar() -> impl IntoView {
|
||||
let (show_add_modal, set_show_add_modal) = create_signal(false);
|
||||
|
||||
view! {
|
||||
<div class="h-14 min-h-14 flex items-center px-4 border-b border-base-300 bg-base-100 gap-4">
|
||||
<div class="navbar min-h-14 h-14 bg-base-100 p-0">
|
||||
<div class="navbar-start gap-4 px-4">
|
||||
<label for="my-drawer" class="btn btn-square btn-ghost lg:hidden drawer-button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="inline-block w-5 h-5 stroke-current"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path></svg>
|
||||
</label>
|
||||
<div class="join">
|
||||
<button class="join-item btn btn-sm btn-outline gap-2" title="Open Torrent">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
</svg>
|
||||
"Open"
|
||||
</button>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
class="join-item btn btn-sm btn-outline gap-2"
|
||||
title="Magnet Link"
|
||||
class="btn btn-sm btn-primary gap-2 font-normal"
|
||||
title="Add Magnet Link"
|
||||
on:click=move |_| set_show_add_modal.set(true)
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
</svg>
|
||||
"URL"
|
||||
"Add Torrent"
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="join">
|
||||
<button class="join-item btn btn-sm btn-ghost" title="Start">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5 text-success">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button class="join-item btn btn-sm btn-ghost" title="Pause">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5 text-warning">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25v13.5m-7.5-13.5v13.5" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="join">
|
||||
<button class="join-item btn btn-sm btn-ghost text-error" title="Remove">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
<div class="navbar-end gap-2 px-4">
|
||||
<input type="text" placeholder="Filter..." class="input input-sm input-bordered w-full max-w-xs" />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -106,14 +106,14 @@ pub fn TorrentTable() -> impl IntoView {
|
||||
}
|
||||
};
|
||||
|
||||
let (selected_hash, set_selected_hash) = create_signal(Option::<String>::None);
|
||||
let (menu_visible, set_menu_visible) = create_signal(false);
|
||||
let (menu_position, set_menu_position) = create_signal((0, 0));
|
||||
let (active_hash, set_active_hash) = create_signal(String::new());
|
||||
|
||||
let handle_context_menu = move |e: web_sys::MouseEvent, hash: String| {
|
||||
e.prevent_default();
|
||||
set_menu_position.set((e.client_x(), e.client_y()));
|
||||
set_active_hash.set(hash);
|
||||
set_selected_hash.set(Some(hash)); // Select on right click too
|
||||
set_menu_visible.set(true);
|
||||
};
|
||||
|
||||
@@ -152,14 +152,10 @@ pub fn TorrentTable() -> impl IntoView {
|
||||
|
||||
view! {
|
||||
<div class="overflow-x-auto h-full bg-base-100 relative"> // Added relative for positioning context if needed, though menu is fixed
|
||||
<table class="table table-xs table-pin-rows w-full max-w-full whitespace-nowrap">
|
||||
<div class="hidden md:block h-full overflow-x-auto">
|
||||
<table class="table table-sm table-pin-rows w-full max-w-full whitespace-nowrap">
|
||||
<thead>
|
||||
<tr class="bg-base-200 text-base-content/70">
|
||||
<th class="w-8">
|
||||
<label>
|
||||
<input type="checkbox" class="checkbox checkbox-xs rounded-none" />
|
||||
</label>
|
||||
</th>
|
||||
<tr class="text-xs uppercase text-base-content/60 border-b border-base-200">
|
||||
<th class="cursor-pointer hover:bg-base-300 transition-colors group select-none" on:click=move |_| handle_sort(SortColumn::Name)>
|
||||
<div class="flex items-center">"Name" {move || sort_arrow(SortColumn::Name)}</div>
|
||||
</th>
|
||||
@@ -194,21 +190,32 @@ pub fn TorrentTable() -> impl IntoView {
|
||||
shared::TorrentStatus::Error => "text-error",
|
||||
_ => "text-base-content/50"
|
||||
};
|
||||
let t_hash = t.hash.clone(); // Clone for closure using it in handler
|
||||
let t_hash = t.hash.clone();
|
||||
let t_hash_click = t.hash.clone();
|
||||
|
||||
let is_selected_fn = move || {
|
||||
selected_hash.get() == Some(t_hash.clone())
|
||||
};
|
||||
|
||||
view! {
|
||||
<tr
|
||||
class="hover group border-b border-base-200 cursor-context-menu"
|
||||
class=move || {
|
||||
let base = "hover border-b border-base-200 transition-colors select-none";
|
||||
if is_selected_fn() {
|
||||
format!("{} bg-primary/10", base)
|
||||
} else {
|
||||
base.to_string()
|
||||
}
|
||||
}
|
||||
on:contextmenu={
|
||||
let t_hash = t_hash.clone();
|
||||
let t_hash = t_hash_click.clone();
|
||||
move |e: web_sys::MouseEvent| handle_context_menu(e, t_hash.clone())
|
||||
}
|
||||
on:click={
|
||||
let t_hash = t_hash_click.clone();
|
||||
move |_| set_selected_hash.set(Some(t_hash.clone()))
|
||||
}
|
||||
>
|
||||
<th>
|
||||
<label>
|
||||
<input type="checkbox" class="checkbox checkbox-xs rounded-none" />
|
||||
</label>
|
||||
</th>
|
||||
<td class="font-medium truncate max-w-xs" title={t.name.clone()}>
|
||||
{t.name}
|
||||
</td>
|
||||
@@ -228,12 +235,87 @@ pub fn TorrentTable() -> impl IntoView {
|
||||
}).collect::<Vec<_>>()}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="md:hidden grid grid-cols-1 gap-3 p-3 pb-20 overflow-y-auto h-full">
|
||||
{move || filtered_torrents().into_iter().map(|t| {
|
||||
let progress_class = if t.percent_complete >= 100.0 { "progress-success" } else { "progress-primary" };
|
||||
let status_str = format!("{:?}", t.status);
|
||||
let status_badge_class = match t.status {
|
||||
shared::TorrentStatus::Seeding => "badge-success badge-soft",
|
||||
shared::TorrentStatus::Downloading => "badge-primary badge-soft",
|
||||
shared::TorrentStatus::Paused => "badge-warning badge-soft",
|
||||
shared::TorrentStatus::Error => "badge-error badge-soft",
|
||||
_ => "badge-ghost"
|
||||
};
|
||||
let t_hash = t.hash.clone();
|
||||
let t_hash_click = t.hash.clone();
|
||||
let t_hash_ctx = t.hash.clone();
|
||||
|
||||
let is_selected_fn = move || {
|
||||
selected_hash.get() == Some(t_hash.clone())
|
||||
};
|
||||
|
||||
view! {
|
||||
<div
|
||||
class=move || {
|
||||
let base = "card card-compact bg-base-100 shadow-sm border border-base-200 transition-all active:scale-[0.99]";
|
||||
if is_selected_fn() {
|
||||
format!("{} ring-2 ring-primary", base)
|
||||
} else {
|
||||
base.to_string()
|
||||
}
|
||||
}
|
||||
on:contextmenu={
|
||||
let t_hash = t_hash_ctx.clone();
|
||||
move |e: web_sys::MouseEvent| handle_context_menu(e, t_hash.clone())
|
||||
}
|
||||
on:click={
|
||||
let t_hash = t_hash_click.clone();
|
||||
move |_| set_selected_hash.set(Some(t_hash.clone()))
|
||||
}
|
||||
>
|
||||
<div class="card-body gap-3">
|
||||
<div class="flex justify-between items-start gap-2">
|
||||
<h3 class="font-medium text-sm line-clamp-2 leading-tight">{t.name}</h3>
|
||||
<div class={format!("badge badge-xs text-[10px] whitespace-nowrap {}", status_badge_class)}>
|
||||
{status_str}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex justify-between text-[10px] opacity-70">
|
||||
<span>{format_bytes(t.size)}</span>
|
||||
<span>{format!("{:.1}%", t.percent_complete)}</span>
|
||||
</div>
|
||||
<progress class={format!("progress w-full h-1.5 {}", progress_class)} value={t.percent_complete} max="100"></progress>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-2 text-[10px] font-mono opacity-80 pt-1 border-t border-base-200/50">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-[9px] opacity-60 uppercase">"Down"</span>
|
||||
<span class="text-success">{format_speed(t.down_rate)}</span>
|
||||
</div>
|
||||
<div class="flex flex-col text-center border-l border-r border-base-200/50">
|
||||
<span class="text-[9px] opacity-60 uppercase">"Up"</span>
|
||||
<span class="text-primary">{format_speed(t.up_rate)}</span>
|
||||
</div>
|
||||
<div class="flex flex-col text-right">
|
||||
<span class="text-[9px] opacity-60 uppercase">"ETA"</span>
|
||||
<span>{if t.eta > 0 { format!("{}s", t.eta) } else { "∞".to_string() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}).collect::<Vec<_>>()}
|
||||
</div>
|
||||
|
||||
<Show when=move || menu_visible.get() fallback=|| ()>
|
||||
<crate::components::context_menu::ContextMenu
|
||||
visible=true
|
||||
position=menu_position.get()
|
||||
torrent_hash=active_hash.get()
|
||||
torrent_hash=selected_hash.get().unwrap_or_default() // Use selected_hash as source of truth
|
||||
on_close=Callback::from(move |_| set_menu_visible.set(false))
|
||||
on_action=Callback::from(on_action)
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user