styles(list): add initial styling to the ui

This commit is contained in:
ryan 2025-02-28 15:40:24 +03:00
parent d77a806fe7
commit 57c40aa1c4
3 changed files with 340 additions and 217 deletions

View file

@ -1,158 +1,271 @@
:root {
--primary: #2F3C7E; /* Deep navy */
--secondary: #f8f9fa; /* Light gray */
--accent: #dc3545; /* Alert red */
--text: #212529; /* Dark gray */
--border: #dee2e6; /* Light gray border */
--hover-bg: #e9ecef; /* Medium gray */
--success: #28a745; /* Green for positive actions */
}
/* Base Styles */
body { body {
font-family: Arial, sans-serif; font-family: 'Segoe UI', system-ui, sans-serif;
margin: 0; color: var(--text);
padding: 0; line-height: 1.6;
background-color: #f4f4f9; margin: 0;
color: #333; padding: 2rem;
background: var(--secondary);
} }
/* Header Styling */
h1 { h1 {
color: #1e88e5; color: var(--primary);
text-align: center; font-size: 2rem;
margin-bottom: 20px; font-weight: 600;
text-align: center;
} }
form { h2, h3 {
margin: 20px auto; color: var(--primary);
max-width: 600px; font-weight: 600;
padding: 20px; margin-bottom: 1.5rem;
background: white;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
form input, form textarea, form button {
display: block;
width: 100%;
margin-bottom: 15px;
padding: 10px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 4px;
}
form button {
background-color: #1e88e5;
color: white;
border: none;
cursor: pointer;
transition: background-color 0.3s ease;
}
form button:hover {
background-color: #1565c0;
}
ul {
list-style: none;
padding: 0;
}
ul li {
background: #fff;
margin: 10px 0;
padding: 15px;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
} }
/* Table Enhancements */
table { table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
background: white;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
border-radius: 8px;
overflow: hidden;
} }
th, td { th, td {
border: 1px solid #ddd; padding: 1.25rem 1.5rem;padding: 1rem;
padding: 8px; text-align: left;
border-bottom: 1px solid var(--border);
position: relative;
}
th:not(:last-child),
td:not(:last-child) {
border-right: 1px solid var(--border);
} }
th { th {
background-color: #f2f2f2; background: var(--primary);
color: white;
font-weight: 500;
} }
/* Style for the grid container */ tr:hover {
.grid-container { background: var(--hover-bg);
display: grid; }
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); /* Adjust the minimum width for cells */ tr:last-child td {
gap: 1px; /* Gap between cells */ border-bottom: none;
background-color: #ccc;/* Grid line color */
border: 1px solid #aaa; /* Outer border */
} }
/* Style for individual cells */ /* Editable Cells */
.editable-cell { .editable-cell {
display: flex; /* Use flexbox for better layout control */ position: relative;
flex-direction: column; /* Stack children vertically */
width: 100%; /* Full width of the allocated space */
height: 100%; /* Full height of the allocated space */
position: relative; /* Relative positioning for absolute children */
box-sizing: border-box; /* Ensure padding and border are included in width/height */
} }
/* Style for the input field inside the editable cell */
.editable-cell-input { .editable-cell-input {
width: 100%; /* Ensure input takes up full width */ width: 100%;
height: 100%; /* Ensure input takes up full height */ border: none;
border: none; /* Remove input box borders */ padding: 0.5rem;
padding: 8px; /* Add padding for spacing */ background: transparent;
box-sizing: border-box; /* Ensure padding doesn't cause overflow */ font: inherit;
font-size: 14px; /* Adjust font size */ color: inherit;
text-align: left; /* Align text to the left */ transition: background 0.2s;
outline: none; /* Remove outline for better UI */
background-color: transparent; /* Make background transparent */
} }
/* Style for the focused input field */
.editable-cell-input:focus { .editable-cell-input:focus {
background-color: #e0f7fa; /* Light blue background when focused */ outline: none;
border: 1px solid #00796b; /* Green border when focused */ background: rgba(47, 60, 126, 0.05);
border-radius: 4px;
} }
/* Style for the suggestions list */ /* Suggestions List */
.editable-cell-suggestions { .editable-cell-suggestions {
position: absolute; /* Position suggestions absolutely within the cell */ position: absolute;
top: 100%; /* Place suggestions below the input field */ top: 100%;
left: 0; /* Align suggestions with the left edge of the cell */ left: 0;
width: 100%; /* Full width of the cell */ margin-top: 0.5rem;
max-height: 200px; /* Limit height of suggestions list */ width: 100%;
overflow-y: auto; /* Add scrollbar if suggestions exceed max height */ z-index: 1000;
background-color: white; /* White background for suggestions */ background: white;
border: 1px solid #ddd; /* Light border for suggestions */ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
z-index: 10; /* Ensure suggestions appear above other content */ border-radius: 4px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Add shadow for better visibility */ max-height: 200px;
overflow-y: auto;
padding: 0.5rem 0;
} }
/* Style for individual suggestion items */ .editable-cell-suggestions-li {
.editable-cell-suggestions li { padding: 0.75rem 1rem;
padding: 8px; /* Add padding for spacing */ cursor: pointer;
cursor: pointer; /* Change cursor to pointer on hover */ font-size: 0.9em;
border-bottom: 1px solid #eee; /* Add separator between items */ color: var(--text);
transition: background 0.1s ease;
} }
.editable-cell-suggestions li:hover { .editable-cell-suggestions-li:hover {
background-color: #f5f5f5; /* Light gray background on hover */ background: var(--hover-bg);
} }
.search-icon { .editable-cell-suggestions .close-button {
margin-left: 10px; position: absolute;
padding: 5px; top: 0.75rem;
border: none; right: 0.75rem;
background-color: transparent; padding: 0.3rem;
cursor: pointer; background: none;
border: none;
color: var(--text);
cursor: pointer;
opacity: 0.6;
transition: opacity 0.2s;
} }
.search-icon i { .editable-cell-suggestions .close-button:hover {
font-size: 18px; opacity: 1;
color: #333; color: var(--accent);
} }
.search-icon i.fas.fa-search { /* Buttons & Icons */
margin-left: 5px; button {
background: var(--primary);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
transition: opacity 0.2s;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
button:hover {
opacity: 0.9;
}
button.delete {
background: var(--accent);
color: white;
padding: 0.5rem 0.75rem;
border-radius: 4px;
margin-left: 1rem;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
button.delete:hover {
background: #c82333;
transform: translateY(-1px);
}
button.delete::before {
content: "×";
font-size: 1.25rem;
line-height: 1;
} }
.editable-cell-textarea { button.search-icon {
width: 100%; background: transparent;
height: 100px; color: var(--primary);
resize: vertical; margin-left: 0.5rem;
overflow: auto; padding: 0.5rem 0.75rem;
border: 1px solid var(--border);
border-radius: 4px;
transition: all 0.2s ease;
white-space: nowrap;
}
button.search-icon:hover {
background: var(--hover-bg);
border-color: var(--primary);
}
.fa-search {
font-size: 0.9em;
}
/* Input Enhancements */
input[type="text"], textarea {
border: 1px solid var(--border);
border-radius: 4px;
padding: 0.75rem;
width: 100%;
transition: border-color 0.2s;
}
input[type="text"]:focus, textarea:focus {
border-color: var(--primary);
outline: none;
}
/* Layout Improvements */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
/* DataList Styling */
datalist {
border: 1px solid var(--border);
border-radius: 6px;
margin-top: 0.5rem;
}
/* Add Property Section Styling */
.add-property-section {
margin: 2rem 0;
padding: 1.5rem;
background: white;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
}
#new-property {
border: 2px solid var(--border);
padding: 0.75rem 1rem;
border-radius: 6px;
width: 300px;
margin-right: 1rem;
transition: border-color 0.2s ease;
}
#new-property:focus {
border-color: var(--primary);
outline: none;
}
.add-property-button {
background: var(--primary);
color: white;
padding: 0.75rem 1.5rem;
border-radius: 6px;
transition: all 0.2s ease;
}
.add-property-button:hover {
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(47, 60, 126, 0.1);
}
/* Container for cell content */
.cell-content {
overflow: hidden;
text-overflow:clip;
white-space: nowrap;
min-height: 2rem;
display: flex;
align-items: center;
}
/* Container for name cell content */
.name-cell-container {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0;
} }

View file

@ -48,7 +48,7 @@ pub fn App() -> impl IntoView {
view! { view! {
<Stylesheet href="/assets/style.css" /> <Stylesheet href="/assets/style.css" />
<Stylesheet href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" /> <Stylesheet href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" />
<div> <div class="container">
<h1>{ "CompareWare" }</h1> <h1>{ "CompareWare" }</h1>
<ItemsList <ItemsList
url=current_url() url=current_url()

View file

@ -700,7 +700,7 @@ pub fn ItemsList(
view! { view! {
<div> <div>
<h1>{ "Items List" }</h1> <h1>{ "Items List" }</h1>
<table> <table class="data-table">
<thead> <thead>
<tr> <tr>
<th>{ "Property" }</th> <th>{ "Property" }</th>
@ -709,7 +709,7 @@ pub fn ItemsList(
view! { view! {
<th> <th>
{item.name.clone()} {item.name.clone()}
<button on:click=move |_| remove_item(index)>{ "Delete" }</button> <button class="delete" on:click=move |_| remove_item(index)>{ "Delete" }</button>
</th> </th>
} }
}).collect::<Vec<_>>()} }).collect::<Vec<_>>()}
@ -728,106 +728,116 @@ pub fn ItemsList(
<td> <td>
{match property { {match property {
"Name" => view! { "Name" => view! {
<div class="editable-cell"> <div class="cell-content">
<EditableCell <div class="name-cell-container">
value=item.name.clone() <EditableCell
on_input=move |value| { value=item.name.clone()
update_item_clone(index, "name", value.clone()); on_input=move |value| {
fetch_wikidata_suggestions(format!("name-{}", index), value); update_item_clone(index, "name", value.clone());
} fetch_wikidata_suggestions(format!("name-{}", index), value);
key=Arc::new(format!("name-{}", index)) }
focused_cell=focused_cell key=Arc::new(format!("name-{}", index))
set_focused_cell=set_focused_cell.clone() focused_cell=focused_cell
on_focus=Some(Callback::new(move |_| { set_focused_cell=set_focused_cell.clone()
log!("Input focused, showing suggestions"); on_focus=Some(Callback::new(move |_| {
log!("Input focused, showing suggestions");
set_show_suggestions.update(|suggestions| {
suggestions.insert(format!("name-{}", index), true);
});
}))
on_blur=Some(Callback::new(move |_| {
log!("Input blurred, delaying hiding suggestions");
spawn_local(async move {
gloo_timers::future::sleep(std::time::Duration::from_millis(500)).await;
log!("Hiding suggestions after delay");
set_show_suggestions.update(|suggestions| {
suggestions.insert(format!("name-{}", index), false);
});
});
}))
input_type=InputType::Text
/>
<button class="search-icon" on:click=move |_| {
log!("Search icon clicked, showing suggestions");
set_show_suggestions.update(|suggestions| { set_show_suggestions.update(|suggestions| {
suggestions.insert(format!("name-{}", index), true); suggestions.insert(format!("name-{}", index), true);
}); });
})) }>
on_blur=Some(Callback::new(move |_| { <i class="fas fa-search"></i> Search Wiki
log!("Input blurred, delaying hiding suggestions"); </button>
spawn_local(async move {
gloo_timers::future::sleep(std::time::Duration::from_millis(500)).await;
log!("Hiding suggestions after delay");
set_show_suggestions.update(|suggestions| {
suggestions.insert(format!("name-{}", index), false);
});
});
}))
input_type=InputType::Text
/>
<button class="search-icon" on:click=move |_| {
log!("Search icon clicked, showing suggestions");
set_show_suggestions.update(|suggestions| {
suggestions.insert(format!("name-{}", index), true);
});
}>
<i class="fas fa-search"></i> Search Wiki
</button>
{move || {
if *show_suggestions.get().get(&format!("name-{}", index)).unwrap_or(&false) {
log!("Rendering suggestions list");
view! {
<ul class="editable-cell-suggestions">
{move || {
let suggestions = wikidata_suggestions.get()
.get(&format!("name-{}", index))
.cloned()
.unwrap_or_default();
log!("Suggestions for cell {}: {:?}", index, suggestions);
suggestions.into_iter().map(|suggestion| {
let label_for_click = suggestion.label.clone();
let label_for_display = suggestion.label.clone();
let description_for_click = suggestion.description.clone().unwrap_or_default();
let description_for_display = suggestion.description.clone().unwrap_or_default();
let id = suggestion.id.clone();
view! {
<li class="editable-cell-suggestions-li" on:click=move |_| {
// Update item with basic suggestion details
set_items.update(|items| {
if let Some(item) = items.get_mut(index) {
item.description = description_for_click.clone();
item.wikidata_id = Some(id.clone());
item.name = label_for_click.clone();
}
});
// Fetch additional properties from Wikidata {move || {
let wikidata_id = id.clone(); if *show_suggestions.get().get(&format!("name-{}", index)).unwrap_or(&false) {
spawn_local(async move { log!("Rendering suggestions list");
let properties = fetch_item_properties(&wikidata_id).await; view! {
// log!("Fetched properties for Wikidata ID {}: {:?}", wikidata_id, properties); <ul class="editable-cell-suggestions">
{move || {
// Populate the custom properties for the new item let suggestions = wikidata_suggestions.get()
.get(&format!("name-{}", index))
.cloned()
.unwrap_or_default();
log!("Suggestions for cell {}: {:?}", index, suggestions);
suggestions.into_iter().map(|suggestion| {
let label_for_click = suggestion.label.clone();
let label_for_display = suggestion.label.clone();
let description_for_click = suggestion.description.clone().unwrap_or_default();
let description_for_display = suggestion.description.clone().unwrap_or_default();
let id = suggestion.id.clone();
view! {
<li class="editable-cell-suggestions-li" on:click=move |_| {
// Update item with basic suggestion details
set_items.update(|items| { set_items.update(|items| {
if let Some(item) = items.iter_mut().find(|item| item.wikidata_id.as_ref() == Some(&wikidata_id)) { if let Some(item) = items.get_mut(index) {
for (property, value) in properties { item.description = description_for_click.clone();
item.custom_properties.insert(property, value); item.wikidata_id = Some(id.clone());
} item.name = label_for_click.clone();
} }
}); });
});
// Hide the suggestion list // Fetch additional properties from Wikidata
set_show_suggestions.update(|suggestions| { let wikidata_id = id.clone();
suggestions.insert(format!("name-{}", index), false); spawn_local(async move {
log!("Updated show_suggestions: {:?}", suggestions); let properties = fetch_item_properties(&wikidata_id).await;
}); // log!("Fetched properties for Wikidata ID {}: {:?}", wikidata_id, properties);
}>
{ format!("{} - {}", label_for_display, description_for_display) } // Populate the custom properties for the new item
</li> set_items.update(|items| {
} if let Some(item) = items.iter_mut().find(|item| item.wikidata_id.as_ref() == Some(&wikidata_id)) {
}).collect::<Vec<_>>() for (property, value) in properties {
}} item.custom_properties.insert(property, value);
</ul> }
}
});
});
// Hide the suggestion list
set_show_suggestions.update(|suggestions| {
suggestions.insert(format!("name-{}", index), false);
log!("Updated show_suggestions: {:?}", suggestions);
});
}>
<button class="close-button" on:click=move |_| {
set_show_suggestions.update(|suggestions| {
suggestions.insert(format!("name-{}", index), false);
});
}>
<i class="fas fa-times"></i>
</button>
{ format!("{} - {}", label_for_display, description_for_display) }
</li>
}
}).collect::<Vec<_>>()
}}
</ul>
}
} else {
log!("Suggestions list hidden");
view! {
<ul></ul>
}
} }
} else { }}
log!("Suggestions list hidden"); </div>
view! {
<ul></ul>
}
}
}}
</div> </div>
}.into_view(), }.into_view(),
"Description" => view! { "Description" => view! {
@ -875,7 +885,7 @@ pub fn ItemsList(
<tr> <tr>
<td> <td>
{ property_label } { property_label }
<button class="delete-property" on:click=move |_| { <button class="delete" on:click=move |_| {
log!("Deleting property: {}", property_clone_for_button); log!("Deleting property: {}", property_clone_for_button);
remove_property_clone(property_clone_for_button.clone()); remove_property_clone(property_clone_for_button.clone());
set_custom_properties.update(|props| { set_custom_properties.update(|props| {
@ -921,7 +931,7 @@ pub fn ItemsList(
}} }}
</tbody> </tbody>
</table> </table>
<div style="margin-bottom: 20px;"> <div class="add-property-section">
<input type="text" id="new-property" placeholder="Add New Property" list="properties" on:keydown=move |event| { <input type="text" id="new-property" placeholder="Add New Property" list="properties" on:keydown=move |event| {
if event.key() == "Enter"{ if event.key() == "Enter"{
let input_element = event.target().unwrap().dyn_into::<web_sys::HtmlInputElement>().unwrap(); let input_element = event.target().unwrap().dyn_into::<web_sys::HtmlInputElement>().unwrap();