[weaver-app] enable themes feature for server-side CSS generation

Orual 20fb6a00 84cc3e65

+1445 -2
+1 -1
crates/weaver-app/Cargo.toml
··· 57 57 jacquard-axum = { workspace = true, optional = true } 58 58 weaver-api = { path = "../weaver-api", features = ["com_whtwnd"] } 59 59 markdown-weaver = { workspace = true } 60 - weaver-renderer = { path = "../weaver-renderer" } 60 + weaver-renderer = { path = "../weaver-renderer", features = ["themes"] } 61 61 n0-future = { workspace = true } 62 62 dioxus-primitives = { git = "https://github.com/DioxusLabs/components", version = "0.0.1", default-features = false, features = ["router"] } 63 63 axum = { version = "0.8.6", optional = true }
+28
crates/weaver-app/src/components/checkbox/component.rs
··· 1 + use dioxus::prelude::*; 2 + use dioxus_primitives::checkbox::{self, CheckboxProps}; 3 + 4 + #[component] 5 + pub fn Checkbox(props: CheckboxProps) -> Element { 6 + rsx! { 7 + document::Link { rel: "stylesheet", href: asset!("./style.css") } 8 + checkbox::Checkbox { 9 + class: "checkbox", 10 + checked: props.checked, 11 + default_checked: props.default_checked, 12 + required: props.required, 13 + disabled: props.disabled, 14 + name: props.name, 15 + value: props.value, 16 + on_checked_change: props.on_checked_change, 17 + attributes: props.attributes, 18 + checkbox::CheckboxIndicator { class: "checkbox-indicator", 19 + svg { 20 + class: "checkbox-check-icon", 21 + view_box: "0 0 24 24", 22 + xmlns: "http://www.w3.org/2000/svg", 23 + path { d: "M5 13l4 4L19 7" } 24 + } 25 + } 26 + } 27 + } 28 + }
+2
crates/weaver-app/src/components/checkbox/mod.rs
··· 1 + mod component; 2 + pub use component::*;
+39
crates/weaver-app/src/components/checkbox/style.css
··· 1 + .checkbox { 2 + width: 1rem; 3 + height: 1rem; 4 + box-sizing: border-box; 5 + padding: 0; 6 + border: none; 7 + border-radius: 4px; 8 + margin: 0; 9 + background-color: var(--primary-color-3); 10 + box-shadow: inset 0 0 0 1px var(--primary-color-7); 11 + color: var(--secondary-color-4); 12 + cursor: pointer; 13 + } 14 + 15 + .checkbox-indicator { 16 + display: flex; 17 + align-items: center; 18 + justify-content: center; 19 + } 20 + 21 + .checkbox[data-state="checked"] { 22 + background-color: var(--secondary-color-2); 23 + box-shadow: none; 24 + color: var(--primary-color); 25 + } 26 + 27 + .checkbox:focus-visible { 28 + box-shadow: 0 0 0 2px var(--focused-border-color); 29 + } 30 + 31 + .checkbox-check-icon { 32 + width: 1rem; 33 + height: 1rem; 34 + fill: none; 35 + stroke: currentcolor; 36 + stroke-linecap: round; 37 + stroke-linejoin: round; 38 + stroke-width: 2; 39 + }
+47
crates/weaver-app/src/components/hover_card/component.rs
··· 1 + use dioxus::prelude::*; 2 + use dioxus_primitives::hover_card::{ 3 + self, HoverCardContentProps, HoverCardProps, HoverCardTriggerProps, 4 + }; 5 + 6 + #[component] 7 + pub fn HoverCard(props: HoverCardProps) -> Element { 8 + rsx! { 9 + document::Link { rel: "stylesheet", href: asset!("./style.css") } 10 + hover_card::HoverCard { 11 + class: "hover-card", 12 + open: props.open, 13 + default_open: props.default_open, 14 + on_open_change: props.on_open_change, 15 + disabled: props.disabled, 16 + attributes: props.attributes, 17 + {props.children} 18 + } 19 + } 20 + } 21 + 22 + #[component] 23 + pub fn HoverCardTrigger(props: HoverCardTriggerProps) -> Element { 24 + rsx! { 25 + hover_card::HoverCardTrigger { 26 + class: "hover-card-trigger", 27 + id: props.id, 28 + attributes: props.attributes, 29 + {props.children} 30 + } 31 + } 32 + } 33 + 34 + #[component] 35 + pub fn HoverCardContent(props: HoverCardContentProps) -> Element { 36 + rsx! { 37 + hover_card::HoverCardContent { 38 + class: "hover-card-content", 39 + side: props.side, 40 + align: props.align, 41 + id: props.id, 42 + force_mount: props.force_mount, 43 + attributes: props.attributes, 44 + {props.children} 45 + } 46 + } 47 + }
+2
crates/weaver-app/src/components/hover_card/mod.rs
··· 1 + mod component; 2 + pub use component::*;
+118
crates/weaver-app/src/components/hover_card/style.css
··· 1 + /* Hover Card Styles */ 2 + .hover-card { 3 + position: relative; 4 + display: inline-block; 5 + } 6 + 7 + .hover-card-trigger { 8 + display: inline-block; 9 + } 10 + 11 + .hover-card-content { 12 + position: absolute; 13 + z-index: 1000; 14 + min-width: 200px; 15 + padding: 5px; 16 + border: 1px solid var(--light, var(--primary-color-6)) 17 + var(--dark, var(--primary-color-7)); 18 + border-radius: 0.5rem; 19 + animation: hover-card-fade-in 0.1s ease-out; 20 + background: var(--light, var(--primary-color)) 21 + var(--dark, var(--primary-color-5)); 22 + 23 + /* Semi transparent shadow effect in light mode */ 24 + box-shadow: var(--light, 0 2px 10px #0000001a) var(--dark, none); 25 + } 26 + 27 + /* Positioning based on side */ 28 + .hover-card-content[data-side="top"] { 29 + position: absolute; 30 + bottom: 100%; 31 + left: 50%; 32 + margin-bottom: 10px; 33 + transform: translateX(-50%); 34 + } 35 + 36 + .hover-card-content[data-side="right"] { 37 + position: absolute; 38 + top: 50%; 39 + left: 100%; 40 + margin-left: 10px; 41 + transform: translateY(-50%); 42 + } 43 + 44 + .hover-card-content[data-side="bottom"] { 45 + position: absolute; 46 + top: 100%; 47 + left: 50%; 48 + margin-top: 10px; 49 + transform: translateX(-50%); 50 + } 51 + 52 + .hover-card-content[data-side="left"] { 53 + position: absolute; 54 + top: 50%; 55 + right: 100%; 56 + margin-right: 10px; 57 + transform: translateY(-50%); 58 + } 59 + 60 + /* Alignment styles for top and bottom */ 61 + .hover-card-content[data-side="top"][data-align="start"], 62 + .hover-card-content[data-side="bottom"][data-align="start"] { 63 + left: 0; 64 + transform: none; 65 + } 66 + 67 + .hover-card-content[data-side="top"][data-align="center"], 68 + .hover-card-content[data-side="bottom"][data-align="center"] { 69 + left: 50%; 70 + transform: translateX(-50%); 71 + } 72 + 73 + .hover-card-content[data-side="top"][data-align="end"], 74 + .hover-card-content[data-side="bottom"][data-align="end"] { 75 + right: 0; 76 + left: auto; 77 + transform: none; 78 + } 79 + 80 + /* Alignment styles for left and right */ 81 + .hover-card-content[data-side="left"][data-align="start"], 82 + .hover-card-content[data-side="right"][data-align="start"] { 83 + top: 0; 84 + transform: none; 85 + } 86 + 87 + .hover-card-content[data-side="left"][data-align="center"], 88 + .hover-card-content[data-side="right"][data-align="center"] { 89 + top: 50%; 90 + transform: translateY(-50%); 91 + } 92 + 93 + .hover-card-content[data-side="left"][data-align="end"], 94 + .hover-card-content[data-side="right"][data-align="end"] { 95 + top: auto; 96 + bottom: 0; 97 + transform: none; 98 + } 99 + 100 + /* Animation */ 101 + @keyframes hover-card-fade-in { 102 + from { 103 + opacity: 0; 104 + } 105 + 106 + to { 107 + opacity: 1; 108 + } 109 + } 110 + 111 + /* State styles */ 112 + .hover-card[data-disabled="true"] .hover-card-trigger { 113 + color: var(--secondary-color-5); 114 + } 115 + 116 + .hover-card-content[data-state="closed"] { 117 + display: none; 118 + }
+8
crates/weaver-app/src/components/mod.rs
··· 351 351 pub use notebook_actions::NotebookActions; 352 352 pub use profile_actions::{ProfileActions, ProfileActionsMenubar}; 353 353 pub mod toast; 354 + pub mod hover_card; 355 + pub mod switch; 356 + pub mod select; 357 + pub mod radio_group; 358 + pub mod slider; 359 + pub mod checkbox; 360 + pub mod toggle_group; 361 + pub mod tooltip;
+36
crates/weaver-app/src/components/radio_group/component.rs
··· 1 + use dioxus::prelude::*; 2 + use dioxus_primitives::radio_group::{self, RadioGroupProps, RadioItemProps}; 3 + 4 + #[component] 5 + pub fn RadioGroup(props: RadioGroupProps) -> Element { 6 + rsx! { 7 + document::Link { rel: "stylesheet", href: asset!("./style.css") } 8 + radio_group::RadioGroup { 9 + class: "radio-group", 10 + value: props.value, 11 + default_value: props.default_value, 12 + on_value_change: props.on_value_change, 13 + disabled: props.disabled, 14 + required: props.required, 15 + name: props.name, 16 + horizontal: props.horizontal, 17 + roving_loop: props.roving_loop, 18 + attributes: props.attributes, 19 + {props.children} 20 + } 21 + } 22 + } 23 + 24 + #[component] 25 + pub fn RadioItem(props: RadioItemProps) -> Element { 26 + rsx! { 27 + radio_group::RadioItem { 28 + class: "radio-item", 29 + value: props.value, 30 + index: props.index, 31 + disabled: props.disabled, 32 + attributes: props.attributes, 33 + {props.children} 34 + } 35 + } 36 + }
+2
crates/weaver-app/src/components/radio_group/mod.rs
··· 1 + mod component; 2 + pub use component::*;
+48
crates/weaver-app/src/components/radio_group/style.css
··· 1 + .radio-group { 2 + display: flex; 3 + flex-direction: column; 4 + gap: .75rem; 5 + } 6 + 7 + .radio-item { 8 + display: flex; 9 + flex-direction: row; 10 + align-items: center; 11 + padding: 0; 12 + border: none; 13 + background-color: transparent; 14 + color: var(--secondary-color-4); 15 + font-size: 14px; 16 + gap: .75rem; 17 + 18 + &::before { 19 + display: block; 20 + width: 1rem; 21 + height: 1rem; 22 + box-sizing: border-box; 23 + border-radius: 1.5rem; 24 + background: var(--light, var(--primary-color)) var(--dark, var(--primary-color-3)); 25 + box-shadow: 0 0 0 1px var(--light, var(--primary-color-6)) 26 + var(--dark, var(--primary-color-7)); 27 + content: ""; 28 + cursor: pointer; 29 + } 30 + 31 + &:focus-visible { 32 + outline: none; 33 + } 34 + 35 + &:focus-visible::before { 36 + box-shadow: 0 0 0 2px var(--focused-border-color); 37 + } 38 + 39 + &[data-state="checked"]::before { 40 + border: 0.25rem solid var(--light, var(--primary-color)) var(--dark, var(--primary-color-3)); 41 + background: var(--secondary-color-4); 42 + } 43 + 44 + &[data-disabled="true"]::before { 45 + cursor: not-allowed; 46 + opacity: 0.5; 47 + } 48 + }
+116
crates/weaver-app/src/components/select/component.rs
··· 1 + use dioxus::prelude::*; 2 + use dioxus_primitives::select::{ 3 + self, SelectGroupLabelProps, SelectGroupProps, SelectListProps, SelectOptionProps, SelectProps, 4 + SelectTriggerProps, SelectValueProps, 5 + }; 6 + 7 + #[component] 8 + pub fn Select<T: Clone + PartialEq + 'static>(props: SelectProps<T>) -> Element { 9 + rsx! { 10 + document::Link { rel: "stylesheet", href: asset!("./style.css") } 11 + select::Select { 12 + class: "select", 13 + value: props.value, 14 + default_value: props.default_value, 15 + on_value_change: props.on_value_change, 16 + disabled: props.disabled, 17 + name: props.name, 18 + placeholder: props.placeholder, 19 + roving_loop: props.roving_loop, 20 + typeahead_timeout: props.typeahead_timeout, 21 + attributes: props.attributes, 22 + {props.children} 23 + } 24 + } 25 + } 26 + 27 + #[component] 28 + pub fn SelectTrigger(props: SelectTriggerProps) -> Element { 29 + rsx! { 30 + select::SelectTrigger { class: "select-trigger", attributes: props.attributes, 31 + {props.children} 32 + svg { 33 + class: "select-expand-icon", 34 + view_box: "0 0 24 24", 35 + xmlns: "http://www.w3.org/2000/svg", 36 + polyline { points: "6 9 12 15 18 9" } 37 + } 38 + } 39 + } 40 + } 41 + 42 + #[component] 43 + pub fn SelectValue(props: SelectValueProps) -> Element { 44 + rsx! { 45 + select::SelectValue { attributes: props.attributes } 46 + } 47 + } 48 + 49 + #[component] 50 + pub fn SelectList(props: SelectListProps) -> Element { 51 + rsx! { 52 + select::SelectList { 53 + class: "select-list", 54 + id: props.id, 55 + attributes: props.attributes, 56 + {props.children} 57 + } 58 + } 59 + } 60 + 61 + #[component] 62 + pub fn SelectGroup(props: SelectGroupProps) -> Element { 63 + rsx! { 64 + select::SelectGroup { 65 + class: "select-group", 66 + disabled: props.disabled, 67 + id: props.id, 68 + attributes: props.attributes, 69 + {props.children} 70 + } 71 + } 72 + } 73 + 74 + #[component] 75 + pub fn SelectGroupLabel(props: SelectGroupLabelProps) -> Element { 76 + rsx! { 77 + select::SelectGroupLabel { 78 + class: "select-group-label", 79 + id: props.id, 80 + attributes: props.attributes, 81 + {props.children} 82 + } 83 + } 84 + } 85 + 86 + #[component] 87 + pub fn SelectOption<T: Clone + PartialEq + 'static>(props: SelectOptionProps<T>) -> Element { 88 + rsx! { 89 + select::SelectOption::<T> { 90 + class: "select-option", 91 + value: props.value, 92 + text_value: props.text_value, 93 + disabled: props.disabled, 94 + id: props.id, 95 + index: props.index, 96 + aria_label: props.aria_label, 97 + aria_roledescription: props.aria_roledescription, 98 + attributes: props.attributes, 99 + {props.children} 100 + } 101 + } 102 + } 103 + 104 + #[component] 105 + pub fn SelectItemIndicator() -> Element { 106 + rsx! { 107 + select::SelectItemIndicator { 108 + svg { 109 + class: "select-check-icon", 110 + view_box: "0 0 24 24", 111 + xmlns: "http://www.w3.org/2000/svg", 112 + path { d: "M5 13l4 4L19 7" } 113 + } 114 + } 115 + } 116 + }
+2
crates/weaver-app/src/components/select/mod.rs
··· 1 + mod component; 2 + pub use component::*;
+155
crates/weaver-app/src/components/select/style.css
··· 1 + .select { 2 + position: relative; 3 + } 4 + 5 + .select-trigger { 6 + position: relative; 7 + display: flex; 8 + box-sizing: border-box; 9 + flex-direction: row; 10 + align-items: center; 11 + justify-content: space-between; 12 + padding: 0.25rem; 13 + padding: 8px 12px; 14 + border: none; 15 + border-radius: 0.5rem; 16 + border-radius: calc(0.5rem); 17 + background: none; 18 + background: var(--light, var(--primary-color)) 19 + var(--dark, var(--primary-color-3)); 20 + box-shadow: inset 0 0 0 1px var(--light, var(--primary-color-6)) 21 + var(--dark, var(--primary-color-7)); 22 + color: var(--secondary-color-4); 23 + cursor: pointer; 24 + gap: 0.25rem; 25 + transition: background-color 100ms ease-out; 26 + } 27 + 28 + .select-trigger span[data-placeholder="true"] { 29 + color: var(--secondary-color-5); 30 + } 31 + 32 + .select[data-state="open"] .select-trigger { 33 + pointer-events: none; 34 + } 35 + 36 + .select-expand-icon { 37 + width: 20px; 38 + height: 20px; 39 + fill: none; 40 + stroke: var(--primary-color-7); 41 + stroke-linecap: round; 42 + stroke-linejoin: round; 43 + stroke-width: 2; 44 + } 45 + 46 + .select-check-icon { 47 + width: 1rem; 48 + height: 1rem; 49 + fill: none; 50 + stroke: var(--secondary-color-5); 51 + stroke-linecap: round; 52 + stroke-linejoin: round; 53 + stroke-width: 2; 54 + } 55 + 56 + .select[data-disabled="true"] .select-trigger { 57 + color: var(--secondary-color-5); 58 + cursor: not-allowed; 59 + } 60 + 61 + .select-trigger:hover:not([data-disabled="true"]), 62 + .select-trigger:focus-visible { 63 + background: var(--light, var(--primary-color-4)) 64 + var(--dark, var(--primary-color-5)); 65 + color: var(--secondary-color-1); 66 + outline: none; 67 + } 68 + 69 + .select-list { 70 + position: absolute; 71 + z-index: 1000; 72 + top: 100%; 73 + left: 0; 74 + min-width: 100%; 75 + box-sizing: border-box; 76 + padding: 0.25rem; 77 + border-radius: 0.5rem; 78 + margin-top: 0.25rem; 79 + background: var(--light, var(--primary-color)) 80 + var(--dark, var(--primary-color-5)); 81 + box-shadow: inset 0 0 0 1px var(--light, var(--primary-color-6)) 82 + var(--dark, var(--primary-color-7)); 83 + opacity: 0; 84 + pointer-events: none; 85 + transform-origin: top; 86 + will-change: transform, opacity; 87 + } 88 + 89 + .select-list[data-state="closed"] { 90 + animation: select-list-animate-out 150ms ease-in forwards; 91 + pointer-events: none; 92 + } 93 + 94 + @keyframes select-list-animate-out { 95 + 0% { 96 + opacity: 1; 97 + transform: scale(1) translateY(0); 98 + } 99 + 100 + 100% { 101 + opacity: 0; 102 + transform: scale(0.95) translateY(-2px); 103 + } 104 + } 105 + 106 + .select-list[data-state="open"] { 107 + animation: select-list-animate-in 150ms ease-out forwards; 108 + pointer-events: auto; 109 + } 110 + 111 + @keyframes select-list-animate-in { 112 + 0% { 113 + opacity: 0; 114 + transform: scale(0.95) translateY(-2px); 115 + } 116 + 117 + 100% { 118 + opacity: 1; 119 + transform: scale(1) translateY(0); 120 + } 121 + } 122 + 123 + .select-option { 124 + display: flex; 125 + align-items: center; 126 + justify-content: space-between; 127 + padding: 8px 12px; 128 + border-radius: calc(0.5rem - 0.25rem); 129 + cursor: pointer; 130 + font-size: 14px; 131 + } 132 + 133 + .select-option[data-disabled="true"] { 134 + color: var(--secondary-color-5); 135 + cursor: not-allowed; 136 + } 137 + 138 + .select-option:hover:not([data-disabled="true"]), 139 + .select-option:focus-visible { 140 + background: var(--light, var(--primary-color-4)) 141 + var(--dark, var(--primary-color-7)); 142 + color: var(--secondary-color-1); 143 + outline: none; 144 + } 145 + 146 + .select-group-label { 147 + padding: 4px 12px; 148 + color: var(--secondary-color-5); 149 + font-size: 0.75rem; 150 + } 151 + 152 + [data-disabled="true"] { 153 + cursor: not-allowed; 154 + opacity: 0.5; 155 + }
+52
crates/weaver-app/src/components/slider/component.rs
··· 1 + use dioxus::prelude::*; 2 + use dioxus_primitives::slider::{ 3 + self, SliderProps, SliderRangeProps, SliderThumbProps, SliderTrackProps, 4 + }; 5 + 6 + #[component] 7 + pub fn Slider(props: SliderProps) -> Element { 8 + rsx! { 9 + document::Link { rel: "stylesheet", href: asset!("./style.css") } 10 + slider::Slider { 11 + class: "slider", 12 + value: props.value, 13 + default_value: props.default_value, 14 + min: props.min, 15 + max: props.max, 16 + step: props.step, 17 + disabled: props.disabled, 18 + horizontal: props.horizontal, 19 + inverted: props.inverted, 20 + on_value_change: props.on_value_change, 21 + label: props.label, 22 + attributes: props.attributes, 23 + {props.children} 24 + } 25 + } 26 + } 27 + 28 + #[component] 29 + pub fn SliderTrack(props: SliderTrackProps) -> Element { 30 + rsx! { 31 + slider::SliderTrack { class: "slider-track", attributes: props.attributes, {props.children} } 32 + } 33 + } 34 + 35 + #[component] 36 + pub fn SliderRange(props: SliderRangeProps) -> Element { 37 + rsx! { 38 + slider::SliderRange { class: "slider-range", attributes: props.attributes, {props.children} } 39 + } 40 + } 41 + 42 + #[component] 43 + pub fn SliderThumb(props: SliderThumbProps) -> Element { 44 + rsx! { 45 + slider::SliderThumb { 46 + class: "slider-thumb", 47 + index: props.index, 48 + attributes: props.attributes, 49 + {props.children} 50 + } 51 + } 52 + }
+2
crates/weaver-app/src/components/slider/mod.rs
··· 1 + mod component; 2 + pub use component::*;
+76
crates/weaver-app/src/components/slider/style.css
··· 1 + .slider { 2 + position: relative; 3 + display: flex; 4 + width: 200px; 5 + align-items: center; 6 + padding: 0.5rem 0; 7 + touch-action: none; 8 + } 9 + 10 + .slider[data-orientation="vertical"] { 11 + width: auto; 12 + height: 200px; 13 + flex-direction: column; 14 + } 15 + 16 + .slider-track { 17 + position: relative; 18 + height: 0.5rem; 19 + box-sizing: border-box; 20 + flex-grow: 1; 21 + border-radius: 9999px; 22 + background: var(--primary-color-5); 23 + } 24 + 25 + .slider[data-orientation="vertical"] .slider-track { 26 + width: 4px; 27 + height: 100%; 28 + } 29 + 30 + .slider-range { 31 + position: absolute; 32 + height: 100%; 33 + border-radius: 9999px; 34 + background-color: var(--secondary-color-2); 35 + } 36 + 37 + .slider[data-orientation="vertical"] .slider-range { 38 + width: 100%; 39 + } 40 + 41 + .slider-thumb { 42 + all: unset; 43 + position: absolute; 44 + top: 50%; 45 + display: block; 46 + width: 16px; 47 + height: 16px; 48 + border: 1px solid var(--secondary-color-2); 49 + border-radius: 50%; 50 + background-color: var(--primary-color-1); 51 + cursor: pointer; 52 + transform: translate(-50%, -50%); 53 + transition: border-color 150ms; 54 + } 55 + 56 + .slider[data-orientation="vertical"] .slider-thumb { 57 + left: 50%; 58 + transform: translate(-50%, 50%); 59 + } 60 + 61 + .slider-thumb:focus-visible[data-dragging="true"], 62 + .slider-thumb:focus-visible, 63 + .slider-thumb:hover { 64 + box-shadow: 0 0 0 4px 65 + color-mix(in oklab, var(--primary-color-7) 50%, transparent); 66 + transition: box-shadow 150ms; 67 + } 68 + 69 + .slider[data-disabled="true"] { 70 + cursor: not-allowed; 71 + opacity: 0.5; 72 + } 73 + 74 + .slider[data-disabled="true"] .slider-thumb { 75 + cursor: not-allowed; 76 + }
+28
crates/weaver-app/src/components/switch/component.rs
··· 1 + use dioxus::prelude::*; 2 + use dioxus_primitives::switch::{self, SwitchProps, SwitchThumbProps}; 3 + 4 + #[component] 5 + pub fn Switch(props: SwitchProps) -> Element { 6 + rsx! { 7 + document::Link { rel: "stylesheet", href: asset!("./style.css") } 8 + switch::Switch { 9 + class: "switch", 10 + checked: props.checked, 11 + default_checked: props.default_checked, 12 + disabled: props.disabled, 13 + required: props.required, 14 + name: props.name, 15 + value: props.value, 16 + on_checked_change: props.on_checked_change, 17 + attributes: props.attributes, 18 + {props.children} 19 + } 20 + } 21 + } 22 + 23 + #[component] 24 + pub fn SwitchThumb(props: SwitchThumbProps) -> Element { 25 + rsx! { 26 + switch::SwitchThumb { class: "switch-thumb", attributes: props.attributes, {props.children} } 27 + } 28 + }
+2
crates/weaver-app/src/components/switch/mod.rs
··· 1 + mod component; 2 + pub use component::*;
+43
crates/weaver-app/src/components/switch/style.css
··· 1 + .switch-example { 2 + display: flex; 3 + align-items: center; 4 + padding: 20px; 5 + gap: 15px; 6 + } 7 + 8 + .switch { 9 + all: unset; 10 + position: relative; 11 + width: 2rem; 12 + height: 1.15rem; 13 + border-radius: 9999px; 14 + background-color: var(--primary-color-6); 15 + cursor: pointer; 16 + transition: background-color 150ms; 17 + } 18 + 19 + .switch[data-state="checked"] { 20 + background-color: var(--secondary-color-2); 21 + } 22 + 23 + .switch-thumb { 24 + display: block; 25 + width: calc(1.15rem - 2px); 26 + height: calc(1.15rem - 2px); 27 + border-radius: 9999px; 28 + background-color: var(--light, var(--primary-color)) var(--dark, var(--secondary-color-2)); 29 + transform: translateX(1px); 30 + transition: transform 150ms; 31 + will-change: transform; 32 + } 33 + 34 + .switch[data-state="checked"] .switch-thumb { 35 + background-color: var(--light, var(--primary-color)) var(--dark, var(--primary-color-3)); 36 + transform: translateX(calc(2rem - 1px - (1.15rem - 2px))); 37 + } 38 + 39 + /* Only apply disabled styles when data-disabled is "true" */ 40 + .switch[data-disabled="true"] { 41 + cursor: not-allowed; 42 + opacity: 0.5; 43 + }
+34
crates/weaver-app/src/components/toggle_group/component.rs
··· 1 + use dioxus::prelude::*; 2 + use dioxus_primitives::toggle_group::{self, ToggleGroupProps, ToggleItemProps}; 3 + 4 + #[component] 5 + pub fn ToggleGroup(props: ToggleGroupProps) -> Element { 6 + rsx! { 7 + document::Link { rel: "stylesheet", href: asset!("./style.css") } 8 + toggle_group::ToggleGroup { 9 + class: "toggle-group", 10 + default_pressed: props.default_pressed, 11 + pressed: props.pressed, 12 + on_pressed_change: props.on_pressed_change, 13 + disabled: props.disabled, 14 + allow_multiple_pressed: props.allow_multiple_pressed, 15 + horizontal: props.horizontal, 16 + roving_loop: props.roving_loop, 17 + attributes: props.attributes, 18 + {props.children} 19 + } 20 + } 21 + } 22 + 23 + #[component] 24 + pub fn ToggleItem(props: ToggleItemProps) -> Element { 25 + rsx! { 26 + toggle_group::ToggleItem { 27 + class: "toggle-item", 28 + index: props.index, 29 + disabled: props.disabled, 30 + attributes: props.attributes, 31 + {props.children} 32 + } 33 + } 34 + }
+2
crates/weaver-app/src/components/toggle_group/mod.rs
··· 1 + mod component; 2 + pub use component::*;
+59
crates/weaver-app/src/components/toggle_group/style.css
··· 1 + .toggle-group { 2 + width: fit-content; 3 + } 4 + 5 + .toggle-item { 6 + min-width: 35px; 7 + padding: 10px; 8 + border: none; 9 + border-radius: 0; 10 + background-color: transparent; 11 + color: var(--secondary-color-4); 12 + font-size: 14px; 13 + outline: none; 14 + transition: background-color 200ms ease, border 200ms ease; 15 + } 16 + 17 + .toggle-group[data-allow-multiple-pressed="true"] 18 + .toggle-item { 19 + border-top: 1px solid var(--primary-color-6); 20 + border-right: 1px solid var(--primary-color-6); 21 + border-bottom: 1px solid var(--primary-color-6); 22 + } 23 + 24 + .toggle-item:hover, 25 + .toggle-item:focus-visible { 26 + background-color: var(--primary-color-4); 27 + cursor: pointer; 28 + } 29 + 30 + .toggle-item[data-state="on"] { 31 + background-color: var(--primary-color-7); 32 + color: var(--secondary-color-1); 33 + } 34 + 35 + .toggle-group[data-allow-multiple-pressed="true"] 36 + .toggle-item[data-state="on"] { 37 + border-top: 1px solid var(--secondary-color-6); 38 + border-right: 1px solid var(--secondary-color-6); 39 + border-bottom: 1px solid var(--secondary-color-6); 40 + } 41 + 42 + .toggle-group[data-allow-multiple-pressed="true"] 43 + .toggle-item:first-child[data-state="on"] { 44 + border: 1px solid var(--secondary-color-6); 45 + } 46 + 47 + .toggle-item:first-child { 48 + border-bottom-left-radius: 0.5rem; 49 + border-top-left-radius: 0.5rem; 50 + } 51 + 52 + .toggle-group[data-allow-multiple-pressed="true"] .toggle-item:first-child { 53 + border: 1px solid var(--primary-color-6); 54 + } 55 + 56 + .toggle-item:last-child { 57 + border-bottom-right-radius: 0.5rem; 58 + border-top-right-radius: 0.5rem; 59 + }
+44
crates/weaver-app/src/components/tooltip/component.rs
··· 1 + use dioxus::prelude::*; 2 + use dioxus_primitives::tooltip::{self, TooltipContentProps, TooltipProps, TooltipTriggerProps}; 3 + 4 + #[component] 5 + pub fn Tooltip(props: TooltipProps) -> Element { 6 + rsx! { 7 + document::Link { rel: "stylesheet", href: asset!("./style.css") } 8 + tooltip::Tooltip { 9 + class: "tooltip", 10 + disabled: props.disabled, 11 + open: props.open, 12 + default_open: props.default_open, 13 + on_open_change: props.on_open_change, 14 + attributes: props.attributes, 15 + {props.children} 16 + } 17 + } 18 + } 19 + 20 + #[component] 21 + pub fn TooltipTrigger(props: TooltipTriggerProps) -> Element { 22 + rsx! { 23 + tooltip::TooltipTrigger { 24 + class: "tooltip-trigger", 25 + id: props.id, 26 + attributes: props.attributes, 27 + {props.children} 28 + } 29 + } 30 + } 31 + 32 + #[component] 33 + pub fn TooltipContent(props: TooltipContentProps) -> Element { 34 + rsx! { 35 + tooltip::TooltipContent { 36 + class: "tooltip-content", 37 + id: props.id, 38 + side: props.side, 39 + align: props.align, 40 + attributes: props.attributes, 41 + {props.children} 42 + } 43 + } 44 + }
+2
crates/weaver-app/src/components/tooltip/mod.rs
··· 1 + mod component; 2 + pub use component::*;
+150
crates/weaver-app/src/components/tooltip/style.css
··· 1 + /* Tooltip Styles */ 2 + .tooltip { 3 + position: relative; 4 + display: inline-block; 5 + } 6 + 7 + .tooltip-trigger { 8 + display: inline-block; 9 + } 10 + 11 + .tooltip-content { 12 + position: absolute; 13 + z-index: 1000; 14 + max-width: 250px; 15 + padding: 8px 12px; 16 + border-radius: 0.5rem; 17 + animation: tooltip-fade-in 0.2s ease-in-out; 18 + background-color: var(--secondary-color-4); 19 + color: var(--primary-color); 20 + font-size: 14px; 21 + line-height: 1.4; 22 + } 23 + 24 + .tooltip-content::after { 25 + position: absolute; 26 + border-width: 0.25rem; 27 + border-style: solid; 28 + margin-left: -0.25rem; 29 + content: " "; 30 + rotate: 45deg; 31 + } 32 + 33 + /* Positioning based on side */ 34 + .tooltip-content[data-side="top"] { 35 + position: absolute; 36 + bottom: 100%; 37 + left: 50%; 38 + margin-bottom: 8px; 39 + transform: translateX(-50%); 40 + } 41 + 42 + .tooltip-content[data-side="top"]::after { 43 + top: calc(100% - 0.25rem); 44 + left: 50%; 45 + border-color: var(--secondary-color-4); 46 + border-radius: 0 0 0.1rem; 47 + } 48 + 49 + .tooltip-content[data-side="right"] { 50 + position: absolute; 51 + top: 50%; 52 + left: 100%; 53 + margin-left: 8px; 54 + transform: translateY(-50%); 55 + } 56 + 57 + .tooltip-content[data-side="right"]::after { 58 + top: calc(50% - 0.25rem); 59 + left: 0; 60 + border-color: var(--secondary-color-4); 61 + border-radius: 0 0 0 0.1rem; 62 + } 63 + 64 + .tooltip-content[data-side="bottom"] { 65 + position: absolute; 66 + top: 100%; 67 + left: 50%; 68 + margin-top: 8px; 69 + transform: translateX(-50%); 70 + } 71 + 72 + .tooltip-content[data-side="bottom"]::after { 73 + bottom: calc(100% - 0.25rem); 74 + left: 50%; 75 + border-color: var(--secondary-color-4); 76 + border-radius: 0.1rem 0 0; 77 + } 78 + 79 + .tooltip-content[data-side="left"] { 80 + position: absolute; 81 + top: 50%; 82 + right: 100%; 83 + margin-right: 8px; 84 + transform: translateY(-50%); 85 + } 86 + 87 + .tooltip-content[data-side="left"]::after { 88 + top: calc(50% - 0.25rem); 89 + right: -0.25rem; 90 + border-color: var(--secondary-color-4); 91 + border-radius: 0 0.1rem 0 0; 92 + } 93 + 94 + /* Alignment styles for top and bottom */ 95 + .tooltip-content[data-side="top"][data-align="start"], 96 + .tooltip-content[data-side="bottom"][data-align="start"] { 97 + left: 0; 98 + transform: none; 99 + } 100 + 101 + .tooltip-content[data-side="top"][data-align="end"], 102 + .tooltip-content[data-side="bottom"][data-align="end"] { 103 + right: 0; 104 + left: auto; 105 + transform: none; 106 + } 107 + 108 + /* Alignment styles for left and right */ 109 + .tooltip-content[data-side="left"][data-align="start"], 110 + .tooltip-content[data-side="right"][data-align="start"] { 111 + top: 0; 112 + transform: none; 113 + } 114 + 115 + .tooltip-content[data-side="left"][data-align="center"], 116 + .tooltip-content[data-side="right"][data-align="center"] { 117 + top: 50%; 118 + transform: translateY(-50%); 119 + } 120 + 121 + .tooltip-content[data-side="left"][data-align="end"], 122 + .tooltip-content[data-side="right"][data-align="end"] { 123 + top: auto; 124 + bottom: 0; 125 + transform: none; 126 + } 127 + 128 + /* Animation */ 129 + @keyframes tooltip-fade-in { 130 + from { 131 + opacity: 0; 132 + } 133 + 134 + to { 135 + opacity: 1; 136 + } 137 + } 138 + 139 + /* State styles */ 140 + .tooltip[data-disabled="true"] .tooltip-trigger { 141 + cursor: default; 142 + } 143 + 144 + .tooltip-content[data-state="closed"] { 145 + display: none; 146 + } 147 + 148 + .tooltip-content[data-state="open"] { 149 + display: block; 150 + }
+88
docs/graph-data.json
··· 2430 2430 "created_at": "2026-01-07T23:38:17.591489488-05:00", 2431 2431 "updated_at": "2026-01-07T23:38:17.591489488-05:00", 2432 2432 "metadata_json": "{\"confidence\":80}" 2433 + }, 2434 + { 2435 + "id": 223, 2436 + "change_id": "c6bc88c6-ec9b-4bd8-b5fb-19a99b884694", 2437 + "node_type": "goal", 2438 + "title": "Phase 2: Custom domain hosting for notebooks", 2439 + "description": null, 2440 + "status": "pending", 2441 + "created_at": "2026-01-10T17:49:33.193809025-05:00", 2442 + "updated_at": "2026-01-10T17:49:33.193809025-05:00", 2443 + "metadata_json": "{\"confidence\":90,\"prompt\":\"User asked: plan phase 2 of subdomain hosting - custom domains with Caddy/Cloudflare TLS, site.standard.* indexing, CNAME encoding\"}" 2444 + }, 2445 + { 2446 + "id": 224, 2447 + "change_id": "e9483041-6861-4697-8f22-c32a2b771ce3", 2448 + "node_type": "action", 2449 + "title": "Wrote phase 2 implementation plan with custom path routing and landing pages", 2450 + "description": null, 2451 + "status": "pending", 2452 + "created_at": "2026-01-10T18:05:27.835814588-05:00", 2453 + "updated_at": "2026-01-10T18:05:27.835814588-05:00", 2454 + "metadata_json": "{\"confidence\":95}" 2455 + }, 2456 + { 2457 + "id": 225, 2458 + "change_id": "7fbf023e-55b9-47d4-81c2-e72d4aed6a68", 2459 + "node_type": "goal", 2460 + "title": "Notebook creation/modification UI + site.standard.* record creation", 2461 + "description": null, 2462 + "status": "pending", 2463 + "created_at": "2026-01-11T15:03:42.186678843-05:00", 2464 + "updated_at": "2026-01-11T15:03:42.186678843-05:00", 2465 + "metadata_json": "{\"branch\":\"main\",\"confidence\":80,\"prompt\":\"User: need components for creating notebooks, modifying settings (publishGlobal, theme), and enabling site.standard.publication/document creation for entries. Requires use-index feature.\"}" 2466 + }, 2467 + { 2468 + "id": 226, 2469 + "change_id": "e984ddb9-1347-4caf-8f60-acf4e3722c52", 2470 + "node_type": "action", 2471 + "title": "Wrote notebook creation/settings design doc", 2472 + "description": null, 2473 + "status": "pending", 2474 + "created_at": "2026-01-11T16:10:01.145513905-05:00", 2475 + "updated_at": "2026-01-11T16:10:01.145513905-05:00", 2476 + "metadata_json": "{\"confidence\":95}" 2477 + }, 2478 + { 2479 + "id": 227, 2480 + "change_id": "71f83579-7dd8-4545-a11d-a57390beaf7d", 2481 + "node_type": "action", 2482 + "title": "Wrote detailed implementation plan for notebook creation/settings", 2483 + "description": null, 2484 + "status": "pending", 2485 + "created_at": "2026-01-11T16:25:55.912466284-05:00", 2486 + "updated_at": "2026-01-11T16:25:55.912466284-05:00", 2487 + "metadata_json": "{\"confidence\":95}" 2433 2488 } 2434 2489 ], 2435 2490 "edges": [ ··· 4808 4863 "weight": 1.0, 4809 4864 "rationale": "Identifies next enhancement for editor-js", 4810 4865 "created_at": "2026-01-07T23:38:25.207053545-05:00" 4866 + }, 4867 + { 4868 + "id": 218, 4869 + "from_node_id": 223, 4870 + "to_node_id": 224, 4871 + "from_change_id": "c6bc88c6-ec9b-4bd8-b5fb-19a99b884694", 4872 + "to_change_id": "e9483041-6861-4697-8f22-c32a2b771ce3", 4873 + "edge_type": "leads_to", 4874 + "weight": 1.0, 4875 + "rationale": "Plan created for this goal", 4876 + "created_at": "2026-01-10T18:05:27.958002778-05:00" 4877 + }, 4878 + { 4879 + "id": 219, 4880 + "from_node_id": 225, 4881 + "to_node_id": 226, 4882 + "from_change_id": "7fbf023e-55b9-47d4-81c2-e72d4aed6a68", 4883 + "to_change_id": "e984ddb9-1347-4caf-8f60-acf4e3722c52", 4884 + "edge_type": "leads_to", 4885 + "weight": 1.0, 4886 + "rationale": "Design document for notebook creation goal", 4887 + "created_at": "2026-01-11T16:10:01.284910370-05:00" 4888 + }, 4889 + { 4890 + "id": 220, 4891 + "from_node_id": 226, 4892 + "to_node_id": 227, 4893 + "from_change_id": "e984ddb9-1347-4caf-8f60-acf4e3722c52", 4894 + "to_change_id": "71f83579-7dd8-4545-a11d-a57390beaf7d", 4895 + "edge_type": "leads_to", 4896 + "weight": 1.0, 4897 + "rationale": "Design doc led to implementation plan", 4898 + "created_at": "2026-01-11T16:25:56.055454076-05:00" 4811 4899 } 4812 4900 ] 4813 4901 }
+105
lexicons/domain/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.domain.defs", 4 + "defs": { 5 + "documentView": { 6 + "type": "object", 7 + "description": "Hydrated view of a document with re-hydrated content.", 8 + "required": [ 9 + "uri", 10 + "cid", 11 + "did", 12 + "rkey", 13 + "title", 14 + "path", 15 + "record", 16 + "indexedAt" 17 + ], 18 + "properties": { 19 + "cid": { 20 + "type": "string", 21 + "format": "cid" 22 + }, 23 + "did": { 24 + "type": "string", 25 + "format": "did" 26 + }, 27 + "entryIndex": { 28 + "type": "integer" 29 + }, 30 + "entryUri": { 31 + "type": "string", 32 + "format": "at-uri" 33 + }, 34 + "indexedAt": { 35 + "type": "string", 36 + "format": "datetime" 37 + }, 38 + "path": { 39 + "type": "string" 40 + }, 41 + "record": { 42 + "type": "unknown" 43 + }, 44 + "rkey": { 45 + "type": "string" 46 + }, 47 + "title": { 48 + "type": "string" 49 + }, 50 + "uri": { 51 + "type": "string", 52 + "format": "at-uri" 53 + } 54 + } 55 + }, 56 + "publicationView": { 57 + "type": "object", 58 + "description": "Hydrated view of a publication with domain info.", 59 + "required": [ 60 + "uri", 61 + "cid", 62 + "did", 63 + "rkey", 64 + "name", 65 + "domain", 66 + "record", 67 + "indexedAt" 68 + ], 69 + "properties": { 70 + "cid": { 71 + "type": "string", 72 + "format": "cid" 73 + }, 74 + "did": { 75 + "type": "string", 76 + "format": "did" 77 + }, 78 + "domain": { 79 + "type": "string" 80 + }, 81 + "indexedAt": { 82 + "type": "string", 83 + "format": "datetime" 84 + }, 85 + "name": { 86 + "type": "string" 87 + }, 88 + "notebookUri": { 89 + "type": "string", 90 + "format": "at-uri" 91 + }, 92 + "record": { 93 + "type": "unknown" 94 + }, 95 + "rkey": { 96 + "type": "string" 97 + }, 98 + "uri": { 99 + "type": "string", 100 + "format": "at-uri" 101 + } 102 + } 103 + } 104 + } 105 + }
+60
lexicons/domain/generateDocument.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.domain.generateDocument", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Generate a site.standard.document record from a weaver entry. Returns a ready-to-write record with fully hydrated BookEntryView in content.", 8 + "parameters": { 9 + "type": "params", 10 + "required": [ 11 + "entry", 12 + "publication", 13 + "path" 14 + ], 15 + "properties": { 16 + "entry": { 17 + "type": "string", 18 + "description": "AT-URI of the sh.weaver.notebook.entry to convert.", 19 + "format": "at-uri" 20 + }, 21 + "path": { 22 + "type": "string", 23 + "description": "URL path for the document." 24 + }, 25 + "publication": { 26 + "type": "string", 27 + "description": "AT-URI of the site.standard.publication this document belongs to.", 28 + "format": "at-uri" 29 + } 30 + } 31 + }, 32 + "output": { 33 + "encoding": "application/json", 34 + "schema": { 35 + "type": "object", 36 + "required": [ 37 + "record" 38 + ], 39 + "properties": { 40 + "record": { 41 + "type": "ref", 42 + "ref": "site.standard.document" 43 + } 44 + } 45 + } 46 + }, 47 + "errors": [ 48 + { 49 + "name": "PublicationNotFound" 50 + }, 51 + { 52 + "name": "EntryNotFound" 53 + }, 54 + { 55 + "name": "NotebookNotLinked" 56 + } 57 + ] 58 + } 59 + } 60 + }
+42
lexicons/domain/resolveByDomain.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.domain.resolveByDomain", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Resolve a publication by its custom domain.", 8 + "parameters": { 9 + "type": "params", 10 + "required": [ 11 + "domain" 12 + ], 13 + "properties": { 14 + "domain": { 15 + "type": "string", 16 + "description": "The custom domain to resolve (e.g., myblog.com)." 17 + } 18 + } 19 + }, 20 + "output": { 21 + "encoding": "application/json", 22 + "schema": { 23 + "type": "object", 24 + "required": [ 25 + "publication" 26 + ], 27 + "properties": { 28 + "publication": { 29 + "type": "ref", 30 + "ref": "sh.weaver.domain.defs#publicationView" 31 + } 32 + } 33 + } 34 + }, 35 + "errors": [ 36 + { 37 + "name": "DomainNotFound" 38 + } 39 + ] 40 + } 41 + } 42 + }
+51
lexicons/domain/resolveDocument.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.domain.resolveDocument", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Resolve a document by path within a publication. Returns re-hydrated content for weaver-backed documents.", 8 + "parameters": { 9 + "type": "params", 10 + "required": [ 11 + "publication", 12 + "path" 13 + ], 14 + "properties": { 15 + "path": { 16 + "type": "string", 17 + "description": "URL path within the publication (e.g., /posts/my-first-post)." 18 + }, 19 + "publication": { 20 + "type": "string", 21 + "description": "AT-URI of the publication.", 22 + "format": "at-uri" 23 + } 24 + } 25 + }, 26 + "output": { 27 + "encoding": "application/json", 28 + "schema": { 29 + "type": "object", 30 + "required": [ 31 + "document" 32 + ], 33 + "properties": { 34 + "document": { 35 + "type": "ref", 36 + "ref": "sh.weaver.domain.defs#documentView" 37 + } 38 + } 39 + } 40 + }, 41 + "errors": [ 42 + { 43 + "name": "PublicationNotFound" 44 + }, 45 + { 46 + "name": "DocumentNotFound" 47 + } 48 + ] 49 + } 50 + } 51 + }
+1 -1
lexicons/notebook/book.json
··· 45 45 "rating": { "type": "ref", "ref": "sh.weaver.notebook.defs#contentRating" }, 46 46 "publishGlobal": { 47 47 "type": "boolean", 48 - "description": "Notebook opts into accessiblity by path only without identity scoping. Path must be globally unique." 48 + "description": "Notebook opts into accessiblity by path only without identity scoping. Path must be globally unique and a valid subdomain." 49 49 } 50 50 } 51 51 }