mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-13 05:42:38 +00:00
inspect.code {language: "tailwind", mobile} dropped every mobile max-width and put the desktop one behind lg:, so a fixed-size layer that is the same size on both frames (a 48 x 48 badge) stretched to the full width below 1024 px. A mobile max-width that equals the desktop one is now kept as a plain class, with no lg: copy. A differing max-width still goes fluid on mobile with the desktop cap behind lg:.
Tests: the issue's two frames (a 1440 and a 390 page, each with a 48 x 48 badge) keep max-w-[48px] unprefixed, and a page-wide banner still gets its lg: cap only. The slashed-names test gave the CTAs frame the same 342 px on both frames, which the new rule rightly keeps, so its desktop frame is now 600 px wide. docs/mcp.md notes the rule.
Model-Effort: Opus 5.5 drove; Fable 5.2 queued; Opus 5.5 briefed; Sonnet 5.5 wrote at medium
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2408d0aa81
commit
3d22c606a7
3 files changed
+53
-4
No files matched your search
@@ -413,10 +413,11 @@ fn zero_padding(sides: [bool; 4]) -> Vec<&'static str> {
|
||||
pub(crate) fn merge(mobile: Option<&[(String, String)]>, desktop: &[(String, String)]) -> Vec<String> {
|
||||
let hidden = [("display".to_string(), "hidden".to_string())];
|
||||
// A mobile frame's widths are the phone's, not caps: below the breakpoint boxes fill the page.
|
||||
// Except a cap desktop has too: that is the layer's own size (a badge, an avatar), kept at every width.
|
||||
let fluid: Vec<(String, String)>;
|
||||
let mobile = match mobile {
|
||||
Some(m) => {
|
||||
fluid = m.iter().filter(|(k, _)| k != "max-width").cloned().collect();
|
||||
fluid = m.iter().filter(|(k, c)| k != "max-width" || desktop.iter().any(|(_, d)| d == c)).cloned().collect();
|
||||
fluid.as_slice()
|
||||
}
|
||||
None => &hidden,
|
||||
|
||||
@@ -176,7 +176,11 @@ fn slashed_layer_names_and_phone_widths_merge_right() {
|
||||
for (name, width, dir, sizing) in [("Landing", 1440.0, "horizontal", "hug"), ("Landing mobile", 390.0, "vertical", "fill")] {
|
||||
let p = idv(&ex(&mut s, "frame.create", json!({"x": 0, "y": 0, "width": width, "height": 600, "name": name})));
|
||||
ex(&mut s, "layout.add", json!({"ids": [p], "direction": "vertical"}));
|
||||
let ctas = idv(&ex(&mut s, "frame.create", json!({"x": 0, "y": 0, "width": 342, "height": 120, "parent": p, "name": "CTAs"})));
|
||||
let ctas = idv(&ex(
|
||||
&mut s,
|
||||
"frame.create",
|
||||
json!({"x": 0, "y": 0, "width": if width > 1000.0 { 600.0 } else { 342.0 }, "height": 120, "parent": p, "name": "CTAs"}),
|
||||
));
|
||||
ex(&mut s, "layout.add", json!({"ids": [ctas], "direction": dir}));
|
||||
let b = idv(&ex(&mut s, "frame.create", json!({"x": 0, "y": 0, "width": 200, "height": 48, "parent": ctas, "name": "Button/Primary"})));
|
||||
ex(&mut s, "layout.add", json!({"ids": [b], "direction": "horizontal"}));
|
||||
@@ -190,7 +194,7 @@ fn slashed_layer_names_and_phone_widths_merge_right() {
|
||||
let button =
|
||||
out.lines().find(|l| l.contains("lg:") && l.contains("rounded") || l.contains("self-stretch") && l.contains("flex-row")).unwrap_or_default();
|
||||
assert!(button.contains("self-stretch") && button.contains("lg:self-auto"), "the slashed name keeps its mobile classes: {out}");
|
||||
assert!(!out.contains("max-w-[342px] ") || out.contains("lg:max-w-[342px]"), "no phone-width cap below lg: {out}");
|
||||
assert!(!out.contains("max-w-[342px]") && out.contains("lg:max-w-[600px]"), "no phone-width cap below lg: {out}");
|
||||
assert!(out.contains("Only in the mobile frame") && out.contains("Tag"), "{out}");
|
||||
let divider = out.lines().filter(|l| l.contains("<div") && (l.contains("h-[1px]") || l.contains("h-px"))).count();
|
||||
assert!(out.contains("w-full") && divider <= 2, "{out}");
|
||||
@@ -353,3 +357,45 @@ fn font_family_with_quote_and_bracket_stays_a_valid_class_token() {
|
||||
assert!(after.starts_with('>') && !attr.contains('\\'), "{out}");
|
||||
assert!(!inner.is_empty() && !inner.contains(['\\', '"', ']']), "{out}");
|
||||
}
|
||||
|
||||
/// Issue #2272: a desktop `page` 1440 wide and a mobile one 390 wide, each holding a fixed 48 x 48
|
||||
/// `badge` and a `banner` that fills the page.
|
||||
fn badge_frames(s: &mut Session) -> (NodeId, NodeId) {
|
||||
let mut frames = Vec::new();
|
||||
for (width, padding, x) in [(1440.0, 80, 0), (390.0, 24, 1600)] {
|
||||
let p = idv(&ex(
|
||||
s,
|
||||
"frame.create",
|
||||
json!({"name": "page", "x": x, "y": 0, "width": width, "height": 400, "props": {"layout": "vertical", "padding": padding, "gap": 16}}),
|
||||
));
|
||||
let badge_props = json!({"layout": "horizontal", "sizingH": "fixed", "sizingV": "fixed", "fill": "#E8EEF7"});
|
||||
ex(s, "frame.create", json!({"name": "badge", "parent": p, "width": 48, "height": 48, "props": badge_props}));
|
||||
let inner = width - 2.0 * f64::from(padding);
|
||||
ex(
|
||||
s,
|
||||
"frame.create",
|
||||
json!({"name": "banner", "parent": p, "width": inner, "height": 40, "props": {"layout": "horizontal", "fill": "#112233"}}),
|
||||
);
|
||||
frames.push(p);
|
||||
}
|
||||
(frames[0], frames[1])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_max_width_both_frames_share_stays_unprefixed() {
|
||||
let mut s = Session::new();
|
||||
let (desk, mob) = badge_frames(&mut s);
|
||||
let out = code(&mut s, desk, json!({"mobile": mob}));
|
||||
let badge = out.lines().find(|l| l.contains("bg-[#E8EEF7]")).unwrap_or_default();
|
||||
assert!(badge.contains("max-w-[48px]") && !badge.contains("lg:max-w-[48px]"), "the badge keeps its cap at every width: {out}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_max_width_that_differs_still_goes_fluid_on_mobile() {
|
||||
let mut s = Session::new();
|
||||
let (desk, mob) = badge_frames(&mut s);
|
||||
let out = code(&mut s, desk, json!({"mobile": mob}));
|
||||
let banner = out.lines().find(|l| l.contains("bg-[#112233]")).unwrap_or_default();
|
||||
assert!(banner.contains("lg:max-w-[1280px]"), "the page-wide box is capped from lg: {banner}");
|
||||
assert!(!banner.split_whitespace().any(|c| c == "max-w-[1280px]" || c == "max-w-[342px]"), "no phone-width cap below lg: {banner}");
|
||||
}
|
||||
+3
-1
@@ -1732,7 +1732,9 @@ permissions: without it nothing is downloaded); `plugin.directory.publish {path
|
||||
`--radius-*`, `--text-*`, `--spacing-*`, `--font-*`; a number's namespace and unit come from what
|
||||
layers bind it to), other modes on `[data-<collection>="<mode>"]`. With `mobile: <frame id>` (the
|
||||
same layer names, mobile layout) the code is mobile first: the mobile frame's classes, the
|
||||
desktop frame's that differ behind `lg:`, and layers only on desktop `hidden` plus `lg:flex`
|
||||
desktop frame's that differ behind `lg:` (a mobile `max-width` is dropped as the phone's own width,
|
||||
unless desktop has the same cap: a fixed-size layer such as a badge keeps `max-w-[48px]` at every
|
||||
width), and layers only on desktop `hidden` plus `lg:flex`
|
||||
(auto layout frames) or `lg:block` (other layers).
|
||||
- **Component code:** `inspect.code {id, language: react|leptos|vue|swiftui|compose|arkui|flutter}` on a component,
|
||||
a component set or a variant writes the component as a reusable unit: React (TSX) with a props
|
||||
|
||||
Reference in new issue
Block a user