|
1 | 1 | @page "/" |
| 2 | +@inject NavigationManager Nav |
2 | 3 |
|
3 | 4 | <PageTitle>{{BlakeSimpleTailwindSample}} - Home</PageTitle> |
4 | 5 |
|
|
42 | 43 | <!-- Recent Posts --> |
43 | 44 | <section> |
44 | 45 | <h3 class="text-2xl font-bold text-gray-900 mb-6">Recent Posts</h3> |
45 | | - <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> |
46 | | - @foreach (var post in _recentPosts) |
47 | | - { |
48 | | - <PostCard Page="@post" /> |
49 | | - } |
50 | | - </div> |
| 46 | + <div class="viewport"> |
| 47 | + <div class="rail @(_isAnimating ? "anim" : null)" style="transform: translateX(@_railX%)"> |
| 48 | + <div class="pane"> |
| 49 | + <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> |
| 50 | + @foreach (var post in _recentPosts) |
| 51 | + { |
| 52 | + <PostCard Page="@post"/> |
| 53 | + } |
| 54 | + </div> |
| 55 | + </div> |
| 56 | + <div class="pane"> |
| 57 | + <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> |
| 58 | + @foreach (var post in _incomingPosts) |
| 59 | + { |
| 60 | + <PostCard Page="@post"/> |
| 61 | + } |
| 62 | + </div> |
| 63 | + </div> |
| 64 | + </div> |
| 65 | + </div> |
51 | 66 | </section> |
52 | 67 |
|
53 | 68 | <!-- Pagination --> |
54 | 69 | @if (_pages.Count > 0) |
55 | 70 | { |
56 | 71 | <div class="flex justify-center mt-12"> |
57 | 72 | <div class="flex space-x-1"> |
58 | | - <a href="#" class="px-3 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-l-md hover:bg-gray-50" @onclick=PreviousPage>Previous</a> |
| 73 | + <button class="px-3 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-l-md hover:bg-gray-50" @onclick=PreviousPage>Previous</button> |
59 | 74 | @foreach (var item in _pages) |
60 | 75 | { |
61 | | - <a href="#" class="px-3 py-2 text-sm font-medium @(item == _currentPage ? "text-white bg-blue-600 border-blue-600" : "text-gray-700 bg-white border-gray-300 hover:bg-gray-50")" |
62 | | - @onclick="() => PageSelected(item)">@item</a> |
| 76 | + <button class="px-3 py-2 text-sm font-medium @(item == _currentPage ? "text-white bg-blue-600 border-blue-600" : "text-gray-700 bg-white border-gray-300 hover:bg-gray-50")" |
| 77 | + @onclick="() => PageSelected(item)">@item</button> |
63 | 78 | } |
64 | | - <a href="#" class="px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-r-md hover:bg-gray-50" @onclick=NextPage>Next</a> |
| 79 | + <button class="px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-r-md hover:bg-gray-50" @onclick=NextPage>Next</button> |
65 | 80 | </div> |
66 | 81 | </div> |
67 | 82 | } |
68 | 83 | </main> |
69 | 84 |
|
70 | 85 | <!-- Footer --> |
71 | | -<Footer /> |
| 86 | +<Footer></Footer> |
72 | 87 |
|
73 | 88 | @code { |
| 89 | + |
| 90 | + [SupplyParameterFromQuery] |
| 91 | + private int? Page { get; set; } = 1; |
| 92 | + |
74 | 93 | private PageModel? _featuredPost; |
75 | 94 |
|
76 | 95 | private List<PageModel> _recentPosts = []; |
77 | 96 |
|
| 97 | + private List<PageModel> _incomingPosts = []; |
| 98 | + |
78 | 99 | private List<int> _pages = []; |
79 | 100 |
|
80 | 101 | private int _currentPage = 1; |
81 | 102 |
|
| 103 | + bool _isAnimating; |
| 104 | + int _railX; // 0 or -100 (or +100 for prev) |
| 105 | +
|
82 | 106 | protected override void OnInitialized() |
83 | 107 | { |
84 | 108 | base.OnInitialized(); |
|
101 | 125 | _pages = Enumerable.Range(1, (int)Math.Ceiling((double)(pages.Count - 1) / 6)).ToList(); |
102 | 126 | } |
103 | 127 | } |
104 | | - } |
105 | 128 |
|
106 | | - private void PageSelected(int pageNumber) |
107 | | - { |
108 | | - _recentPosts = GeneratedContentIndex.GetPages() |
109 | | - .Where(p => !p.Draft && p.Slug.Contains("/posts/", StringComparison.OrdinalIgnoreCase)) |
110 | | - .OrderByDescending(p => p.Date) |
111 | | - .Skip((pageNumber - 1) * 6) |
112 | | - .Take(6) |
113 | | - .ToList(); |
| 129 | + if (Page is > 1) |
| 130 | + { |
| 131 | + _currentPage = Page.Value; |
| 132 | + GoToPage(_currentPage); |
| 133 | + } |
114 | 134 | } |
115 | 135 |
|
116 | | - private void NextPage() |
| 136 | + |
| 137 | + private async Task NextPage() |
117 | 138 | { |
118 | 139 | if (_currentPage < _pages.Count) |
119 | 140 | { |
120 | | - _currentPage++; |
121 | | - PageSelected(_currentPage); |
| 141 | + await PageSelected(_currentPage + 1); |
122 | 142 | } |
123 | 143 | } |
124 | 144 |
|
125 | | - private void PreviousPage() |
| 145 | + private async Task PreviousPage() |
126 | 146 | { |
127 | 147 | if (_currentPage > 1) |
128 | 148 | { |
129 | | - _currentPage--; |
130 | | - PageSelected(_currentPage); |
| 149 | + await PageSelected(_currentPage - 1); |
131 | 150 | } |
132 | 151 | } |
| 152 | + |
| 153 | + private async Task PageSelected(int pageNumber) |
| 154 | + { |
| 155 | + if (pageNumber > _currentPage - 1 || pageNumber < _currentPage + 1) |
| 156 | + { |
| 157 | + await GoToPageAsync(pageNumber); |
| 158 | + } |
| 159 | + else |
| 160 | + { |
| 161 | + GoToPage(pageNumber); |
| 162 | + } |
| 163 | + } |
| 164 | + |
| 165 | + private async Task GoToPageAsync(int pageId) |
| 166 | + { |
| 167 | + if (pageId == _currentPage || _isAnimating) return; |
| 168 | + |
| 169 | + _incomingPosts = GeneratedContentIndex.GetPages() |
| 170 | + .Where(p => !p.Draft && p.Slug.Contains("/posts/", StringComparison.OrdinalIgnoreCase)) |
| 171 | + .OrderByDescending(p => p.Date) |
| 172 | + .Skip((pageId - 1) * 6) |
| 173 | + .Take(6) |
| 174 | + .ToList(); |
| 175 | + |
| 176 | + _isAnimating = true; |
| 177 | + _railX = pageId > _currentPage ? -100 : 100; // slide direction |
| 178 | + StateHasChanged(); |
| 179 | + |
| 180 | + await Task.Delay(250); // match CSS |
| 181 | + _currentPage = pageId; |
| 182 | + _recentPosts = _incomingPosts; |
| 183 | + _incomingPosts = []; |
| 184 | + _railX = 0; _isAnimating = false; |
| 185 | + GoToPage(_currentPage); // update URL |
| 186 | + } |
| 187 | + |
| 188 | + private void GoToPage(int pageId) |
| 189 | + { |
| 190 | + _recentPosts = GeneratedContentIndex.GetPages() |
| 191 | + .Where(p => !p.Draft && p.Slug.Contains("/posts/", StringComparison.OrdinalIgnoreCase)) |
| 192 | + .OrderByDescending(p => p.Date) |
| 193 | + .Skip((pageId - 1) * 6) |
| 194 | + .Take(6) |
| 195 | + .ToList(); |
| 196 | + |
| 197 | + _isAnimating = false; |
| 198 | + _railX = 0; |
| 199 | + |
| 200 | + // TODO: forceLoad: false still navigates, just client-side. So we still get scroll to top. |
| 201 | + // Will need to either bypass that, or just use history.pushState in JSInterop. |
| 202 | + // SetQuery(pageId); |
| 203 | + } |
| 204 | + |
| 205 | + private void SetQuery(int page) |
| 206 | + { |
| 207 | + var uri = new Uri(Nav.Uri); |
| 208 | + var baseUri = uri.GetLeftPart(UriPartial.Path); |
| 209 | + var qs = System.Web.HttpUtility.ParseQueryString(uri.Query); |
| 210 | + qs["page"] = (page + 1).ToString(); |
| 211 | + |
| 212 | + var newUri = $"{baseUri}?{qs}"; |
| 213 | + Nav.NavigateTo(newUri, forceLoad: false); |
| 214 | + } |
133 | 215 | } |
0 commit comments