Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 52 additions & 18 deletions samples/IOSwipeView.Demo/Components/PlaygroundState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,47 +58,81 @@ public sealed class PlaygroundState
public static IReadOnlyList<(string Id, string Label, double Stiffness, double Damping)> Springs =>
[
("snappy", "Snappy", 420, 40),
("bouncy", "Bouncy", 280, 18),
("smooth", "Smooth", 220, 36),
("default", "Default", 300, 32),
("smooth", "Smooth", 240, 28),
("bouncy", "Bouncy", 220, 18),
("gentle", "Gentle", 160, 22),
("wobbly", "Wobbly", 180, 12),
("stiff", "Stiff", 500, 48),
("gentle", "Gentle", 160, 26),
];

public void ApplyPreset(string id)
{
PresetId = id;
Style = SwipeActionStyle.Mask;

switch (id)
{
case "classic":
(ActionWidth, Spacing, ActionCornerRadius, MaskCornerRadius, FadeStart, FadeEnd) = (80d, 0d, 0d, 0d, 0d, 0d);
Style = SwipeActionStyle.Mask;
ActionWidth = 80;
Spacing = 0;
ActionCornerRadius = 0;
MaskCornerRadius = 0;
FadeStart = 0;
FadeEnd = 0;
ReadyToExpandPadding = 50;
ReadyToTriggerPadding = 20;
MinimumPointToTrigger = 200;
break;

case "grouped":
(ActionWidth, Spacing, ActionCornerRadius, MaskCornerRadius, FadeStart, FadeEnd) = (80d, 0d, 0d, 12d, 0d, 0d);
Style = SwipeActionStyle.Mask;
ActionWidth = 80;
Spacing = 0;
ActionCornerRadius = 0;
MaskCornerRadius = 12;
FadeStart = 0;
FadeEnd = 0;
ReadyToExpandPadding = 50;
ReadyToTriggerPadding = 20;
MinimumPointToTrigger = 200;
break;

case "notification":
(ActionWidth, Spacing, ActionCornerRadius, MaskCornerRadius, FadeStart, FadeEnd) = (76d, 6d, 16d, 16d, 10d, 40d);
Style = SwipeActionStyle.Mask;
ActionWidth = 72;
Spacing = 6;
ActionCornerRadius = 16;
MaskCornerRadius = 20;
FadeStart = 10;
FadeEnd = 60;
ReadyToExpandPadding = 50;
ReadyToTriggerPadding = 20;
MinimumPointToTrigger = 200;
break;

case "capsule":
(ActionWidth, Spacing, ActionCornerRadius, MaskCornerRadius, FadeStart, FadeEnd) = (100d, 8d, 32d, 20d, 20d, 60d);
Style = SwipeActionStyle.EqualWidths;
ActionWidth = 64;
Spacing = 8;
ActionCornerRadius = 24;
MaskCornerRadius = 28;
FadeStart = 20;
FadeEnd = 80;
ReadyToExpandPadding = 50;
ReadyToTriggerPadding = 20;
MinimumPointToTrigger = 200;
break;
}
}

public void ApplySpring(string id)
{
var match = Springs.FirstOrDefault(s => s.Id == id);

if (match.Id is null)
SpringId = id;
var spring = Springs.FirstOrDefault(s => s.Id == id);
if (spring.Id is not null)
{
return;
Stiffness = spring.Stiffness;
Damping = spring.Damping;
}

SpringId = id;
Stiffness = match.Stiffness;
Damping = match.Damping;
}

public void MarkCustomSpring() => SpringId = "custom";
Expand Down
49 changes: 28 additions & 21 deletions samples/IOSwipeView.Demo/Layout/MainLayout.razor
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
@inject NavigationManager Navigation

<div class="min-h-dvh flex flex-col">
<header class="bg-grouped sticky top-0 z-50 border-b border-separator/70">
<header class="bg-grouped/90 backdrop-blur-md sticky top-0 z-50 border-b border-separator/70 transition-colors">
<div class="mx-auto max-w-6xl px-4 sm:px-6">
<div class="flex items-center gap-4 h-14">
<div class="flex items-center gap-3 sm:gap-4 h-14">
<a href="" class="flex items-center gap-2 shrink-0 no-underline">
<span class="grid place-items-center w-7 h-7 rounded-[8px] bg-sys-blue text-white text-caption1 font-bold">IO</span>
<span class="text-headline text-label font-bold">IOSwipeView</span>
<span class="grid place-items-center w-7 h-7 rounded-[8px] bg-sys-blue text-white text-caption1 font-bold shadow-xs">IO</span>
<span class="text-headline text-label font-bold tracking-tight">IOSwipeView</span>
</a>

<!-- DESKTOP FLUSH SEGMENTED NAVIGATION -->
<nav class="hidden md:flex items-center ml-2">
<div class="segmented-nav">
<nav class="hidden lg:flex items-center ml-2 min-w-0">
<div class="segmented-nav overflow-x-auto">
<NavLink class="nav-tab" ActiveClass="nav-tab-active" href="" Match="NavLinkMatch.All">Overview</NavLink>
<NavLink class="nav-tab" ActiveClass="nav-tab-active" href="swipeview" Match="NavLinkMatch.Prefix">SwipeView</NavLink>
<NavLink class="nav-tab" ActiveClass="nav-tab-active" href="swipelist" Match="NavLinkMatch.Prefix">SwipeList</NavLink>
Expand All @@ -31,7 +31,7 @@
@if (_samplesOpen)
{
<div class="fixed inset-0 z-40" @onclick="CloseSamplesMenu"></div>
<div class="absolute top-full left-0 mt-1.5 w-44 bg-grouped-secondary rounded-ios-lg shadow-xl border border-separator/60 p-1.5 z-50 flex flex-col space-y-0.5"
<div class="absolute top-full left-0 mt-1.5 w-48 bg-grouped-secondary rounded-ios-lg shadow-xl border border-separator/60 p-1.5 z-50 flex flex-col space-y-0.5"
@onclick="CloseSamplesMenu">
<NavLink class="flex items-center gap-2.5 px-3 py-2 text-footnote text-label hover:bg-fill-tertiary rounded-ios transition-colors no-underline"
href="samples/mail">
Expand Down Expand Up @@ -63,14 +63,14 @@

<div class="flex-1"></div>

<div class="flex items-center gap-2 shrink-0">
<div class="flex items-center gap-1.5 sm:gap-2 shrink-0">
<ThemeToggle />
<a href="https://github.com/iolitetech/IOSwipeView" target="_blank" rel="noopener"
class="hidden sm:grid place-items-center w-9 h-9 rounded-full bg-fill-tertiary text-label hover:bg-fill-secondary transition-colors"
aria-label="GitHub repository">
<svg viewBox="0 0 16 16" width="17" height="17" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82a7.4 7.4 0 0 1 2-.27c.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>
</a>
<button type="button" class="md:hidden grid place-items-center w-9 h-9 rounded-full bg-fill-tertiary text-label"
<button type="button" class="lg:hidden grid place-items-center w-9 h-9 rounded-full bg-fill-tertiary text-label hover:bg-fill-secondary transition-colors"
aria-label="Menu" @onclick="ToggleMenu">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 7h16M4 12h16M4 17h16"/></svg>
</button>
Expand All @@ -79,23 +79,30 @@

@if (_menuOpen)
{
<nav class="md:hidden pb-3 flex flex-col gap-1 border-t border-separator/50 pt-2" @onclick="CloseMenu">
<NavLink class="nav-tab text-left" ActiveClass="nav-tab-active" href="" Match="NavLinkMatch.All">Overview</NavLink>
<NavLink class="nav-tab text-left" ActiveClass="nav-tab-active" href="swipeview" Match="NavLinkMatch.Prefix">🎴 SwipeView</NavLink>
<NavLink class="nav-tab text-left" ActiveClass="nav-tab-active" href="swipelist" Match="NavLinkMatch.Prefix">📑 SwipeList</NavLink>
<NavLink class="nav-tab text-left" ActiveClass="nav-tab-active" href="styles" Match="NavLinkMatch.Prefix">Styles</NavLink>
<NavLink class="nav-tab text-left" ActiveClass="nav-tab-active" href="playground" Match="NavLinkMatch.Prefix">Playground</NavLink>
<NavLink class="nav-tab text-left" ActiveClass="nav-tab-active" href="samples/mail" Match="NavLinkMatch.Prefix">✉️ Mail</NavLink>
<NavLink class="nav-tab text-left" ActiveClass="nav-tab-active" href="samples/messages" Match="NavLinkMatch.Prefix">💬 Messages</NavLink>
<NavLink class="nav-tab text-left" ActiveClass="nav-tab-active" href="samples/reminders" Match="NavLinkMatch.Prefix">📋 Reminders</NavLink>
<NavLink class="nav-tab text-left" ActiveClass="nav-tab-active" href="samples/rtl" Match="NavLinkMatch.Prefix">🌐 RTL (کوردی)</NavLink>
<NavLink class="nav-tab text-left" ActiveClass="nav-tab-active" href="docs" Match="NavLinkMatch.Prefix">Docs</NavLink>
<nav class="lg:hidden pb-4 flex flex-col gap-1 border-t border-separator/50 pt-2" @onclick="CloseMenu">
<div class="grid grid-cols-2 sm:grid-cols-3 gap-1 mb-1">
<NavLink class="nav-tab text-center justify-center py-2" ActiveClass="nav-tab-active" href="" Match="NavLinkMatch.All">Overview</NavLink>
<NavLink class="nav-tab text-center justify-center py-2" ActiveClass="nav-tab-active" href="swipeview" Match="NavLinkMatch.Prefix">🎴 SwipeView</NavLink>
<NavLink class="nav-tab text-center justify-center py-2" ActiveClass="nav-tab-active" href="swipelist" Match="NavLinkMatch.Prefix">📑 SwipeList</NavLink>
<NavLink class="nav-tab text-center justify-center py-2" ActiveClass="nav-tab-active" href="styles" Match="NavLinkMatch.Prefix">🎨 Styles</NavLink>
<NavLink class="nav-tab text-center justify-center py-2" ActiveClass="nav-tab-active" href="playground" Match="NavLinkMatch.Prefix">🕹️ Playground</NavLink>
<NavLink class="nav-tab text-center justify-center py-2" ActiveClass="nav-tab-active" href="docs" Match="NavLinkMatch.Prefix">📖 Docs</NavLink>
</div>
<div class="border-t border-separator/40 pt-2 mt-1">
<p class="text-caption2 uppercase tracking-wider text-label-tertiary font-semibold px-2 mb-1">Interactive iOS Samples</p>
<div class="grid grid-cols-2 sm:grid-cols-4 gap-1">
<NavLink class="flex items-center gap-2 px-3 py-2 text-footnote text-label bg-grouped-secondary rounded-ios hover:bg-fill-tertiary transition-colors no-underline" href="samples/mail" Match="NavLinkMatch.Prefix">✉️ Mail</NavLink>
<NavLink class="flex items-center gap-2 px-3 py-2 text-footnote text-label bg-grouped-secondary rounded-ios hover:bg-fill-tertiary transition-colors no-underline" href="samples/messages" Match="NavLinkMatch.Prefix">💬 Messages</NavLink>
<NavLink class="flex items-center gap-2 px-3 py-2 text-footnote text-label bg-grouped-secondary rounded-ios hover:bg-fill-tertiary transition-colors no-underline" href="samples/reminders" Match="NavLinkMatch.Prefix">📋 Reminders</NavLink>
<NavLink class="flex items-center gap-2 px-3 py-2 text-footnote text-label bg-grouped-secondary rounded-ios hover:bg-fill-tertiary transition-colors no-underline" href="samples/rtl" Match="NavLinkMatch.Prefix">🌐 RTL</NavLink>
</div>
</div>
</nav>
}
</div>
</header>

<main class="flex-1 w-full mx-auto max-w-6xl px-4 sm:px-6 py-8">
<main class="flex-1 w-full mx-auto max-w-6xl px-3 sm:px-6 py-6 sm:py-8">
@Body
</main>

Expand Down
83 changes: 83 additions & 0 deletions samples/IOSwipeView.Demo/Pages/Playground.razor
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,24 @@
</button>
}
</div>

<!-- Live Spring ODE Trajectory Visualizer -->
<div class="bg-fill-tertiary/40 border border-separator/40 rounded-ios p-3">
<div class="flex items-center justify-between text-caption2 font-mono text-label-secondary mb-1.5">
<span class="font-semibold text-label">Spring Settling Curve: x(t)</span>
<span>ζ = @DampingRatio.ToString("0.00") · @SpringRegime</span>
</div>
<svg viewBox="0 0 300 65" class="w-full h-16 overflow-visible">
<!-- Baseline (Resting Position) -->
<line x1="0" y1="42" x2="300" y2="42" stroke="currentColor" stroke-dasharray="3,3" class="text-separator" stroke-width="1" />
<line x1="0" y1="40" x2="300" y2="40" stroke="currentColor" stroke-dasharray="2,4" class="text-separator/40" stroke-width="0.5" />
<line x1="0" y1="44" x2="300" y2="44" stroke="currentColor" stroke-dasharray="2,4" class="text-separator/40" stroke-width="0.5" />

<!-- Analytical ODE Spring Curve -->
<path d="@SpringSvgPath" fill="none" stroke="var(--sys-blue, #007aff)" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</div>

<div class="grid grid-cols-2 gap-4">
<SliderField Label="Stiffness" Unit="" Min="60" Max="600" Step="10" Value="_state.Stiffness"
ValueChanged="v => Set(() => { _state.Stiffness = v; _state.MarkCustomSpring(); })" />
Expand Down Expand Up @@ -207,6 +225,71 @@

private string SpringSubtitle => $"stiffness {_state.Stiffness:0} · damping {_state.Damping:0} · {_state.SpringId}";

private double DampingRatio => _state.Damping / (2.0 * Math.Sqrt(Math.Max(1.0, _state.Stiffness)));

private string SpringRegime => DampingRatio switch
{
< 0.99 => "Underdamped (Bouncy)",
<= 1.01 => "Critically Damped",
_ => "Overdamped (Smooth)"
};

private string SpringSvgPath
{
get
{
var k = Math.Max(1.0, _state.Stiffness);
var c = Math.Max(0.1, _state.Damping);
var w0 = Math.Sqrt(k);
var zeta = c / (2.0 * w0);
var sb = new System.Text.StringBuilder();

const int samples = 75;
const double totalTime = 1.0;
const double baselineY = 42.0;
const double initialDisplacement = 30.0; // peak start at Y=12

for (var i = 0; i <= samples; i++)
{
var t = (i / (double)samples) * totalTime;
var x = 0.0;

if (zeta < 1.0)
{
var wd = w0 * Math.Sqrt(1.0 - zeta * zeta);
x = Math.Exp(-zeta * w0 * t) * (Math.Cos(wd * t) + (zeta * w0 / wd) * Math.Sin(wd * t));
}
else if (Math.Abs(zeta - 1.0) < 0.001)
{
x = Math.Exp(-w0 * t) * (1.0 + w0 * t);
}
else
{
var gamma = w0 * Math.Sqrt(zeta * zeta - 1.0);
var r1 = -zeta * w0 + gamma;
var r2 = -zeta * w0 - gamma;
var c1 = r2 / (r2 - r1);
var c2 = 1.0 - c1;
x = c1 * Math.Exp(r1 * t) + c2 * Math.Exp(r2 * t);
}

var px = (i / (double)samples) * 300.0;
var py = baselineY - (initialDisplacement * x);

if (i == 0)
{
sb.Append(System.Globalization.CultureInfo.InvariantCulture, $"M {px:0.0} {py:0.0}");
}
else
{
sb.Append(System.Globalization.CultureInfo.InvariantCulture, $" L {px:0.0} {py:0.0}");
}
}

return sb.ToString();
}
}

protected override void OnInitialized() => _state.ApplyPreset("classic");

private void LogLeading(int index) => Log($"Leading · {PlaygroundState.LeadingNames[index]}");
Expand Down
2 changes: 1 addition & 1 deletion samples/IOSwipeView.Demo/wwwroot/css/app.css

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions src/IOSwipeView/Components/SwipeList/SwipeItemContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ internal SwipeItemContext(TItem item, SwipeList<TItem> list, int index, bool isF
public Task OpenAsync(SwipeSide side) => _list.OpenItemAsync(Item, side);

/// <summary>
/// Smoothly animates this row's height to zero with an iOS spring curve,
/// then invokes <see cref="SwipeList{TItem}.OnItemDeleted"/>.
/// Animates row collapse and removes the item from the list.
/// </summary>
public Task DeleteAsync() => _list.RemoveItemAnimatedAsync(Item);
}
1 change: 1 addition & 0 deletions src/IOSwipeView/Components/SwipeList/SwipeList.razor
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@

<div @key="key"
role="listitem"
aria-roledescription="swipeable item"
aria-selected="@(IsEditing ? (isSelected ? "true" : "false") : null)"
class="ioswipe-list__row-wrapper @(isCollapsing ? collapseClass : "") @(IsEditing ? "ioswipe-list__row-wrapper--editing" : "") @(ResolvedAnimateInsert ? "ioswipe-list__row-wrapper--enter" : "") @customRowClass">

Expand Down
Loading
Loading