Compare commits

..

No commits in common. "5a1454e6c2f63016fd653ad04f4a70bea836bd59" and "2bbb46ced190db05c3d779889fe0482751af730f" have entirely different histories.

24 changed files with 26 additions and 2175 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -1,8 +1,7 @@
[package]
name = "color-module"
version = "0.1.9"
edition = "2024"
rust-version = "1.88.0"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
@ -10,18 +9,16 @@ crate-type = ["cdylib"]
[dependencies]
color-name = "1.1.0"
enum-iterator = "2.1.0"
getrandom = { version = "0.3.3", features = ["wasm_js"] }
internment = { version = "0.8.6", features = ["arc"] }
linked-hash-map = { version = "0.5.6", features = ["serde", "serde_impl"] }
linked_hash_set = { version = "0.1.5", features = ["serde"] }
palette = { version = "0.7.6", features = ["serde"] }
serde = { version = "1.0.219", features = ["derive"] }
serde = { version = "1.0.216", features = ["derive"] }
serde-wasm-bindgen = "0.6.5"
serde_json = "1.0.140"
serde_repr = "0.1.20"
strum = { version = "0.27.1", features = ["derive", "strum_macros"] }
strum_macros = "0.27.1"
thiserror = "2.0.12"
serde_json = "1.0.134"
serde_repr = "0.1.19"
strum = { version = "0.26.3", features = ["derive", "strum_macros"] }
strum_macros = "0.26.4"
thiserror = "2.0.9"
wasm-bindgen = { version = "0.2.99", features = ["serde", "serde_json", "serde-serialize"] }
web-sys = {version = "0.3.77", features = ["console", "Window"]}

View File

@ -1,2 +1,2 @@
#!/bin/bash
RUSTFLAGS='--cfg getrandom_backend="wasm_js"' wasm-pack build --release --target web -d ../color_functions
wasm-pack build --release --target web -d ../color_functions

View File

@ -1,12 +0,0 @@
use palette::Oklch;
use serde::Serializer;
use crate::convert::map_oklch_to_srgb_hex;
pub fn serialize_oklch_to_hex<S>(color: &Oklch, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let hex_color = map_oklch_to_srgb_hex(color);
serializer.serialize_str(&hex_color)
}

View File

@ -1,10 +1,10 @@
use std::str::FromStr;
use palette::{
FromColor, Hsl, IntoColor, IsWithinBounds, Lab, Oklch, Srgb,
cam16::{Cam16Jch, Parameters},
color_difference::Wcag21RelativeContrast,
convert::FromColorUnclamped,
FromColor, Hsl, IntoColor, IsWithinBounds, Lab, Oklch, Srgb,
};
use wasm_bindgen::prelude::*;
@ -14,7 +14,6 @@ mod color_differ;
mod color_shifting;
mod convert;
mod errors;
mod foreign_serializer;
mod palettes;
mod reversing;
mod schemes;
@ -140,6 +139,10 @@ pub fn wacg_relative_contrast(fg_color: &str, bg_color: &str) -> Result<f32, err
#[macro_export]
macro_rules! cond {
($s: expr, $a: expr, $b: expr) => {
if $s { $a } else { $b }
if $s {
$a
} else {
$b
}
};
}

View File

@ -1,20 +1,18 @@
use std::collections::HashMap;
use internment::Intern;
use material_design_2::MaterialDesign2Scheme;
use material_design_3::MaterialDesign3Scheme;
use material_design_3_dynamic::{Variant, build_baseline, build_dynamic_scheme, build_swatches};
use material_design_3_dynamic::{build_baseline, build_dynamic_scheme, build_swatches, Variant};
use q_style::{QScheme, SchemeSetting};
use swatch_style::{SwatchEntry, SwatchSchemeSetting};
use wasm_bindgen::{JsValue, prelude::wasm_bindgen};
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
use crate::{errors, schemes::q_style_2::QScheme2};
use crate::errors;
pub mod material_design_2;
pub mod material_design_3;
pub mod material_design_3_dynamic;
pub mod q_style;
pub mod q_style_2;
pub mod swatch_style;
pub trait SchemeExport {
@ -24,10 +22,6 @@ pub trait SchemeExport {
fn output_javascript_object(&self) -> String;
}
pub fn get_static_str(s: &str) -> &'static str {
Intern::new(s.to_string()).as_ref()
}
#[wasm_bindgen]
pub fn generate_material_design_3_scheme(
source_color: &str,
@ -141,50 +135,6 @@ pub fn generate_q_scheme_manually(
.map_err(|_| errors::ColorError::UnableToAssembleOutput)?)
}
#[wasm_bindgen]
pub fn generate_q_scheme_2_manually(
primary_color: &str,
secondary_color: Option<String>,
tertiary_color: Option<String>,
accent_color: Option<String>,
danger_color: &str,
success_color: &str,
warn_color: &str,
info_color: &str,
fg_color: &str,
bg_color: &str,
custom_colors: JsValue,
setting: SchemeSetting,
) -> Result<JsValue, errors::ColorError> {
let mut scheme = QScheme2::new(
primary_color,
secondary_color.as_deref(),
tertiary_color.as_deref(),
accent_color.as_deref(),
danger_color,
success_color,
warn_color,
info_color,
fg_color,
bg_color,
setting,
)?;
let custom_colors: HashMap<String, String> = serde_wasm_bindgen::from_value(custom_colors)
.map_err(|_| errors::ColorError::UnableToParseArgument)?;
for (name, color) in custom_colors {
scheme.add_custom_color(&name, &color)?;
}
Ok(serde_wasm_bindgen::to_value(&(
scheme.clone(),
scheme.output_css_variables(),
scheme.output_css_auto_scheme_variables(),
scheme.output_scss_variables(),
scheme.output_javascript_object(),
))
.map_err(|_| errors::ColorError::UnableToAssembleOutput)?)
}
#[wasm_bindgen]
pub fn generate_swatch_scheme(
colors: Vec<SwatchEntry>,

View File

@ -3,6 +3,7 @@ use std::str::FromStr;
use baseline::Baseline;
use linked_hash_set::LinkedHashSet;
use palette::FromColor;
use scheme_setting::{ColorExpand, WACGSetting};
use serde::Serialize;
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
@ -15,8 +16,7 @@ mod color_set;
mod neutral_swatch;
mod scheme_setting;
pub use neutral_swatch::NeutralSwatch;
pub use scheme_setting::{ColorExpand, ColorShifting, SchemeSetting, WACGSetting};
pub use scheme_setting::{ColorShifting, SchemeSetting};
#[derive(Debug, Clone, Serialize)]
pub struct QScheme {

View File

@ -1,513 +0,0 @@
use std::{collections::HashMap, sync::Arc};
use linked_hash_map::LinkedHashMap;
use palette::{
Oklch, ShiftHue,
color_theory::{Analogous, Complementary, SplitComplementary, Tetradic, Triadic},
};
use serde::Serialize;
use crate::{
convert::map_oklch_to_srgb_hex,
errors,
schemes::{
q_style::{ColorExpand, NeutralSwatch, SchemeSetting},
q_style_2::{color_set::ColorSet, swatch::Swatch},
},
};
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ColorUnit {
pub root: ColorSet,
pub surface: ColorSet,
pub swatch: Swatch,
}
impl ColorUnit {
pub fn new(
color: &Oklch,
neutral_swatch: &Arc<NeutralSwatch>,
foreground_lightness: f32,
settings: &Arc<SchemeSetting>,
) -> Self {
let root = ColorSet::new(color, neutral_swatch, foreground_lightness, settings);
let surface = root.generate_surface_set();
let swatch = root.generate_swatch();
Self {
root,
surface,
swatch,
}
}
pub fn to_css_variables(&self, prefix: &str, name: &str) -> Vec<String> {
let mut css_variables = Vec::new();
css_variables.extend(self.root.to_css_variables(prefix, name));
css_variables.extend(self.surface.to_css_variables(prefix, name));
css_variables.extend(self.swatch.to_css_variables(prefix, name));
css_variables
}
pub fn to_css_auto_scheme_collection(&self, name: &str) -> LinkedHashMap<String, String> {
let mut css_auto_scheme_collection = LinkedHashMap::new();
css_auto_scheme_collection.extend(self.root.to_css_auto_scheme_collection(name));
css_auto_scheme_collection.extend(self.surface.to_css_auto_scheme_collection(name));
css_auto_scheme_collection.extend(self.swatch.to_css_auto_scheme_collection(name));
css_auto_scheme_collection
}
pub fn to_scss_variables(&self, prefix: &str, name: &str) -> Vec<String> {
let mut scss_variables = Vec::new();
scss_variables.extend(self.root.to_scss_variables(prefix, name));
scss_variables.extend(self.surface.to_scss_variables(prefix, name));
scss_variables.extend(self.swatch.to_scss_variables(prefix, name));
scss_variables
}
pub fn to_javascript_fields(&self, name: &str) -> Vec<String> {
let mut js_object_fields = Vec::new();
js_object_fields.extend(self.root.to_javascript_fields(name));
js_object_fields.extend(self.surface.to_javascript_fields(name));
js_object_fields.extend(self.swatch.to_javascript_fields(name));
js_object_fields
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Baseline {
pub primary: ColorUnit,
pub secondary: Option<ColorUnit>,
pub tertiary: Option<ColorUnit>,
pub accent: Option<ColorUnit>,
pub neutral: ColorSet,
pub neutral_variant: ColorSet,
pub surface: ColorSet,
pub surface_variant: ColorSet,
#[serde(serialize_with = "crate::schemes::q_style_2::swatch::serialize_neutral_swatch")]
pub neutral_swatch: Arc<NeutralSwatch>,
pub danger: ColorUnit,
pub success: ColorUnit,
pub warn: ColorUnit,
pub info: ColorUnit,
pub custom_colors: HashMap<String, ColorUnit>,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub shadow: Oklch,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub overlay: Oklch,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub outline: Oklch,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub outline_variant: Oklch,
#[serde(skip)]
pub neutral_lightness: f32,
#[serde(skip)]
pub scheme_settings: Arc<SchemeSetting>,
#[serde(skip)]
pub is_dark: bool,
}
impl Baseline {
pub fn new(
primary: &Oklch,
secondary: Option<&Oklch>,
tertiary: Option<&Oklch>,
accent: Option<&Oklch>,
danger: &Oklch,
success: &Oklch,
warn: &Oklch,
info: &Oklch,
neutral_lightest: &Oklch,
neutral_darkest: &Oklch,
settings: &Arc<SchemeSetting>,
is_dark: bool,
) -> Self {
let (final_secondary, final_tertiary, final_accent) = match settings.expand_method {
ColorExpand::Complementary => {
let sec_color = secondary.cloned().or(Some(primary.complementary()));
(sec_color, tertiary.cloned(), accent.cloned())
}
ColorExpand::Analogous => {
let analogous_color = primary.analogous();
(
secondary.cloned().or(Some(analogous_color.0)),
tertiary.cloned().or(Some(analogous_color.1)),
accent.cloned(),
)
}
ColorExpand::AnalogousAndComplementary => {
let analogous_color = primary.analogous();
let complementary_color = primary.complementary();
(
secondary.cloned().or(Some(analogous_color.0)),
tertiary.cloned().or(Some(analogous_color.1)),
accent.cloned().or(Some(complementary_color)),
)
}
ColorExpand::Triadic => {
let triadic_color = primary.triadic();
(
secondary.cloned().or(Some(triadic_color.0)),
tertiary.cloned().or(Some(triadic_color.1)),
accent.cloned(),
)
}
ColorExpand::SplitComplementary => {
let split_complementary_color = primary.split_complementary();
(
secondary.cloned().or(Some(split_complementary_color.0)),
tertiary.cloned(),
accent.cloned().or(Some(split_complementary_color.1)),
)
}
ColorExpand::Tetradic => {
let tetradic_color = primary.tetradic();
(
secondary.cloned().or(Some(tetradic_color.0)),
tertiary.cloned().or(Some(tetradic_color.2)),
accent.cloned().or(Some(tetradic_color.1)),
)
}
ColorExpand::Square => {
let c_90 = primary.shift_hue(90.0);
let complementary_color = primary.complementary();
let c_270 = primary.shift_hue(270.0);
(
secondary.cloned().or(Some(c_90)),
tertiary.cloned().or(Some(c_270)),
accent.cloned().or(Some(complementary_color)),
)
}
};
let reference_lightness = if is_dark {
neutral_darkest.l
} else {
neutral_lightest.l
};
let neutral_swatch = Arc::new(NeutralSwatch::new(*neutral_lightest, *neutral_darkest));
let outline_color =
neutral_swatch.get(neutral_lightest.l * if is_dark { 0.5 } else { 0.7 });
let outline_variant_color =
neutral_swatch.get(neutral_lightest.l * if is_dark { 0.3 } else { 0.8 });
let shadow_color = neutral_swatch.get(0.1);
let overlay_color =
neutral_swatch.get(neutral_lightest.l * if is_dark { 0.4 } else { 0.5 });
let neutral_color = neutral_swatch.get(if is_dark { 0.35 } else { 0.65 });
let neutral_variant_color = neutral_swatch.get(if is_dark { 0.45 } else { 0.55 });
let surface_color = neutral_swatch.get(if is_dark { 0.10 } else { 0.98 });
let surface_variant_color = neutral_swatch.get(if is_dark { 0.20 } else { 0.85 });
let neutral_set = ColorSet::new(
&neutral_color,
&neutral_swatch,
reference_lightness,
settings,
);
let neutral_variant_set = ColorSet::new(
&neutral_variant_color,
&neutral_swatch,
reference_lightness,
settings,
);
let surface_set = ColorSet::new(
&surface_color,
&neutral_swatch,
reference_lightness,
settings,
);
let surface_variant_set = ColorSet::new(
&surface_variant_color,
&neutral_swatch,
reference_lightness,
settings,
);
let primary_unit = ColorUnit::new(primary, &neutral_swatch, reference_lightness, settings);
let secondary_unit = final_secondary
.map(|color| ColorUnit::new(&color, &neutral_swatch, reference_lightness, settings));
let tertiary_unit = final_tertiary
.map(|color| ColorUnit::new(&color, &neutral_swatch, reference_lightness, settings));
let accent_unit = final_accent
.map(|color| ColorUnit::new(&color, &neutral_swatch, reference_lightness, settings));
let danger_unit = ColorUnit::new(danger, &neutral_swatch, reference_lightness, settings);
let success_unit = ColorUnit::new(success, &neutral_swatch, reference_lightness, settings);
let warn_unit = ColorUnit::new(warn, &neutral_swatch, reference_lightness, settings);
let info_unit = ColorUnit::new(info, &neutral_swatch, reference_lightness, settings);
Self {
primary: primary_unit,
secondary: secondary_unit,
tertiary: tertiary_unit,
accent: accent_unit,
neutral: neutral_set,
neutral_variant: neutral_variant_set,
surface: surface_set,
surface_variant: surface_variant_set,
neutral_swatch,
danger: danger_unit,
success: success_unit,
warn: warn_unit,
info: info_unit,
custom_colors: HashMap::new(),
shadow: shadow_color,
overlay: overlay_color,
outline: outline_color,
outline_variant: outline_variant_color,
neutral_lightness: reference_lightness,
scheme_settings: settings.clone(),
is_dark,
}
}
pub fn add_custom_color(
&mut self,
name: &str,
color: &Oklch,
) -> Result<(), errors::ColorError> {
let custom_color = ColorUnit::new(
color,
&self.neutral_swatch,
self.neutral_lightness,
&self.scheme_settings,
);
self.custom_colors.insert(name.to_string(), custom_color);
Ok(())
}
pub fn to_css_variables(&self) -> Vec<String> {
let mut css_variables = Vec::new();
let scheme_mode = if self.is_dark { "dark" } else { "light" };
css_variables.extend(self.primary.to_css_variables(scheme_mode, "primary"));
if let Some(secondary) = &self.secondary {
css_variables.extend(secondary.to_css_variables(scheme_mode, "secondary"));
}
if let Some(tertiary) = &self.tertiary {
css_variables.extend(tertiary.to_css_variables(scheme_mode, "tertiary"));
}
if let Some(accent) = &self.accent {
css_variables.extend(accent.to_css_variables(scheme_mode, "accent"));
}
css_variables.extend(self.neutral.to_css_variables(scheme_mode, "neutral"));
css_variables.extend(
self.neutral_variant
.to_css_variables(scheme_mode, "neutral-variant"),
);
css_variables.extend(self.surface.to_css_variables(scheme_mode, "surface"));
css_variables.extend(
self.surface_variant
.to_css_variables(scheme_mode, "surface-variant"),
);
css_variables.extend(self.danger.to_css_variables(scheme_mode, "danger"));
css_variables.extend(self.success.to_css_variables(scheme_mode, "success"));
css_variables.extend(self.warn.to_css_variables(scheme_mode, "warn"));
css_variables.extend(self.info.to_css_variables(scheme_mode, "info"));
css_variables.push(format!(
"--color-{scheme_mode}-shadow: #{};",
map_oklch_to_srgb_hex(&self.shadow)
));
css_variables.push(format!(
"--color-{scheme_mode}-overlay: #{};",
map_oklch_to_srgb_hex(&self.overlay)
));
css_variables.push(format!(
"--color-{scheme_mode}-outlint: #{};",
map_oklch_to_srgb_hex(&self.outline)
));
css_variables.push(format!(
"--color-{scheme_mode}-outline-variant: #{};",
map_oklch_to_srgb_hex(&self.outline_variant)
));
for (name, color_unit) in &self.custom_colors {
css_variables.extend(color_unit.to_css_variables(scheme_mode, name));
}
css_variables
}
pub fn to_css_auto_scheme_collection(&self) -> LinkedHashMap<String, String> {
let mut css_variables = LinkedHashMap::new();
css_variables.extend(self.primary.to_css_auto_scheme_collection("primary"));
if let Some(secondary) = &self.secondary {
css_variables.extend(secondary.to_css_auto_scheme_collection("secondary"));
}
if let Some(tertiary) = &self.tertiary {
css_variables.extend(tertiary.to_css_auto_scheme_collection("tertiary"));
}
if let Some(accent) = &self.accent {
css_variables.extend(accent.to_css_auto_scheme_collection("accent"));
}
css_variables.extend(self.neutral.to_css_auto_scheme_collection("neutral"));
css_variables.extend(
self.neutral_variant
.to_css_auto_scheme_collection("neutral-variant"),
);
css_variables.extend(self.surface.to_css_auto_scheme_collection("surface"));
css_variables.extend(
self.surface_variant
.to_css_auto_scheme_collection("surface-variant"),
);
css_variables.insert("shadow".to_string(), map_oklch_to_srgb_hex(&self.shadow));
css_variables.insert("overlay".to_string(), map_oklch_to_srgb_hex(&self.overlay));
css_variables.insert("outline".to_string(), map_oklch_to_srgb_hex(&self.outline));
css_variables.insert(
"outline-variant".to_string(),
map_oklch_to_srgb_hex(&self.outline_variant),
);
for (name, color) in &self.custom_colors {
css_variables.extend(color.to_css_auto_scheme_collection(name));
}
css_variables
}
pub fn to_scss_variables(&self) -> Vec<String> {
let mut scss_variables = Vec::new();
let scheme_mode = if self.is_dark { "dark" } else { "light" };
scss_variables.extend(self.primary.to_scss_variables(scheme_mode, "primary"));
if let Some(secondary) = &self.secondary {
scss_variables.extend(secondary.to_scss_variables(scheme_mode, "secondary"));
}
if let Some(tertiary) = &self.tertiary {
scss_variables.extend(tertiary.to_scss_variables(scheme_mode, "tertiary"));
}
if let Some(accent) = &self.accent {
scss_variables.extend(accent.to_scss_variables(scheme_mode, "accent"));
}
scss_variables.extend(self.neutral.to_scss_variables(scheme_mode, "neutral"));
scss_variables.extend(
self.neutral_variant
.to_scss_variables(scheme_mode, "neutral-variant"),
);
scss_variables.extend(self.surface.to_scss_variables(scheme_mode, "surface"));
scss_variables.extend(
self.surface_variant
.to_scss_variables(scheme_mode, "surface-variant"),
);
scss_variables.push(format!(
"--color-{scheme_mode}-shadow: #{};",
map_oklch_to_srgb_hex(&self.shadow)
));
scss_variables.push(format!(
"--color-{scheme_mode}-overlay: #{};",
map_oklch_to_srgb_hex(&self.overlay)
));
scss_variables.push(format!(
"--color-{scheme_mode}-outlint: #{};",
map_oklch_to_srgb_hex(&self.outline)
));
scss_variables.push(format!(
"--color-{scheme_mode}-outline-variant: #{};",
map_oklch_to_srgb_hex(&self.outline_variant)
));
scss_variables
}
pub fn to_javascript_fields(&self) -> Vec<String> {
let mut javascript_fields = Vec::new();
let scheme_mode = if self.is_dark { "dark" } else { "light" };
javascript_fields.push(format!("{scheme_mode}: {{"));
let indent = " ".repeat(4);
for line in self.primary.to_javascript_fields("primary").iter() {
javascript_fields.push(format!("{indent}{line:4}"));
}
for line in self
.secondary
.as_ref()
.map(|s| s.to_javascript_fields("secondary"))
.unwrap_or(Vec::new())
.iter()
{
javascript_fields.push(format!("{indent}{line:4}"));
}
for line in self
.tertiary
.as_ref()
.map(|s| s.to_javascript_fields("tertiary"))
.unwrap_or(Vec::new())
.iter()
{
javascript_fields.push(format!("{indent}{line:4}"));
}
for line in self
.accent
.as_ref()
.map(|s| s.to_javascript_fields("accent"))
.unwrap_or(Vec::new())
.iter()
{
javascript_fields.push(format!("{indent}{line:4}"));
}
for line in self.neutral.to_javascript_fields("neutral").iter() {
javascript_fields.push(format!("{indent}{line:4}"));
}
for line in self
.neutral_variant
.to_javascript_fields("neutral_variant")
.iter()
{
javascript_fields.push(format!("{indent}{line:4}"));
}
for line in self.surface.to_javascript_fields("surface").iter() {
javascript_fields.push(format!("{indent}{line:4}"));
}
for line in self
.surface_variant
.to_javascript_fields("surface_variant")
.iter()
{
javascript_fields.push(format!("{indent}{line:4}"));
}
javascript_fields.push(format!(
"{indent}shadow: '#{}',",
map_oklch_to_srgb_hex(&self.shadow)
));
javascript_fields.push(format!(
"{indent}overlay: '#{}',",
map_oklch_to_srgb_hex(&self.overlay)
));
javascript_fields.push(format!(
"{indent}outline: '#{}',",
map_oklch_to_srgb_hex(&self.outline)
));
javascript_fields.push(format!(
"{indent}outlineVariant: '#{}',",
map_oklch_to_srgb_hex(&self.outline_variant)
));
for (name, color) in &self.custom_colors {
let color_lines = color.to_javascript_fields(name);
javascript_fields.extend(color_lines.iter().map(|s| format!("{indent}{s}")));
}
javascript_fields.push("}".to_string());
javascript_fields
}
}

View File

@ -1,282 +0,0 @@
use core::f32;
use std::sync::Arc;
use linked_hash_map::LinkedHashMap;
use palette::{Oklch, color_difference::Wcag21RelativeContrast, luma::Luma};
use serde::Serialize;
use crate::{
convert::{map_oklch_to_luma, map_oklch_to_srgb_hex},
schemes::{
q_style::{NeutralSwatch, SchemeSetting, WACGSetting},
q_style_2::swatch::Swatch,
},
};
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ColorSet {
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub root: Oklch,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub active: Oklch,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub focus: Oklch,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub hover: Oklch,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub disabled: Oklch,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub on_root: Oklch,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub on_disabled: Oklch,
#[serde(skip)]
pub neutral_swatch: Arc<NeutralSwatch>,
#[serde(skip)]
pub neutral_lightness: f32,
#[serde(skip)]
pub scheme_settings: Arc<SchemeSetting>,
}
#[inline]
fn match_wacg(original: &Oklch<f32>, reference: &Luma) -> f32 {
let luma_original = map_oklch_to_luma(original);
luma_original.relative_contrast(*reference)
}
fn search_for_common_wacg_color(
reference_colors: &[&Oklch],
neutral_swatch: &NeutralSwatch,
minium_ratio: f32,
) -> Oklch {
// store in: (lightness, avg_wacg_abs, sum_wacg_abs)
let mut minium_match: (f32, f32, f32) = (0.0, f32::INFINITY, 0.0);
let mut closest_match: (f32, f32, f32) = (f32::INFINITY, f32::INFINITY, 0.0);
for scan_lightness in (0..=100).map(|x| x as f32 / 100.0) {
let new_target = neutral_swatch.get(scan_lightness);
let new_target_luma = map_oklch_to_luma(&new_target);
let reference_wacgs_sum: f32 = reference_colors
.iter()
.map(|ref_color| match_wacg(&ref_color, &new_target_luma) - minium_ratio)
.sum();
let reference_wacgs = reference_wacgs_sum / reference_colors.len() as f32;
if reference_wacgs.abs() < closest_match.1.abs() && reference_wacgs_sum > closest_match.2 {
closest_match = (scan_lightness, reference_wacgs, reference_wacgs_sum);
}
if reference_wacgs >= 0.0
&& reference_wacgs.abs() < minium_match.1.abs()
&& reference_wacgs_sum > minium_match.2
{
minium_match = (scan_lightness, reference_wacgs, reference_wacgs_sum);
}
}
if minium_match.1 != f32::INFINITY {
neutral_swatch.get(minium_match.0)
} else {
neutral_swatch.get(closest_match.0)
}
}
impl ColorSet {
pub fn new(
color: &Oklch,
neutral_swatch: &Arc<NeutralSwatch>,
neutral_lightness: f32,
settings: &Arc<SchemeSetting>,
) -> Self {
let neutral_swatch = Arc::clone(neutral_swatch);
let settings = Arc::clone(settings);
let root = color.clone();
let hover = color * settings.hover;
let active = color * settings.active;
let focus = color * settings.focus;
let disabled = color * settings.disabled;
let color_list = &[&root, &hover, &active, &focus];
let (on_root, on_disabled) = match settings.wacg_follows {
WACGSetting::Fixed => (
neutral_swatch.get(neutral_lightness),
neutral_swatch.get(neutral_lightness),
),
WACGSetting::AutomaticAA => (
search_for_common_wacg_color(color_list, &neutral_swatch, 4.5),
search_for_common_wacg_color(&[&disabled], &neutral_swatch, 4.5),
),
WACGSetting::AutomaticAAA => (
search_for_common_wacg_color(color_list, &neutral_swatch, 7.0),
search_for_common_wacg_color(&[&disabled], &neutral_swatch, 7.0),
),
WACGSetting::HighContrast => (
search_for_common_wacg_color(color_list, &neutral_swatch, 21.0),
search_for_common_wacg_color(&[&disabled], &neutral_swatch, 21.0),
),
};
Self {
root,
active,
focus,
hover,
disabled,
on_root,
on_disabled,
neutral_swatch,
neutral_lightness,
scheme_settings: settings,
}
}
pub fn generate_surface_set(&self) -> Self {
let root_swatch = Swatch::new(&self.root);
let root_lightness = self.root.l;
let surface_lightness = if root_lightness + 40.0 > 90.0 {
root_lightness - 40.0
} else {
root_lightness + 40.0
};
let surface_color = root_swatch.get(surface_lightness);
Self::new(
&surface_color,
&self.neutral_swatch,
self.neutral_lightness,
&self.scheme_settings,
)
}
pub fn generate_swatch(&self) -> Swatch {
Swatch::new(&self.root)
}
fn root_hex(&self) -> String {
map_oklch_to_srgb_hex(&self.root)
}
fn hover_hex(&self) -> String {
map_oklch_to_srgb_hex(&self.hover)
}
fn active_hex(&self) -> String {
map_oklch_to_srgb_hex(&self.active)
}
fn focus_hex(&self) -> String {
map_oklch_to_srgb_hex(&self.focus)
}
fn disabled_hex(&self) -> String {
map_oklch_to_srgb_hex(&self.disabled)
}
fn on_root_hex(&self) -> String {
map_oklch_to_srgb_hex(&self.on_root)
}
fn on_disabled_hex(&self) -> String {
map_oklch_to_srgb_hex(&self.on_disabled)
}
pub fn to_css_variables(&self, prefix: &str, name: &str) -> Vec<String> {
let mut variables = Vec::new();
variables.push(format!("--color-{prefix}-{name}: #{};", self.root_hex()));
variables.push(format!(
"--color-{prefix}-{name}-hover: #{};",
self.hover_hex()
));
variables.push(format!(
"--color-{prefix}-{name}-active: #{};",
self.active_hex()
));
variables.push(format!(
"--color-{prefix}-{name}-focus: #{};",
self.focus_hex()
));
variables.push(format!(
"--color-{prefix}-{name}-disabled: ${};",
self.disabled_hex()
));
variables.push(format!(
"--color-{prefix}-on-{name}: #{};",
self.on_root_hex()
));
variables.push(format!(
"--color-{prefix}-on-{name}-disabled: #{};",
self.on_disabled_hex()
));
variables
}
pub fn to_css_auto_scheme_collection(&self, name: &str) -> LinkedHashMap<String, String> {
let mut collection = LinkedHashMap::new();
collection.insert(format!("{name}"), self.root_hex());
collection.insert(format!("{name}-hover"), self.hover_hex());
collection.insert(format!("{name}-active"), self.active_hex());
collection.insert(format!("{name}-focus"), self.focus_hex());
collection.insert(format!("{name}-disabled"), self.disabled_hex());
collection.insert(format!("on-{name}"), self.on_root_hex());
collection.insert(format!("on-{name}-disabled"), self.on_disabled_hex());
collection
}
pub fn to_scss_variables(&self, prefix: &str, name: &str) -> Vec<String> {
let mut variables = Vec::new();
variables.push(format!("&color-{prefix}-{name}: #{};", self.root_hex()));
variables.push(format!(
"$color-{prefix}-{name}-hover: #{};",
self.hover_hex()
));
variables.push(format!(
"$color-{prefix}-{name}-active: #{};",
self.active_hex()
));
variables.push(format!(
"$color-{prefix}-{name}-focus: ${};",
self.focus_hex()
));
variables.push(format!(
"$color-{prefix}-{name}-disabled: #{};",
self.disabled_hex()
));
variables.push(format!(
"$color-{prefix}-on-{name}: #{};",
self.on_root_hex()
));
variables.push(format!(
"$color-{prefix}-on-{name}-disabled: #{};",
self.on_disabled_hex()
));
variables
}
pub fn to_javascript_fields(&self, name: &str) -> Vec<String> {
let mut variables = Vec::new();
let capitalized_name = name
.chars()
.next()
.unwrap()
.to_ascii_uppercase()
.to_string()
+ &name[1..];
variables.push(format!("{name}: '#{}',", self.root_hex()));
variables.push(format!("{name}Hover: '#{}',", self.hover_hex()));
variables.push(format!("{name}Active: '#{}',", self.active_hex()));
variables.push(format!("{name}Focus: '#{}',", self.focus_hex()));
variables.push(format!("{name}Disabled: '#{}',", self.disabled_hex()));
variables.push(format!("on{capitalized_name}: '#{}',", self.on_root_hex()));
variables.push(format!(
"on{capitalized_name}Disabled: '#{}',",
self.on_disabled_hex()
));
variables
}
}

View File

@ -1,157 +0,0 @@
use std::{str::FromStr, sync::Arc};
use linked_hash_set::LinkedHashSet;
use palette::FromColor;
use serde::Serialize;
use crate::{
errors, parse_option_to_oklch, parse_to_oklch,
schemes::{SchemeExport, q_style::SchemeSetting, q_style_2::baseline::Baseline},
};
mod baseline;
mod color_set;
mod swatch;
#[derive(Debug, Clone, Serialize)]
pub struct QScheme2 {
pub light: Baseline,
pub dark: Baseline,
#[serde(skip)]
_settings: Arc<SchemeSetting>,
}
impl QScheme2 {
pub fn new(
primary: &str,
secondary: Option<&str>,
tertiary: Option<&str>,
accent: Option<&str>,
danger: &str,
success: &str,
warn: &str,
info: &str,
foreground: &str,
background: &str,
setting: SchemeSetting,
) -> Result<Self, errors::ColorError> {
let primary = parse_to_oklch!(primary);
let secondary = parse_option_to_oklch!(secondary);
let tertiary = parse_option_to_oklch!(tertiary);
let accent = parse_option_to_oklch!(accent);
let danger = parse_to_oklch!(danger);
let success = parse_to_oklch!(success);
let warn = parse_to_oklch!(warn);
let info = parse_to_oklch!(info);
let foreground = parse_to_oklch!(foreground);
let background = parse_to_oklch!(background);
let settings = Arc::new(setting);
let light_scheme = Baseline::new(
&primary,
secondary.as_ref(),
tertiary.as_ref(),
accent.as_ref(),
&danger,
&success,
&warn,
&info,
&foreground,
&background,
&settings,
false,
);
let dark_scheme = Baseline::new(
&(&primary * settings.dark_convert),
secondary.map(|c| c * settings.dark_convert).as_ref(),
tertiary.map(|c| c * settings.dark_convert).as_ref(),
accent.map(|c| c * settings.dark_convert).as_ref(),
&(danger * settings.dark_convert),
&(success * settings.dark_convert),
&(warn * settings.dark_convert),
&(info * settings.dark_convert),
&foreground,
&background,
&settings,
true,
);
Ok(Self {
light: light_scheme,
dark: dark_scheme,
_settings: settings,
})
}
pub fn add_custom_color(&mut self, name: &str, color: &str) -> Result<(), errors::ColorError> {
let custom_color = parse_to_oklch!(color);
self.light.add_custom_color(name, &custom_color)?;
self.dark
.add_custom_color(name, &(custom_color * self._settings.dark_convert))?;
Ok(())
}
}
impl SchemeExport for QScheme2 {
fn output_css_variables(&self) -> String {
let mut variables = Vec::new();
variables.extend(self.light.to_css_variables());
variables.extend(self.dark.to_css_variables());
variables.join("\n")
}
fn output_css_auto_scheme_variables(&self) -> String {
let mut collection = Vec::new();
let mut keys = LinkedHashSet::new();
let light_collection = self.light.to_css_auto_scheme_collection();
let dark_collection = self.dark.to_css_auto_scheme_collection();
keys.extend(light_collection.keys().cloned());
keys.extend(dark_collection.keys().cloned());
for key in keys {
match (light_collection.get(&key), dark_collection.get(&key)) {
(Some(light), Some(dark)) => {
collection.push(format!("--color-{key}: light-dark(#{light}, #{dark});"));
}
(Some(color), None) | (None, Some(color)) => {
collection.push(format!("--color-{key}: #{color}"));
}
(None, None) => {}
}
}
collection.join("\n")
}
fn output_scss_variables(&self) -> String {
let mut variables = Vec::new();
variables.extend(self.light.to_scss_variables());
variables.extend(self.dark.to_scss_variables());
variables.join("\n")
}
fn output_javascript_object(&self) -> String {
let mut javascript_object = Vec::new();
let indent = " ".repeat(4);
javascript_object.push("{".to_string());
for line in self.light.to_javascript_fields() {
javascript_object.push(format!("{indent}{line}"));
}
for line in self.dark.to_javascript_fields() {
javascript_object.push(format!("{indent}{line}"));
}
javascript_object.push("}".to_string());
javascript_object.join("\n")
}
}

View File

@ -1,119 +0,0 @@
use std::sync::Arc;
use linked_hash_map::LinkedHashMap;
use palette::Oklch;
use serde::{Serialize, Serializer, ser::SerializeStruct};
use crate::{
convert::map_oklch_to_srgb_hex,
schemes::{get_static_str, q_style::NeutralSwatch},
};
static SWATCH_LIGHTINGS: [u8; 16] = [
10, 15, 20, 25, 30, 35, 40, 50, 60, 70, 75, 80, 85, 90, 95, 98,
];
#[derive(Debug, Clone)]
pub struct Swatch(Oklch);
impl Swatch {
pub fn new(color: &Oklch) -> Self {
Self(color.clone())
}
pub fn get<L: Into<f32>>(&self, lightness: L) -> Oklch {
let request_lightness: f32 = lightness.into() / 100.0;
Oklch {
l: request_lightness.clamp(0.1, 0.98),
..self.0.clone()
}
}
pub fn get_hex<L: Into<f32>>(&self, lightness: L) -> String {
let c = self.get(lightness.into());
map_oklch_to_srgb_hex(&c)
}
pub fn to_css_variables(&self, prefix: &str, name: &str) -> Vec<String> {
let mut variables = Vec::new();
for l in SWATCH_LIGHTINGS {
variables.push(format!(
"--color-{prefix}-swatch-{name}-{l:02}: #{};",
self.get_hex(l)
));
}
variables
}
pub fn to_css_auto_scheme_collection(&self, name: &str) -> LinkedHashMap<String, String> {
let mut collection = LinkedHashMap::new();
for l in SWATCH_LIGHTINGS {
collection.insert(format!("{name}-{l:02}"), self.get_hex(l));
}
collection
}
pub fn to_scss_variables(&self, prefix: &str, name: &str) -> Vec<String> {
let mut variables = Vec::new();
for l in SWATCH_LIGHTINGS {
variables.push(format!(
"$color-{prefix}-swatch-{name}-{l:02}: #{};",
self.get_hex(l)
));
}
variables
}
pub fn to_javascript_fields(&self, name: &str) -> Vec<String> {
let mut variables = Vec::new();
for l in SWATCH_LIGHTINGS {
variables.push(format!("{name}{l:02}: '#{}',", self.get_hex(l)));
}
variables
}
}
impl Serialize for Swatch {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut state = serializer.serialize_struct("Swatch", SWATCH_LIGHTINGS.len())?;
for l in SWATCH_LIGHTINGS {
let color = self.get_hex(l);
let key: &'static str = get_static_str(&format!("{l:02}"));
state.serialize_field(key, &color)?;
}
state.end()
}
}
pub fn serialize_neutral_swatch<S>(
swatch: &Arc<NeutralSwatch>,
serailizer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let swatch = swatch.clone();
let mut swatch_struct = serailizer.serialize_struct("NeutralSwatch", SWATCH_LIGHTINGS.len())?;
for l in SWATCH_LIGHTINGS {
let color = swatch.get((l as f32) / 100.0);
let color = map_oklch_to_srgb_hex(&color);
let key: &'static str = get_static_str(&format!("{l:02}"));
swatch_struct.serialize_field(key, &color)?;
}
swatch_struct.end()
}

View File

@ -10,7 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"@iconify/react": "^6.0.0",
"@iconify/react": "^5.1.0",
"clsx": "^2.1.1",
"color-module": "./color_functions",
"dayjs": "^1.11.13",
@ -18,7 +18,7 @@
"lodash-es": "^4.17.21",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-error-boundary": "^6.0.0",
"react-error-boundary": "^5.0.0",
"react-router-dom": "^7.1.1",
"react-transition-group": "^4.4.5",
"react-use": "^17.6.0",
@ -36,10 +36,10 @@
"eslint": "^9.17.0",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.16",
"globals": "^16.3.0",
"globals": "^15.14.0",
"lightningcss": "^1.28.2",
"typescript": "~5.8.3",
"typescript": "~5.6.2",
"typescript-eslint": "^8.18.2",
"vite": "^7.0.4"
"vite": "^6.0.5"
}
}

View File

@ -16,7 +16,6 @@ import {
MaterialDesign3DynamicSchemeSource,
MaterialDesign3SchemeSource,
} from '../material-3-scheme';
import { Q2SchemeSource } from '../q-2-scheme';
import { QSchemeSource } from '../q-scheme';
import { currentPickedColor } from '../stores/colors';
import { activeSchemeAtom, useActiveScheme, useUpdateScheme } from '../stores/schemes';
@ -102,79 +101,6 @@ const QSchemeMenu: FC<ContextMenuItemProps> = ({ color, afterClick }) => {
);
};
const Q2SchemeMenu: FC<ContextMenuBodyProps> = ({ color, afterClick }) => {
const { showToast } = useNotification();
const activeSchemeId = useAtomValue(activeSchemeAtom);
const updateScheme = useUpdateScheme(activeSchemeId);
const updateSchemeContent = useCallback(
(content: keyof QSchemeSource) => {
updateScheme((prev) => {
prev.schemeStorage.source[content] = color;
return prev;
});
showToast(
NotificationType.SUCCESS,
`${capitalize(content)} color in active scheme updated.`,
'tabler:settings-up',
3000,
);
afterClick?.();
},
[color, activeSchemeId, updateScheme],
);
const addCustomColor = useCallback(() => {
updateScheme((prev) => {
const source = prev.schemeStorage.source as Q2SchemeSource;
const colorAmount = size(source.custom_colors);
source.custom_colors[`Custom Color ${colorAmount + 1}`] = color;
return prev;
});
showToast(NotificationType.SUCCESS, `New color entry added.`, 'tabler:settings-up', 3000);
afterClick?.();
}, [color, activeSchemeId, updateScheme]);
return (
<>
<hr />
<div className={styles.menu_item} onClick={() => updateSchemeContent('primary')}>
Set as Primary color
</div>
<div className={styles.menu_item} onClick={() => updateSchemeContent('secondary')}>
Set as Secondary color
</div>
<div className={styles.menu_item} onClick={() => updateSchemeContent('tertiary')}>
Set as Tertiary color
</div>
<div className={styles.menu_item} onClick={() => updateSchemeContent('accent')}>
Set as Accent color
</div>
<div className={styles.menu_item} onClick={() => updateSchemeContent('danger')}>
Set as Danger color
</div>
<div className={styles.menu_item} onClick={() => updateSchemeContent('success')}>
Set as Success color
</div>
<div className={styles.menu_item} onClick={() => updateSchemeContent('warning')}>
Set as Warn color
</div>
<div className={styles.menu_item} onClick={() => updateSchemeContent('info')}>
Set as Info color
</div>
<div className={styles.menu_item} onClick={() => updateSchemeContent('foreground')}>
Set as Foreground color
</div>
<div className={styles.menu_item} onClick={() => updateSchemeContent('background')}>
Set as Background color
</div>
<div className={styles.menu_item} onClick={addCustomColor}>
Add to Custom colors
</div>
</>
);
};
const SwatchSchemeMenu: FC<ContextMenuItemProps> = ({ color, afterClick }) => {
const { showToast } = useNotification();
const activeSchemeId = useAtomValue(activeSchemeAtom);
@ -327,8 +253,6 @@ export const ContextMenuBody: FC<ContextMenuBodyProps> = ({ color, afterClick, x
switch (activeScheme?.type) {
case 'q_scheme':
return <QSchemeMenu {...sharedProps} />;
case 'q_2_scheme':
return <Q2SchemeMenu {...sharedProps} />;
case 'swatch_scheme':
return <SwatchSchemeMenu {...sharedProps} />;
case 'material_2':

View File

@ -9,9 +9,6 @@
&.swatch {
background-color: var(--color-pinlan);
}
&.q2 {
background-color: var(--color-jugengzi);
}
&.m2 {
background-color: #03dac6;
color: var(--color-qihei);

View File

@ -16,8 +16,6 @@ export function SchemeSign({ scheme, short = false }: SchemeSignProps) {
switch (scheme) {
case 'q_scheme':
return styles.q;
case 'q_2_scheme':
return styles.q2;
case 'swatch_scheme':
return styles.swatch;
case 'material_2':

View File

@ -4,7 +4,6 @@ import {
MaterialDesign3DynamicSchemeStorage,
MaterialDesign3SchemeStorage,
} from './material-3-scheme';
import { Q2SchemeStorage } from './q-2-scheme';
import { QSchemeStorage } from './q-scheme';
import { SwatchSchemeStorage } from './swatch_scheme';
@ -35,7 +34,6 @@ export type ColorDescription = {
export type SchemeType =
| 'q_scheme'
| 'q_2_scheme'
| 'swatch_scheme'
| 'material_2'
| 'material_3'
@ -47,7 +45,6 @@ export type SchemeTypeOption = {
};
export const SchemeTypeOptions: SchemeTypeOption[] = [
{ label: 'Q Scheme', short: 'Q', value: 'q_scheme' },
{ label: 'Q2 Scheme', short: 'Q2', value: 'q_2_scheme' },
{ label: 'Swatch Scheme', short: 'Swatch', value: 'swatch_scheme' },
{ label: 'Material Design 2 Scheme', short: 'M2', value: 'material_2' },
{ label: 'Material Design 3 Scheme', short: 'M3', value: 'material_3' },
@ -82,7 +79,6 @@ export type ColorShifting = {
export type SchemeStorage =
| QSchemeStorage
| Q2SchemeStorage
| SwatchSchemeStorage
| MaterialDesign2SchemeStorage
| MaterialDesign3SchemeStorage

View File

@ -1,44 +0,0 @@
import { isEqual, isNil } from 'lodash-es';
import { useState } from 'react';
import { Tab } from '../../components/Tab';
import { SchemeContent } from '../../models';
import { Q2SchemeStorage } from '../../q-2-scheme';
import { isNilOrEmpty } from '../../utls';
import { SchemeExport } from './Export';
import { Q2SchemeBuilder } from './q-2-scheme/Builder';
import Q2SchemePreview from './q-2-scheme/Preview';
const tabOptions = [
{ title: 'Overview', id: 'overview' },
{ title: 'Builder', id: 'builder' },
{ title: 'Exports', id: 'export' },
];
type Q2SchemeProps = {
scheme: SchemeContent<Q2SchemeStorage>;
};
export function Q2Scheme({ scheme }: Q2SchemeProps) {
const [activeTab, setActiveTab] = useState<(typeof tabOptions)[number]['id']>(() =>
isNil(scheme.schemeStorage.scheme) ? 'builder' : 'overview',
);
return (
<>
<Tab
tabs={tabOptions}
activeTab={activeTab}
onActive={(v) => setActiveTab(v as string)}
disabled={{
overview: isNilOrEmpty(scheme.schemeStorage?.scheme),
export: isNilOrEmpty(scheme.schemeStorage?.cssVariables),
}}
/>
{isEqual(activeTab, 'overview') && <Q2SchemePreview scheme={scheme} />}
{isEqual(activeTab, 'builder') && (
<Q2SchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />
)}
{isEqual(activeTab, 'export') && <SchemeExport scheme={scheme} />}
</>
);
}

View File

@ -1,44 +0,0 @@
@layer pages {
.builder_layout {
padding: var(--spacing-s) var(--spacing-m);
font-size: var(--font-size-s);
line-height: 1.3em;
display: grid;
grid-template-columns: repeat(3, 200px);
align-items: center;
gap: var(--spacing-xs);
.label {
max-width: 200px;
grid-column: 1;
padding-inline-end: var(--spacing-m);
text-align: right;
}
.color_picker_row {
grid-column: 2 / span 2;
display: flex;
align-items: center;
gap: var(--spacing-s);
.error_msg {
color: var(--color-danger);
font-size: var(--font-size-xs);
}
}
.segment_title {
grid-column: 1 / span 2;
text-align: center;
}
.parameter_input {
max-width: 8em;
}
.button_row {
display: flex;
flex-direction: row;
align-items: center;
gap: var(--spacing-s);
}
h5 {
font-size: var(--font-size-m);
line-height: 1.7em;
}
}
}

View File

@ -1,532 +0,0 @@
import { ColorExpand, ColorShifting, SchemeSetting, WACGSetting } from 'color-module';
import { includes, isEmpty, isNil } from 'lodash-es';
import { useActionState, useCallback, useMemo, useState } from 'react';
import { useColorFunction } from '../../../ColorFunctionContext';
import { FloatColorPicker } from '../../../components/FloatColorPicker';
import { NotificationType, useNotification } from '../../../components/Notifications';
import { ScrollArea } from '../../../components/ScrollArea';
import { VSegmentedControl } from '../../../components/VSegmentedControl';
import { SchemeContent } from '../../../models';
import { Q2SchemeSource, Q2SchemeStorage } from '../../../q-2-scheme';
import { useUpdateScheme } from '../../../stores/schemes';
import { isNilOrEmpty, mapToObject } from '../../../utls';
import { ColorEntry, IdenticalColorEntry } from '../ColorEntry';
import styles from './Builder.module.css';
type Q2SchemeBuilderProps = {
scheme: SchemeContent<Q2SchemeStorage>;
onBuildCompleted?: () => void;
};
export function Q2SchemeBuilder({ scheme, onBuildCompleted }: Q2SchemeBuilderProps) {
const { showToast } = useNotification();
const { colorFn } = useColorFunction();
const updateScheme = useUpdateScheme(scheme.id);
// Load scheme setting and scheme default setting.
const defaultSetting = useMemo(() => {
try {
if (!colorFn) throw 'Web Assembly functions is not available';
const defaultValues = colorFn.q_scheme_default_settings();
if (scheme.schemeStorage.source?.setting) {
return new SchemeSetting(
new ColorShifting(
scheme.schemeStorage.source?.setting.hover.chroma ?? defaultValues.hover.chroma,
scheme.schemeStorage.source?.setting.hover.lightness ?? defaultValues.hover.lightness,
),
new ColorShifting(
scheme.schemeStorage.source?.setting?.active.chroma ?? defaultValues.active.chroma,
scheme.schemeStorage.source?.setting?.active.lightness ??
defaultValues.active.lightness,
),
new ColorShifting(
scheme.schemeStorage.source?.setting?.focus.chroma ?? defaultValues.focus.chroma,
scheme.schemeStorage.source?.setting?.focus.lightness ?? defaultValues.focus.lightness,
),
new ColorShifting(
scheme.schemeStorage.source?.setting?.disabled.chroma ?? defaultValues.disabled.chroma,
scheme.schemeStorage.source?.setting?.disabled.lightness ??
defaultValues.disabled.lightness,
),
new ColorShifting(
scheme.schemeStorage.source?.setting?.dark_convert.chroma ??
defaultValues.dark_convert.chroma,
scheme.schemeStorage.source?.setting?.dark_convert.lightness ??
defaultValues.dark_convert.lightness,
),
scheme.schemeStorage.source?.setting?.expand_method ?? defaultValues.expand_method,
scheme.schemeStorage.source?.setting?.wacg_follows ?? defaultValues.wacg_follows,
);
}
return defaultValues;
} catch (e) {
console.error('[Q2 Scheme builder]', e);
}
}, [scheme]);
// Collect choices in color scheme settings
const expandingMethods = useMemo(() => {
try {
if (!colorFn) throw 'Web Assembly functions is not available';
return colorFn.q_scheme_color_expanding_methods();
} catch (e) {
console.error('[Q scheme builder]', e);
}
return [];
}, []);
const wacgFollowStrategies = useMemo(() => {
try {
if (!colorFn) throw 'Web Assembly functions is not available';
return colorFn.q_scheme_wacg_settings();
} catch (e) {
console.error('[Q scheme builder]', e);
}
return [];
}, []);
// Custom Colors processing
const originalColors = useMemo(() => {
return Object.entries(scheme.schemeStorage.source?.custom_colors ?? {}).map(
([name, color], index) => ({ id: `oc_${index}`, name, color } as IdenticalColorEntry),
);
}, [scheme.schemeStorage.source]);
const [newColors, setNewColors] = useState<IdenticalColorEntry[]>([]);
const [deleted, setDeleted] = useState<string[]>([]);
const addEntryAction = useCallback(() => {
setNewColors((prev) => [...prev, { id: `nc_${prev.length}`, name: '', color: '' }]);
}, []);
const colorKeys = useMemo(
() =>
[...originalColors, ...newColors]
.map((color) => color.id)
.filter((c) => !includes(deleted, c)),
[originalColors, newColors, deleted],
);
// Collect scheme source
const collectSchemeSource = (formData: FormData): [Q2SchemeSource, QSchemeSetting] => {
const primaryColor = formData.get('primary')?.toString();
const secondaryColor = formData.get('secondary')?.toString();
const tertiaryColor = formData.get('tertiary')?.toString();
const accentColor = formData.get('accent')?.toString();
const dangerColor = formData.get('danger')?.toString();
const successColor = formData.get('success')?.toString();
const warnColor = formData.get('warn')?.toString();
const infoColor = formData.get('info')?.toString();
const foregroundColor = formData.get('foreground')?.toString();
const backgroundColor = formData.get('background')?.toString();
const customColors: Record<string, string> = {};
for (const key of colorKeys) {
const name = formData.get(`name_${key}`)?.toString();
const color = formData.get(`color_${key}`)?.toString();
if (isNil(name) || isEmpty(name) || isNil(color) || isEmpty(color)) continue;
customColors[name] = color;
}
// collect scheme settings
const schemeSetting = new SchemeSetting(
new ColorShifting(
Number(formData.get('hover_chroma')) / 100,
Number(formData.get('hover_lightness')) / 100,
),
new ColorShifting(
Number(formData.get('active_chroma')) / 100,
Number(formData.get('active_lightness')) / 100,
),
new ColorShifting(
Number(formData.get('focus_chroma')) / 100,
Number(formData.get('focus_lightness')) / 100,
),
new ColorShifting(
Number(formData.get('disabled_chroma')) / 100,
Number(formData.get('disabled_lightness')) / 100,
),
new ColorShifting(
Number(formData.get('dark_chroma')) / 100,
Number(formData.get('dark_lightness')) / 100,
),
Number(formData.get('expanding')) as ColorExpand,
Number(formData.get('wacg')) as WACGSetting,
);
const dumpedSetting = schemeSetting.toJsValue() as QSchemeSetting;
return [
{
primary: primaryColor,
secondary: secondaryColor,
tertiary: tertiaryColor,
accent: accentColor,
danger: dangerColor,
success: successColor,
warn: warnColor,
info: infoColor,
foreground: foregroundColor,
background: backgroundColor,
custom_colors: customColors,
setting: dumpedSetting,
} as Q2SchemeSource,
schemeSetting,
];
};
// Scheme save actions
const handleDraftAction = (formData: FormData) => {
const [source] = collectSchemeSource(formData);
updateScheme((prev) => {
prev.schemeStorage.source = source;
return prev;
});
showToast(NotificationType.SUCCESS, 'Scheme draft saved!', 'tabler:device-floppy', 3000);
};
const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
(_state, formData) => {
const errMsg = new Map<string, string>();
// Check required color
const requiredFields = [
'primary',
'danger',
'success',
'warn',
'info',
'foreground',
'background',
];
for (const field of requiredFields) {
if (!formData.get(field)) {
errMsg.set(field, 'This color is required for scheme generating.');
}
}
if (!isEmpty(errMsg)) return errMsg;
try {
const [source, settings] = collectSchemeSource(formData);
console.log('[Collected form data]', source, settings);
const generatedScheme = colorFn?.generate_q_scheme_2_manually(
source.primary ?? '',
isEmpty(source.secondary) ? undefined : source.secondary,
isEmpty(source.tertiary) ? undefined : source.tertiary,
isEmpty(source.accent) ? undefined : source.accent,
source.danger ?? '',
source.success ?? '',
source.warn ?? '',
source.info ?? '',
source.foreground ?? '',
source.background ?? '',
source.custom_colors,
settings,
);
console.log('[Generated scheme]', generatedScheme);
updateScheme((prev) => {
prev.schemeStorage.source = source;
prev.schemeStorage.scheme = {
light: {
...generatedScheme[0].light,
customColors: mapToObject(generatedScheme[0].light.customColors),
},
dark: {
...generatedScheme[0].dark,
customColors: mapToObject(generatedScheme[0].dark.customColors),
},
};
prev.schemeStorage.cssVariables = generatedScheme[1];
prev.schemeStorage.cssAutoSchemeVariables = generatedScheme[2];
prev.schemeStorage.scssVariables = generatedScheme[3];
prev.schemeStorage.jsVariables = generatedScheme[4];
return prev;
});
onBuildCompleted?.();
} catch (e) {
console.error('[build q2 scheme]', e);
}
return errMsg;
},
new Map<string, string>(),
);
return (
<ScrollArea enableY>
<form action={handleSubmitAction} className={styles.builder_layout}>
<h5 className={styles.segment_title}>Original Colors</h5>
<label className={styles.label}>Primary Color*</label>
<div className={styles.color_picker_row}>
<FloatColorPicker
name="primary"
color={
isNilOrEmpty(scheme.schemeStorage.source?.primary)
? undefined
: scheme.schemeStorage.source?.primary
}
/>
{errMsg.has('primary') && (
<span className={styles.error_msg}>{errMsg.get('primary')}</span>
)}
</div>
<label className={styles.label}>Secondary Color</label>
<div className={styles.color_picker_row}>
<FloatColorPicker
name="secondary"
color={
isNilOrEmpty(scheme.schemeStorage.source?.secondary)
? undefined
: scheme.schemeStorage.source?.secondary
}
/>
</div>
<label className={styles.label}>Tertiary Color</label>
<div className={styles.color_picker_row}>
<FloatColorPicker
name="tertiary"
color={
isNilOrEmpty(scheme.schemeStorage.source?.tertiary)
? undefined
: scheme.schemeStorage.source?.tertiary
}
/>
</div>
<label className={styles.label}>Accent Color</label>
<div className={styles.color_picker_row}>
<FloatColorPicker
name="accent"
color={
isNilOrEmpty(scheme.schemeStorage.source?.accent)
? undefined
: scheme.schemeStorage.source?.accent
}
/>
</div>
<label className={styles.label}>Danger Color*</label>
<div className={styles.color_picker_row}>
<FloatColorPicker
name="danger"
color={
isNilOrEmpty(scheme.schemeStorage.source?.danger)
? undefined
: scheme.schemeStorage.source?.danger
}
/>
{errMsg.has('danger') && <span className={styles.error_msg}>{errMsg.get('danger')}</span>}
</div>
<label className={styles.label}>Success Color*</label>
<div className={styles.color_picker_row}>
<FloatColorPicker
name="success"
color={
isNilOrEmpty(scheme.schemeStorage.source?.success)
? undefined
: scheme.schemeStorage.source?.success
}
/>
{errMsg.has('success') && (
<span className={styles.error_msg}>{errMsg.get('success')}</span>
)}
</div>
<label className={styles.label}>Warning Color*</label>
<div className={styles.color_picker_row}>
<FloatColorPicker
name="warn"
color={
isNilOrEmpty(scheme.schemeStorage.source?.warn)
? undefined
: scheme.schemeStorage.source?.warn
}
/>
{errMsg.has('warn') && <span className={styles.error_msg}>{errMsg.get('warn')}</span>}
</div>
<label className={styles.label}>Info Color*</label>
<div className={styles.color_picker_row}>
<FloatColorPicker
name="info"
color={
isNilOrEmpty(scheme.schemeStorage.source?.info)
? undefined
: scheme.schemeStorage.source?.info
}
/>
{errMsg.has('info') && <span className={styles.error_msg}>{errMsg.get('info')}</span>}
</div>
<label className={styles.label}>Foreground Color*</label>
<div className={styles.color_picker_row}>
<FloatColorPicker
name="foreground"
color={
isNilOrEmpty(scheme.schemeStorage.source?.foreground)
? undefined
: scheme.schemeStorage.source?.foreground
}
/>
{errMsg.has('foreground') && (
<span className={styles.error_msg}>{errMsg.get('foreground')}</span>
)}
</div>
<label className={styles.label}>Background Color*</label>
<div className={styles.color_picker_row}>
<FloatColorPicker
name="background"
color={
isNilOrEmpty(scheme.schemeStorage.source?.background)
? undefined
: scheme.schemeStorage.source?.background
}
/>
{errMsg.has('background') && (
<span className={styles.error_msg}>{errMsg.get('background')}</span>
)}
</div>
<h5 className={styles.segment_title}>Custom Colors</h5>
<label style={{ gridColumn: 1 }}>Name</label>
<label>Color</label>
<div>
<button type="button" className="small" onClick={addEntryAction}>
Add Color
</button>
</div>
{originalColors
.filter((color) => !includes(deleted, color.id))
.map((color) => (
<ColorEntry
key={color.id}
entry={color}
onDelete={(index) => setDeleted((prev) => [...prev, index])}
/>
))}
{newColors
.filter((color) => !includes(deleted, color.id))
.map((color) => (
<ColorEntry
key={color.id}
entry={color}
onDelete={(index) => setDeleted((prev) => [...prev, index])}
/>
))}
<h5 className={styles.segment_title}>Automated parameters</h5>
<label style={{ gridColumn: 2 }}>Chroma shifting</label>
<label style={{ gridColumn: 3 }}>Lightness shifting</label>
<label className={styles.label}>Hover</label>
<div className="input_wrapper">
<input
type="number"
name="hover_chroma"
defaultValue={((defaultSetting?.hover.chroma ?? 0) * 100).toFixed(2)}
className={styles.parameter_input}
/>
<span>%</span>
</div>
<div className="input_wrapper">
<input
type="number"
name="hover_lightness"
defaultValue={((defaultSetting?.hover.lightness ?? 0) * 100).toFixed(2)}
className={styles.parameter_input}
/>
<span>%</span>
</div>
<label className={styles.label}>Active</label>
<div className="input_wrapper">
<input
type="number"
name="active_chroma"
defaultValue={((defaultSetting?.active.chroma ?? 0) * 100).toFixed(2)}
className={styles.parameter_input}
/>
<span>%</span>
</div>
<div className="input_wrapper">
<input
type="number"
name="active_lightness"
defaultValue={((defaultSetting?.active.lightness ?? 0) * 100).toFixed(2)}
className={styles.parameter_input}
/>
<span>%</span>
</div>
<label className={styles.label}>Focus</label>
<div className="input_wrapper">
<input
type="number"
name="focus_chroma"
defaultValue={((defaultSetting?.focus.chroma ?? 0) * 100).toFixed(2)}
className={styles.parameter_input}
/>
<span>%</span>
</div>
<div className="input_wrapper">
<input
type="number"
name="focus_lightness"
defaultValue={((defaultSetting?.focus.lightness ?? 0) * 100).toFixed(2)}
className={styles.parameter_input}
/>
<span>%</span>
</div>
<label className={styles.label}>Disabled</label>
<div className="input_wrapper">
<input
type="number"
name="disabled_chroma"
defaultValue={((defaultSetting?.disabled.chroma ?? 0) * 100).toFixed(2)}
className={styles.parameter_input}
/>
<span>%</span>
</div>
<div className="input_wrapper">
<input
type="number"
name="disabled_lightness"
defaultValue={((defaultSetting?.disabled.lightness ?? 0) * 100).toFixed(2)}
className={styles.parameter_input}
/>
<span>%</span>
</div>
<label className={styles.label}>Convert to Dark scheme</label>
<div className="input_wrapper">
<input
type="number"
name="dark_chroma"
defaultValue={((defaultSetting?.dark_convert.chroma ?? 0) * 100).toFixed(2)}
className={styles.parameter_input}
/>
<span>%</span>
</div>
<div className="input_wrapper">
<input
type="number"
name="dark_lightness"
defaultValue={((defaultSetting?.dark_convert.lightness ?? 0) * 100).toFixed(2)}
className={styles.parameter_input}
/>
<span>%</span>
</div>
<h5 className={styles.segment_title}>Settings</h5>
<label className={styles.label}>Color Expanding Method</label>
<div style={{ gridColumn: '2 / span 2' }}>
<VSegmentedControl
options={expandingMethods}
name="expanding"
defaultValue={defaultSetting?.expand_method}
/>
</div>
<label className={styles.label}>Follow WACG Standard</label>
<div style={{ gridColumn: '2 / span 2' }}>
<VSegmentedControl
options={wacgFollowStrategies}
name="wacg"
defaultValue={defaultSetting?.wacg_follows}
/>
</div>
<div className={styles.button_row} style={{ gridColumn: '2 / span 2' }}>
<button type="submit" className="primary">
Build Scheme
</button>
<button type="submit" className="secondary" formAction={handleDraftAction}>
Save Draft
</button>
</div>
</form>
</ScrollArea>
);
}

View File

@ -1,62 +0,0 @@
@layer pages {
.preview_layout {
padding: var(--spacing-s) var(--spacing-m);
width: 100%;
display: flex;
flex-direction: column;
align-items: stretch;
gap: var(--spacing-m);
}
.preview_block {
width: inherit;
padding: var(--spacing-xl) var(--spacing-m);
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: var(--spacing-xs);
h2 {
font-size: var(--font-size-xl);
font-weight: bold;
line-height: 1.7em;
}
}
.preview_unit {
width: inherit;
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: var(--spacing-xs);
}
.preview_indi_block {
width: inherit;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--spacing-xs);
}
.preview_swatch {
width: inherit;
display: grid;
grid-template-columns: repeat(16, 1fr);
gap: var(--spacing-xs);
.preview_swatch_cell {
padding: var(--spacing-xs) var(--spacing-s);
.swatch_label {
font-size: var(--font-size-s);
filter: invert(100%);
}
}
}
.preview_cell {
padding: var(--spacing-xs) var(--spacing-s);
display: flex;
flex-direction: column;
align-items: flex-start;
gap: var(--spacing-xxs);
font-size: var(--font-size-s);
line-height: 1.5em;
.wacg {
font-size: var(--font-size-xxs);
line-height: 1em;
}
}
}

View File

@ -1,177 +0,0 @@
import { capitalize, keys } from 'lodash-es';
import { FC, ReactNode, useMemo } from 'react';
import { useColorFunction } from '../../../ColorFunctionContext';
import { ScrollArea } from '../../../components/ScrollArea';
import { SchemeContent } from '../../../models';
import { Q2Baseline, Q2ColorSet, Q2ColorUnit, Q2SchemeStorage } from '../../../q-2-scheme';
import styles from './Preview.module.css';
interface PreviewCellProps {
bg: string;
fg: string;
children: ReactNode;
}
const PreviewCell: FC<PreviewCellProps> = ({ bg, fg, children }) => {
const { colorFn } = useColorFunction();
const wacgRatio = useMemo(() => {
try {
if (!colorFn) return null;
return colorFn.wacg_relative_contrast(fg, bg);
} catch (e) {
console.error('[Error on calc WACG Ratio]', e);
}
return null;
}, [bg, fg]);
return (
<div className={styles.preview_cell} style={{ backgroundColor: `#${bg}`, color: `#${fg}` }}>
<span>{children}</span>
{wacgRatio && <span className={styles.wacg}>WACG {wacgRatio?.toFixed(2)}</span>}
</div>
);
};
interface PreviewLineProps {
name: string;
unit: Q2ColorSet;
}
const PreviewLine: FC<PreviewLineProps> = ({ name, unit }) => {
return (
<div className={styles.preview_unit}>
<PreviewCell bg={unit.root} fg={unit.onRoot}>
{name}
</PreviewCell>
<PreviewCell bg={unit.hover} fg={unit.onRoot}>
{name} Hover
</PreviewCell>
<PreviewCell bg={unit.active} fg={unit.onRoot}>
{name} Active
</PreviewCell>
<PreviewCell bg={unit.focus} fg={unit.onRoot}>
{name} Focus
</PreviewCell>
<PreviewCell bg={unit.disabled} fg={unit.onDisabled}>
{name} Disabled
</PreviewCell>
</div>
);
};
interface PreviewSwatchLineProps {
swatch: Record<string, string>;
}
const PreviewSwatchLine: FC<PreviewSwatchLineProps> = ({ swatch }) => {
const cells = useMemo(() => {
const collection: ReactNode[] = [];
for (const key of keys(swatch)) {
const color = swatch[key];
collection.push(
<div
className={styles.preview_swatch_cell}
style={{ backgroundColor: `#${color}` }}
key={key}>
<span className={styles.swatch_label} style={{ color: `#${color}` }}>
{key}
</span>
</div>,
);
}
return collection;
}, [swatch]);
return <div className={styles.preview_swatch}>{cells}</div>;
};
interface PreviewSetProps {
name: string;
colorUnit: Q2ColorUnit;
}
const PreviewSet: FC<PreviewSetProps> = ({ name, colorUnit }) => {
return (
<>
<PreviewLine name={name} unit={colorUnit.root} />
<PreviewLine name={`${name} Surface`} unit={colorUnit.surface} />
<PreviewSwatchLine swatch={colorUnit.swatch} />
</>
);
};
interface PreviewBlockProps {
baseline: Q2Baseline;
title: string;
}
const PreviewBlock: FC<PreviewBlockProps> = ({ baseline, title }) => {
const customSets = useMemo(() => {
const colors = keys(baseline.customColors);
const elements: ReactNode[] = [];
for (const key of colors) {
const color = baseline.customColors[key];
elements.push(<PreviewSet name={capitalize(key)} colorUnit={color} />);
}
return elements;
}, [baseline.customColors]);
return (
<div className={styles.preview_block} style={{ backgroundColor: `#${baseline.surface.root}` }}>
<h2 style={{ color: `#${baseline.surface.onRoot}` }}>{title}</h2>
<PreviewSet name="Primary" colorUnit={baseline.primary} />
<PreviewSet name="Secondary" colorUnit={baseline.secondary} />
<PreviewSet name="Tertiary" colorUnit={baseline.tertiary} />
<PreviewSet name="Accent" colorUnit={baseline.accent} />
<PreviewSet name="Danger" colorUnit={baseline.danger} />
<PreviewSet name="Success" colorUnit={baseline.success} />
<PreviewSet name="Warn" colorUnit={baseline.warn} />
<PreviewSet name="Info" colorUnit={baseline.info} />
<PreviewLine name="Neutral" unit={baseline.neutral} />
<PreviewLine name="Neutral Variant" unit={baseline.neutralVariant} />
<PreviewLine name="Surface" unit={baseline.surface} />
<PreviewLine name="Surface Variant" unit={baseline.surfaceVariant} />
<div className={styles.preview_indi_block}>
<PreviewCell bg={baseline.shadow} fg={baseline.surface.root.onRoot}>
Shadow
</PreviewCell>
<PreviewCell bg={baseline.overlay} fg={baseline.surface.root.onRoot}>
Overlay
</PreviewCell>
<PreviewCell bg={baseline.outline} fg={baseline.surface.root.onRoot}>
Outline
</PreviewCell>
<PreviewCell bg={baseline.outlineVariant} fg={baseline.surface.root.onRoot}>
Outline Variant
</PreviewCell>
</div>
<PreviewSwatchLine swatch={baseline.neutralSwatch} />
{customSets}
</div>
);
};
interface PreviewProps {
scheme: SchemeContent<Q2SchemeStorage>;
}
const Q2SchemePreview: FC<PreviewProps> = ({ scheme }) => {
return (
<ScrollArea enableY>
<div className={styles.preview_layout}>
<div className={styles.preview_layout}>
{scheme.schemeStorage.scheme?.light && (
<PreviewBlock baseline={scheme.schemeStorage.scheme.light} title="Light Scheme" />
)}
{scheme.schemeStorage.scheme?.dark && (
<PreviewBlock baseline={scheme.schemeStorage.scheme.dark} title="Dark Scheme" />
)}
</div>
</div>
</ScrollArea>
);
};
export default Q2SchemePreview;

View File

@ -15,10 +15,8 @@ import { CorruptedScheme } from '../page-components/scheme/CorruptedScheme';
import { M2Scheme } from '../page-components/scheme/M2Scheme';
import { M3DynamicScheme } from '../page-components/scheme/M3DynamicScheme';
import { M3Scheme } from '../page-components/scheme/M3Scheme';
import { Q2Scheme } from '../page-components/scheme/Q2Scheme';
import { QScheme } from '../page-components/scheme/QScheme';
import { SwatchScheme } from '../page-components/scheme/SwatchScheme';
import { Q2SchemeSource } from '../q-2-scheme';
import { QSchemeStorage } from '../q-scheme';
import { useScheme, useUpdateScheme } from '../stores/schemes';
import { SwatchSchemeStorage } from '../swatch_scheme';
@ -52,8 +50,6 @@ export function SchemeDetail() {
switch (scheme?.type) {
case 'q_scheme':
return <QScheme scheme={scheme as SchemeContent<QSchemeStorage>} />;
case 'q_2_scheme':
return <Q2Scheme scheme={scheme as SchemeContent<Q2SchemeSource>} />;
case 'swatch_scheme':
return <SwatchScheme scheme={scheme as SchemeContent<SwatchSchemeStorage>} />;
case 'material_2':

View File

@ -1,67 +0,0 @@
import { QSchemeSetting } from './q-scheme';
export type Q2ColorSet = {
root: string;
hover: string;
active: string;
focus: string;
disabled: string;
onRoot: string;
onDisabled: string;
};
export type Q2ColorUnit = {
root: Q2ColorSet;
surface: Q2ColorSet;
swatch: Record<string, string>;
};
export type Q2Baseline = {
primary: Q2ColorUnit;
secondary: Q2ColorUnit | null;
tertiary: Q2ColorUnit | null;
accent: Q2ColorUnit | null;
neutral: Q2ColorUnit;
neutralVariant: Q2ColorUnit;
surface: Q2ColorUnit;
surfaceVariant: Q2ColorUnit;
neutralSwatch: Record<string, string>;
danger: Q2ColorUnit;
success: Q2ColorUnit;
warn: Q2ColorUnit;
info: Q2ColorUnit;
shadow: string;
overlay: string;
outline: string;
outlineVariant: string;
customColors: Record<string, Q2ColorUnit>;
};
export type Q2Scheme = {
light: Q2Baseline;
dark: Q2Baseline;
};
export type Q2SchemeSource = {
primary: string | null;
secondary: string | null;
tertiary: string | null;
accent: string | null;
danger: string | null;
success: string | null;
warn: string | null;
info: string | null;
foreground: string | null;
background: string | null;
custom_colors?: Record<string, string>;
setting: QSchemeSetting | null;
};
export type Q2SchemeStorage = {
source?: Q2SchemeSource;
scheme?: Q2Scheme;
cssVariables?: string;
cssAutoSchemeVariables?: string;
scssVariables?: string;
jsVariables?: string;
};

View File

@ -47,7 +47,6 @@
--color-youlv: hsl(118, 26%, 19%);
--color-jingtailan: hsl(207, 65%, 43%);
--color-yejuzi: hsl(240, 25%, 43%);
--color-jugengzi: hsl(297, 38%, 38%);
/* background colors */
--color-yunshanlv: hsl(146, 25%, 11%);
--color-wumeizi: hsl(305, 22%, 10%);