Compare commits

..

5 Commits

Author SHA1 Message Date
徐涛
1a136670ff 调整输出自定义颜色变量名称的大小写。 2025-02-17 10:14:23 +08:00
徐涛
639b6b223e 避免Scheme在构建之前点击预览和导出。 2025-02-17 10:10:10 +08:00
徐涛
e980eeec3d 增加禁用标签页功能。 2025-02-17 09:44:01 +08:00
徐涛
4b56d3a625 增加一个快捷判断内容综合为空的函数。 2025-02-17 09:41:19 +08:00
徐涛
84c164b2c8 完成M3动态Scheme的创建。 2025-02-17 09:28:33 +08:00
13 changed files with 128 additions and 32 deletions

View File

@ -75,7 +75,7 @@ impl M2BaselineColors {
variable_lines.push(format!("--color-{}-shadow: #{};", prefix, self.shadow)); variable_lines.push(format!("--color-{}-shadow: #{};", prefix, self.shadow));
for (name, color_set) in &self.custom_colors { for (name, color_set) in &self.custom_colors {
variable_lines.extend(color_set.to_css_variable(&prefix, name)); variable_lines.extend(color_set.to_css_variable(&prefix, &name.to_lowercase()));
} }
variable_lines variable_lines
@ -93,7 +93,7 @@ impl M2BaselineColors {
variable_lines.push(format!("$color-{}-shadow: #{};", prefix, self.shadow)); variable_lines.push(format!("$color-{}-shadow: #{};", prefix, self.shadow));
for (name, color_set) in &self.custom_colors { for (name, color_set) in &self.custom_colors {
variable_lines.extend(color_set.to_scss_variable(&prefix, name)); variable_lines.extend(color_set.to_scss_variable(&prefix, &name.to_lowercase()));
} }
variable_lines variable_lines
@ -111,7 +111,7 @@ impl M2BaselineColors {
variable_lines.push(format!("{}Shadow: '#{}',", prefix, self.shadow)); variable_lines.push(format!("{}Shadow: '#{}',", prefix, self.shadow));
for (name, color_set) in &self.custom_colors { for (name, color_set) in &self.custom_colors {
variable_lines.extend(color_set.to_javascript_object(&prefix, name)); variable_lines.extend(color_set.to_javascript_object(&prefix, &name.to_lowercase()));
} }
variable_lines variable_lines

View File

@ -125,7 +125,7 @@ impl M3BaselineColors {
css_variables.push(format!("--color-{}-scrim: #{};", prefix, self.scrim)); css_variables.push(format!("--color-{}-scrim: #{};", prefix, self.scrim));
css_variables.push(format!("--color-{}-shadow: #{};", prefix, self.shadow)); css_variables.push(format!("--color-{}-shadow: #{};", prefix, self.shadow));
for (name, color_set) in &self.customs { for (name, color_set) in &self.customs {
css_variables.extend(color_set.to_css_variables(prefix, name)); css_variables.extend(color_set.to_css_variables(prefix, &name.to_lowercase()));
} }
css_variables css_variables
@ -148,7 +148,7 @@ impl M3BaselineColors {
scss_variables.push(format!("$color-{}-scrim: #{};", prefix, self.scrim)); scss_variables.push(format!("$color-{}-scrim: #{};", prefix, self.scrim));
scss_variables.push(format!("$color-{}-shadow: #{};", prefix, self.shadow)); scss_variables.push(format!("$color-{}-shadow: #{};", prefix, self.shadow));
for (name, color_set) in &self.customs { for (name, color_set) in &self.customs {
scss_variables.extend(color_set.to_scss_variables(prefix, name)); scss_variables.extend(color_set.to_scss_variables(prefix, &name.to_lowercase()));
} }
scss_variables scss_variables
@ -177,7 +177,8 @@ impl M3BaselineColors {
js_object_fields.push(format!("{}Scrim: '#{}',", prefix, self.scrim)); js_object_fields.push(format!("{}Scrim: '#{}',", prefix, self.scrim));
js_object_fields.push(format!("{}Shadow: '#{}',", prefix, self.shadow)); js_object_fields.push(format!("{}Shadow: '#{}',", prefix, self.shadow));
for (name, color_set) in &self.customs { for (name, color_set) in &self.customs {
js_object_fields.extend(color_set.to_javascript_object_fields(prefix, name)); js_object_fields
.extend(color_set.to_javascript_object_fields(prefix, &name.to_lowercase()));
} }
js_object_fields js_object_fields

View File

@ -84,10 +84,10 @@ impl SchemeExport for SwatchScheme {
let mut variables = Vec::new(); let mut variables = Vec::new();
for (name, swatch) in &self.light { for (name, swatch) in &self.light {
variables.extend(swatch.to_css_variables("light", name)); variables.extend(swatch.to_css_variables("light", &name.to_lowercase()));
} }
for (name, swatch) in &self.dark { for (name, swatch) in &self.dark {
variables.extend(swatch.to_css_variables("dark", name)); variables.extend(swatch.to_css_variables("dark", &name.to_lowercase()));
} }
variables.join("\n") variables.join("\n")
@ -97,10 +97,10 @@ impl SchemeExport for SwatchScheme {
let mut variables = Vec::new(); let mut variables = Vec::new();
for (name, swatch) in &self.light { for (name, swatch) in &self.light {
variables.extend(swatch.to_scss_variables("light", name)); variables.extend(swatch.to_scss_variables("light", &name.to_lowercase()));
} }
for (name, swatch) in &self.dark { for (name, swatch) in &self.dark {
variables.extend(swatch.to_scss_variables("dark", name)); variables.extend(swatch.to_scss_variables("dark", &name.to_lowercase()));
} }
variables.join("\n") variables.join("\n")
@ -114,7 +114,7 @@ impl SchemeExport for SwatchScheme {
for (name, swatch) in &self.light { for (name, swatch) in &self.light {
object.extend( object.extend(
swatch swatch
.to_javascript_fields("light", name) .to_javascript_fields("light", &name.to_lowercase())
.iter() .iter()
.map(|s| format!(" {}", s)), .map(|s| format!(" {}", s)),
); );
@ -125,7 +125,7 @@ impl SchemeExport for SwatchScheme {
for (name, swatch) in &self.dark { for (name, swatch) in &self.dark {
object.extend( object.extend(
swatch swatch
.to_javascript_fields("dark", name) .to_javascript_fields("dark", &name.to_lowercase())
.iter() .iter()
.map(|s| format!(" {}", s)), .map(|s| format!(" {}", s)),
); );

View File

@ -18,5 +18,9 @@
&:hover { &:hover {
color: var(--color-primary-hover); color: var(--color-primary-hover);
} }
&.disabled {
color: var(--color-primary-disabled);
cursor: not-allowed;
}
} }
} }

View File

@ -3,20 +3,30 @@ import { isEqual, isNil } from 'lodash-es';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import styles from './Tab.module.css'; import styles from './Tab.module.css';
type TabProps = { type TabOption = {
tabs: { title: string; id: unknown }[]; title: string;
activeTab?: unknown; id: unknown;
onActive?: (id: unknown) => void;
}; };
export function Tab({ tabs = [], activeTab, onActive }: TabProps) { type TabProps = {
tabs: TabOption[];
activeTab?: unknown;
onActive?: (id: TabOption['id']) => void;
disabled?: { [d: keyof TabOption['id']]: boolean };
};
export function Tab({ tabs = [], activeTab, onActive, disabled }: TabProps) {
const [active, setActive] = useState(() => const [active, setActive] = useState(() =>
isNil(activeTab) ? 0 : tabs.findIndex((tab) => isEqual(tab.id, activeTab)), isNil(activeTab) ? 0 : tabs.findIndex((tab) => isEqual(tab.id, activeTab)),
); );
const handleActivate = useCallback((index: number) => { const handleActivate = useCallback(
setActive(index); (index: number) => {
onActive?.(tabs[index].id); if (disabled?.[tabs[index].id] ?? false) return;
}, []); setActive(index);
onActive?.(tabs[index].id);
},
[tabs, onActive, disabled],
);
useEffect(() => { useEffect(() => {
const activeIndex = tabs.findIndex((tab) => isEqual(tab.id, activeTab)); const activeIndex = tabs.findIndex((tab) => isEqual(tab.id, activeTab));
@ -30,7 +40,11 @@ export function Tab({ tabs = [], activeTab, onActive }: TabProps) {
{tabs.map((tab, index) => ( {tabs.map((tab, index) => (
<div <div
key={`tab_${index}_${tab.id}`} key={`tab_${index}_${tab.id}`}
className={cx(styles.tab, isEqual(index, active) && styles.actived)} className={cx(
styles.tab,
isEqual(index, active) && styles.actived,
(disabled?.[tab.id] ?? false) && styles.disabled,
)}
onClick={() => handleActivate(index)}> onClick={() => handleActivate(index)}>
{tab.title} {tab.title}
</div> </div>

View File

@ -64,7 +64,7 @@ export type MaterialDesign3DynamicSchemeSource = {
error: string | null; error: string | null;
custom_colors?: Record<string, string>; custom_colors?: Record<string, string>;
variant: number | null; variant: number | null;
constrastLevel: number | null; contrastLevel: number | null;
harmonizeCustoms: boolean | null; harmonizeCustoms: boolean | null;
}; };

View File

@ -3,6 +3,7 @@ import { useState } from 'react';
import { Tab } from '../../components/Tab'; import { Tab } from '../../components/Tab';
import { MaterialDesign2SchemeStorage } from '../../material-2-scheme'; import { MaterialDesign2SchemeStorage } from '../../material-2-scheme';
import { SchemeContent } from '../../models'; import { SchemeContent } from '../../models';
import { isNilOrEmpty } from '../../utls';
import { SchemeExport } from './Export'; import { SchemeExport } from './Export';
import { M2SchemeBuilder } from './m2-scheme/Builder'; import { M2SchemeBuilder } from './m2-scheme/Builder';
import { M2SchemePreview } from './m2-scheme/Preview'; import { M2SchemePreview } from './m2-scheme/Preview';
@ -24,7 +25,15 @@ export function M2Scheme({ scheme }: M2SchemeProps) {
return ( return (
<> <>
<Tab tabs={tabOptions} activeTab={activeTab} onActive={(v) => setActiveTab(v as string)} /> <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') && <M2SchemePreview scheme={scheme} />} {isEqual(activeTab, 'overview') && <M2SchemePreview scheme={scheme} />}
{isEqual(activeTab, 'builder') && ( {isEqual(activeTab, 'builder') && (
<M2SchemeBuilder scheme={scheme} onBuildComplete={() => setActiveTab('overview')} /> <M2SchemeBuilder scheme={scheme} onBuildComplete={() => setActiveTab('overview')} />

View File

@ -3,6 +3,7 @@ import { useState } from 'react';
import { Tab } from '../../components/Tab'; import { Tab } from '../../components/Tab';
import { MaterialDesign3DynamicSchemeStorage } from '../../material-3-scheme'; import { MaterialDesign3DynamicSchemeStorage } from '../../material-3-scheme';
import { SchemeContent } from '../../models'; import { SchemeContent } from '../../models';
import { isNilOrEmpty } from '../../utls';
import { SchemeExport } from './Export'; import { SchemeExport } from './Export';
import { M3DynamicSchemeBuilder } from './m3-dynamic-scheme/Builder'; import { M3DynamicSchemeBuilder } from './m3-dynamic-scheme/Builder';
import { M3SchemePreview } from './m3-scheme/Preview'; import { M3SchemePreview } from './m3-scheme/Preview';
@ -24,7 +25,15 @@ export function M3DynamicScheme({ scheme }: M3SchemeProps) {
return ( return (
<> <>
<Tab tabs={tabOptions} activeTab={activeTab} onActive={(v) => setActiveTab(v as string)} /> <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') && <M3SchemePreview scheme={scheme.schemeStorage.scheme} />} {isEqual(activeTab, 'overview') && <M3SchemePreview scheme={scheme.schemeStorage.scheme} />}
{isEqual(activeTab, 'builder') && ( {isEqual(activeTab, 'builder') && (
<M3DynamicSchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} /> <M3DynamicSchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />

View File

@ -3,6 +3,7 @@ import { useState } from 'react';
import { Tab } from '../../components/Tab'; import { Tab } from '../../components/Tab';
import { MaterialDesign3SchemeStorage } from '../../material-3-scheme'; import { MaterialDesign3SchemeStorage } from '../../material-3-scheme';
import { SchemeContent } from '../../models'; import { SchemeContent } from '../../models';
import { isNilOrEmpty } from '../../utls';
import { SchemeExport } from './Export'; import { SchemeExport } from './Export';
import { M3SchemeBuilder } from './m3-scheme/Builder'; import { M3SchemeBuilder } from './m3-scheme/Builder';
import { M3SchemePreview } from './m3-scheme/Preview'; import { M3SchemePreview } from './m3-scheme/Preview';
@ -24,7 +25,15 @@ export function M3Scheme({ scheme }: M3SchemeProps) {
return ( return (
<> <>
<Tab tabs={tabOptions} activeTab={activeTab} onActive={(v) => setActiveTab(v as string)} /> <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') && <M3SchemePreview scheme={scheme.schemeStorage.scheme} />} {isEqual(activeTab, 'overview') && <M3SchemePreview scheme={scheme.schemeStorage.scheme} />}
{isEqual(activeTab, 'builder') && ( {isEqual(activeTab, 'builder') && (
<M3SchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} /> <M3SchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />

View File

@ -3,6 +3,7 @@ import { useState } from 'react';
import { Tab } from '../../components/Tab'; import { Tab } from '../../components/Tab';
import { SchemeContent } from '../../models'; import { SchemeContent } from '../../models';
import { QSchemeStorage } from '../../q-scheme'; import { QSchemeStorage } from '../../q-scheme';
import { isNilOrEmpty } from '../../utls';
import { SchemeExport } from './Export'; import { SchemeExport } from './Export';
import { QSchemeBuilder } from './q-scheme/Builder'; import { QSchemeBuilder } from './q-scheme/Builder';
import { QSchemePreview } from './q-scheme/Preview'; import { QSchemePreview } from './q-scheme/Preview';
@ -24,7 +25,15 @@ export function QScheme({ scheme }: QSchemeProps) {
return ( return (
<> <>
<Tab tabs={tabOptions} activeTab={activeTab} onActive={(v) => setActiveTab(v as string)} /> <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') && <QSchemePreview scheme={scheme} />} {isEqual(activeTab, 'overview') && <QSchemePreview scheme={scheme} />}
{isEqual(activeTab, 'builder') && ( {isEqual(activeTab, 'builder') && (
<QSchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} /> <QSchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />

View File

@ -3,6 +3,7 @@ import { useState } from 'react';
import { Tab } from '../../components/Tab'; import { Tab } from '../../components/Tab';
import { SchemeContent } from '../../models'; import { SchemeContent } from '../../models';
import { SwatchSchemeStorage } from '../../swatch_scheme'; import { SwatchSchemeStorage } from '../../swatch_scheme';
import { isNilOrEmpty } from '../../utls';
import { SchemeExport } from './Export'; import { SchemeExport } from './Export';
import { SwatchSchemeBuilder } from './swatch-scheme/Builder'; import { SwatchSchemeBuilder } from './swatch-scheme/Builder';
import { SwatchSchemePreview } from './swatch-scheme/Preview'; import { SwatchSchemePreview } from './swatch-scheme/Preview';
@ -24,7 +25,15 @@ export function SwatchScheme({ scheme }: SwatchSchemeProps) {
return ( return (
<> <>
<Tab tabs={tabOptions} activeTab={activeTab} onActive={(v) => setActiveTab(v as string)} /> <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') && <SwatchSchemePreview scheme={scheme} />} {isEqual(activeTab, 'overview') && <SwatchSchemePreview scheme={scheme} />}
{isEqual(activeTab, 'builder') && ( {isEqual(activeTab, 'builder') && (
<SwatchSchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} /> <SwatchSchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />

View File

@ -8,6 +8,7 @@ import { VSegmentedControl } from '../../../components/VSegmentedControl';
import { MaterialDesign3DynamicSchemeStorage } from '../../../material-3-scheme'; import { MaterialDesign3DynamicSchemeStorage } from '../../../material-3-scheme';
import { Option, SchemeContent } from '../../../models'; import { Option, SchemeContent } from '../../../models';
import { useUpdateScheme } from '../../../stores/schemes'; import { useUpdateScheme } from '../../../stores/schemes';
import { mapToObject } from '../../../utls';
import { ColorEntry, IdenticalColorEntry } from '../ColorEntry'; import { ColorEntry, IdenticalColorEntry } from '../ColorEntry';
import styles from './Builder.module.css'; import styles from './Builder.module.css';
@ -46,7 +47,7 @@ export function M3DynamicSchemeBuilder({ scheme, onBuildCompleted }: M3DynamicSc
return []; return [];
}, []); }, []);
const [contrastLevel, setContrastLevel] = useState<number>( const [contrastLevel, setContrastLevel] = useState<number>(
scheme.schemeStorage.source?.constrastLevel ?? 0, () => scheme.schemeStorage.source?.contrastLevel ?? 1,
); );
const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>( const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
@ -61,7 +62,7 @@ export function M3DynamicSchemeBuilder({ scheme, onBuildCompleted }: M3DynamicSc
try { try {
const dynamicVariant = Number(formData.get('variant')); const dynamicVariant = Number(formData.get('variant'));
const contrastLevel = Number(formData.get('contrast_level')); const contrast = Number(formData.get('contrast_level'));
const harmonizeCustoms = isEqual(formData.get('harmonize_customs'), 'true'); const harmonizeCustoms = isEqual(formData.get('harmonize_customs'), 'true');
const errorColor = formData.get('error') as string; const errorColor = formData.get('error') as string;
const customColors: Record<string, string> = {}; const customColors: Record<string, string> = {};
@ -80,7 +81,34 @@ export function M3DynamicSchemeBuilder({ scheme, onBuildCompleted }: M3DynamicSc
harmonizeCustoms, harmonizeCustoms,
customColors, customColors,
); );
console.debug('[generate m3d]', generate_scheme); updateScheme((prev) => {
prev.schemeStorage.source = {
source: sourceColor,
error: errorColor,
custom_colors: customColors,
variant: dynamicVariant,
contrastLevel: contrast,
harmonizeCustoms: harmonizeCustoms,
};
prev.schemeStorage.scheme = {
white: generate_scheme[0].white,
black: generate_scheme[0].black,
light_baseline: {
...generate_scheme[0].light_baseline,
customs: mapToObject(generate_scheme[0].light_baseline.customs),
},
dark_baseline: {
...generate_scheme[0].dark_baseline,
customs: mapToObject(generate_scheme[0].dark_baseline.customs),
},
};
prev.schemeStorage.cssVariables = generate_scheme[1];
prev.schemeStorage.scssVariables = generate_scheme[2];
prev.schemeStorage.jsVariables = generate_scheme[3];
return prev;
});
onBuildCompleted?.();
} catch (e) { } catch (e) {
console.error('[generate m3d]', e); console.error('[generate m3d]', e);
} }
@ -125,7 +153,7 @@ export function M3DynamicSchemeBuilder({ scheme, onBuildCompleted }: M3DynamicSc
<VSegmentedControl <VSegmentedControl
name="variant" name="variant"
options={variantOptions} options={variantOptions}
defaultValue={scheme.schemeStorage.source?.variant} defaultValue={scheme.schemeStorage.source?.variant ?? 6}
/> />
</div> </div>
<label className={styles.label}>Contrast Level</label> <label className={styles.label}>Contrast Level</label>

View File

@ -15,6 +15,10 @@ export function defaultEmptyValue<T, D>(value: T, defaultValue: D): T | D {
return value; return value;
} }
export function isNilOrEmpty(value?: unknown): boolean {
return isNil(value) || isEmpty(value);
}
export function mapToObject<K extends string | number | symbol, V>(map: Map<K, V>): Record<K, V> { export function mapToObject<K extends string | number | symbol, V>(map: Map<K, V>): Record<K, V> {
const obj = {} as Record<K, V>; const obj = {} as Record<K, V>;
map.forEach((value, key) => { map.forEach((value, key) => {