├── .gitattributes
├── .gitignore
├── Codecell.Blazor.Components.sln
├── Codecell.Component.Blazor
├── Codecell.Component.Blazor.csproj
├── Components
│ ├── PersianDatePickerComponent
│ │ ├── DateCellModel.cs
│ │ ├── PersianDateHelper.cs
│ │ ├── PersianDatePicker.razor
│ │ ├── PersianDatePicker.razor.cs
│ │ ├── PersianDatePicker.razor.css
│ │ └── PersianDatePicker.razor.scss
│ └── Shared
│ │ ├── CustomValidationMessage.razor
│ │ ├── CustomValidationMessage.razor.css
│ │ └── CustomValidationMessage.razor.scss
├── Infrastructure
│ ├── PersianDatePickerJsInterop.cs
│ └── ValidationMessageBase.cs
├── ServiceCollectionExtensions.cs
├── _Imports.razor
└── wwwroot
│ ├── background.png
│ ├── codecell.css
│ ├── codecell.scss
│ ├── imask.js
│ ├── iranyekanwebmediumfanum.woff
│ └── persianDatePicker.js
├── CodecellBlazorComponent.Sample
├── App.razor
├── CodecellBlazorComponent.Sample.csproj
├── Layout
│ ├── MainLayout.razor
│ ├── MainLayout.razor.css
│ ├── NavMenu.razor
│ └── NavMenu.razor.css
├── Pages
│ ├── Counter.razor
│ ├── Home.razor
│ └── Weather.razor
├── Program.cs
├── Properties
│ └── launchSettings.json
├── _Imports.razor
└── wwwroot
│ ├── css
│ ├── app.css
│ └── bootstrap
│ │ ├── bootstrap.min.css
│ │ └── bootstrap.min.css.map
│ ├── favicon.png
│ ├── icon-192.png
│ ├── icon-512.png
│ ├── index.html
│ ├── manifest.webmanifest
│ ├── sample-data
│ └── weather.json
│ ├── service-worker.js
│ └── service-worker.published.js
├── LICENSE.txt
├── README.md
├── logo.png
└── screenshots
├── dark.png
├── light.png
└── validation.png
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Oo]ut/
33 | [Ll]og/
34 | [Ll]ogs/
35 |
36 | # Visual Studio 2015/2017 cache/options directory
37 | .vs/
38 | # Uncomment if you have tasks that create the project's static files in wwwroot
39 | #wwwroot/
40 |
41 | # Visual Studio 2017 auto generated files
42 | Generated\ Files/
43 |
44 | # MSTest test Results
45 | [Tt]est[Rr]esult*/
46 | [Bb]uild[Ll]og.*
47 |
48 | # NUnit
49 | *.VisualState.xml
50 | TestResult.xml
51 | nunit-*.xml
52 |
53 | # Build Results of an ATL Project
54 | [Dd]ebugPS/
55 | [Rr]eleasePS/
56 | dlldata.c
57 |
58 | # Benchmark Results
59 | BenchmarkDotNet.Artifacts/
60 |
61 | # .NET Core
62 | project.lock.json
63 | project.fragment.lock.json
64 | artifacts/
65 |
66 | # ASP.NET Scaffolding
67 | ScaffoldingReadMe.txt
68 |
69 | # StyleCop
70 | StyleCopReport.xml
71 |
72 | # Files built by Visual Studio
73 | *_i.c
74 | *_p.c
75 | *_h.h
76 | *.ilk
77 | *.meta
78 | *.obj
79 | *.iobj
80 | *.pch
81 | *.pdb
82 | *.ipdb
83 | *.pgc
84 | *.pgd
85 | *.rsp
86 | *.sbr
87 | *.tlb
88 | *.tli
89 | *.tlh
90 | *.tmp
91 | *.tmp_proj
92 | *_wpftmp.csproj
93 | *.log
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio LightSwitch build output
298 | **/*.HTMLClient/GeneratedArtifacts
299 | **/*.DesktopClient/GeneratedArtifacts
300 | **/*.DesktopClient/ModelManifest.xml
301 | **/*.Server/GeneratedArtifacts
302 | **/*.Server/ModelManifest.xml
303 | _Pvt_Extensions
304 |
305 | # Paket dependency manager
306 | .paket/paket.exe
307 | paket-files/
308 |
309 | # FAKE - F# Make
310 | .fake/
311 |
312 | # CodeRush personal settings
313 | .cr/personal
314 |
315 | # Python Tools for Visual Studio (PTVS)
316 | __pycache__/
317 | *.pyc
318 |
319 | # Cake - Uncomment if you are using it
320 | # tools/**
321 | # !tools/packages.config
322 |
323 | # Tabs Studio
324 | *.tss
325 |
326 | # Telerik's JustMock configuration file
327 | *.jmconfig
328 |
329 | # BizTalk build output
330 | *.btp.cs
331 | *.btm.cs
332 | *.odx.cs
333 | *.xsd.cs
334 |
335 | # OpenCover UI analysis results
336 | OpenCover/
337 |
338 | # Azure Stream Analytics local run output
339 | ASALocalRun/
340 |
341 | # MSBuild Binary and Structured Log
342 | *.binlog
343 |
344 | # NVidia Nsight GPU debugger configuration file
345 | *.nvuser
346 |
347 | # MFractors (Xamarin productivity tool) working folder
348 | .mfractor/
349 |
350 | # Local History for Visual Studio
351 | .localhistory/
352 |
353 | # BeatPulse healthcheck temp database
354 | healthchecksdb
355 |
356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
357 | MigrationBackup/
358 |
359 | # Ionide (cross platform F# VS Code tools) working folder
360 | .ionide/
361 |
362 | # Fody - auto-generated XML schema
363 | FodyWeavers.xsd
--------------------------------------------------------------------------------
/Codecell.Blazor.Components.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.10.34928.147
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodecellBlazorComponent.Sample", "CodecellBlazorComponent.Sample\CodecellBlazorComponent.Sample.csproj", "{5FB5C980-E3B9-45D2-BAFA-90C0E7138F60}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Codecell.Component.Blazor", "Codecell.Component.Blazor\Codecell.Component.Blazor.csproj", "{95C6535D-8EE2-42E3-B46A-7F7CC1677F76}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {5FB5C980-E3B9-45D2-BAFA-90C0E7138F60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {5FB5C980-E3B9-45D2-BAFA-90C0E7138F60}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {5FB5C980-E3B9-45D2-BAFA-90C0E7138F60}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {5FB5C980-E3B9-45D2-BAFA-90C0E7138F60}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {95C6535D-8EE2-42E3-B46A-7F7CC1677F76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {95C6535D-8EE2-42E3-B46A-7F7CC1677F76}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {95C6535D-8EE2-42E3-B46A-7F7CC1677F76}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {95C6535D-8EE2-42E3-B46A-7F7CC1677F76}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {D60D590A-DB47-49A3-A357-6D48060BC766}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/Codecell.Component.Blazor/Codecell.Component.Blazor.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 | Cool Components for Blazor
8 | 0.2.7
9 | Sohrab Gheisari
10 | codecell.ir
11 | blazor,picker,persiandatePicker,datePicker
12 | Component Collection for Blazor
13 | logo.png
14 | README.md
15 | https://github.com/codecellir/Codecell.Blazor.Components
16 | https://github.com/codecellir/Codecell.Blazor.Components
17 | LICENSE.txt
18 |
19 |
20 |
21 |
22 | True
23 | \
24 |
25 |
26 | True
27 | \
28 |
29 |
30 | True
31 | \
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Codecell.Component.Blazor/Components/PersianDatePickerComponent/DateCellModel.cs:
--------------------------------------------------------------------------------
1 | namespace Codecell.Component.Blazor.Components.PersianDatePickerComponent;
2 |
3 | public class DateCellModel
4 | {
5 | public int Day { get; set; }
6 | public DateTime Date { get; set; }
7 | public bool Show => Day > 0;
8 | public string PersianDateFormat => PersianDate();
9 | string PersianDate()
10 | {
11 | if (Day <= 0)
12 | return string.Empty;
13 |
14 | return Date.ToPersianDate();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Codecell.Component.Blazor/Components/PersianDatePickerComponent/PersianDateHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 |
3 | namespace Codecell.Component.Blazor.Components.PersianDatePickerComponent;
4 |
5 | public static class PersianDateHelper
6 | {
7 | private static PersianCalendar pc = new();
8 | public static string[] WeekNames => ["ش", "ی", "د", "س", "چ", "پ", "ج"];
9 | public static int GetWeekSpan(this DayOfWeek week)
10 | {
11 | return week switch
12 | {
13 | DayOfWeek.Saturday => 0,
14 | DayOfWeek.Sunday => 1,
15 | DayOfWeek.Monday => 2,
16 | DayOfWeek.Tuesday => 3,
17 | DayOfWeek.Wednesday => 4,
18 | DayOfWeek.Thursday => 5,
19 | DayOfWeek.Friday => 6,
20 | _ => 0
21 | };
22 | }
23 | public static string GetMonthName(this int month) =>
24 | month switch
25 | {
26 | 1 => "فروردین",
27 | 2 => "اردیبهشت",
28 | 3 => "خرداد",
29 | 4 => "تیر",
30 | 5 => "مرداد",
31 | 6 => "شهریور",
32 | 7 => "مهر",
33 | 8 => "آبان",
34 | 9 => "آذر",
35 | 10 => "دی",
36 | 11 => "بهمن",
37 | 12 => "اسفند",
38 | _ => "نامشخص",
39 | };
40 | public static (int MonthNumber, string MonthName)[] GetMonths() =>
41 | [
42 | (1, "فروردین"),
43 | (2, "اردیبهشت"),
44 | (3, "خرداد"),
45 | (4, "تیر"),
46 | (5, "مرداد"),
47 | (6, "شهریور"),
48 | (7, "مهر"),
49 | (8, "آبان"),
50 | (9, "آذر"),
51 | (10, "دی"),
52 | (11, "بهمن"),
53 | (12, "اسفند")
54 | ];
55 | public static string ToPersianDate(this DateTime date)
56 | {
57 | var year = pc.GetYear(date);
58 | var month = pc.GetMonth(date);
59 | var day = pc.GetDayOfMonth(date);
60 | return $"{year}/{month.ToString("D2")}/{day.ToString("D2")}";
61 | }
62 | public static DateTime? ToGeorgianDate(this string persianDateText)
63 | {
64 |
65 | try
66 | {
67 | if (string.IsNullOrWhiteSpace(persianDateText))
68 | return null;
69 |
70 | var splited = persianDateText.Split('/');
71 |
72 | var year = int.Parse(splited[0]);
73 | var month = int.Parse(splited[1]);
74 | var day = int.Parse(splited[2]);
75 |
76 | return new DateTime(year, month, day, pc);
77 | }
78 | catch (Exception ex)
79 | {
80 | return null;
81 | }
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/Codecell.Component.Blazor/Components/PersianDatePickerComponent/PersianDatePicker.razor:
--------------------------------------------------------------------------------
1 |
2 |
3 |
23 |
24 |
25 |
26 |
32 |
33 |
34 |
35 | @for (int i = 1300; i <= 1500; i++)
36 | {
37 | var year = i;
38 |
GoToYear(year)">@i
39 | }
40 |
41 |
42 |
43 |
44 |
45 | @currentYear
46 |
47 |
48 |
‹
49 |
@fullMonthName
50 |
›
51 |
52 |
53 | @foreach (var name in PersianDateHelper.WeekNames)
54 | {
55 |
56 | @name
57 |
58 | }
59 | @foreach (var item in cells)
60 | {
61 | var dayClass = item.Show ? "day" : "";
62 | if (item.Show && selectedDate.HasValue && selectedDate.Value.Date == item.Date.Date)
63 | dayClass = $"{dayClass} current";
64 |
SelectDate(item.Date)">
65 | @if (item.Show)
66 | {
67 |
@item.Day
68 | }
69 |
70 | }
71 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/Codecell.Component.Blazor/Components/PersianDatePickerComponent/PersianDatePicker.razor.cs:
--------------------------------------------------------------------------------
1 | using Codecell.Component.Blazor.Components.Shared;
2 | using Microsoft.AspNetCore.Components;
3 | using Microsoft.AspNetCore.Components.Web;
4 | using Microsoft.JSInterop;
5 | using System.Linq.Expressions;
6 | using System.Xml.Linq;
7 |
8 | namespace Codecell.Component.Blazor.Components.PersianDatePickerComponent;
9 |
10 | public partial class PersianDatePicker : IDisposable
11 | {
12 | [Inject] public PersianDatePickerJsInterop JsInterop { get; set; }
13 |
14 | [Parameter] public bool DarkMode { get; set; }
15 | [Parameter] public bool Immediate { get; set; }
16 | [Parameter] public string PlaceHolder { get; set; }
17 | [Parameter] public string Label { get; set; } = "تاریخ";
18 | [Parameter] public DateTime? Date { get; set; }
19 | [Parameter] public Expression> For { get; set; }
20 | [Parameter] public EventCallback DateChanged { get; set; }
21 | [Parameter] public EventCallback ValueChanged { get; set; }
22 |
23 | DotNetObjectReference? objRef;
24 | CustomValidationMessage validation;
25 | DateTime? selectedDate;
26 | System.Globalization.PersianCalendar pc = new();
27 | List cells = new();
28 |
29 | private string ComponentId = "codecell_persian_date_picker_component";
30 | private string InputId = "codecell-p-date";
31 | string componentClass = "persian-date-input";
32 | string pickerClass = "persian-date-wrapper d-none";
33 | string monthClass = "month-select d-none";
34 | string yearClass = "year-select d-none";
35 | string calendarClass = "calendar d-none";
36 | string persianDateFormat = string.Empty;
37 | string fullMonthName = string.Empty;
38 | int currentMonth;
39 | int currentYear;
40 | int currentDay;
41 | bool inMode;
42 | protected override void OnInitialized()
43 | {
44 | ComponentId = $"{ComponentId}_{Guid.NewGuid()}";
45 | InputId = $"{InputId}_{Guid.NewGuid()}";
46 | objRef = DotNetObjectReference.Create(this);
47 |
48 | var intitialDate = Date;
49 |
50 | Clear();
51 |
52 | if (intitialDate.HasValue)
53 | {
54 | SetPersianFormatText(intitialDate.Value);
55 | Date = intitialDate;
56 | DateChanged.InvokeAsync(Date);
57 | }
58 |
59 | if (DarkMode)
60 | {
61 | componentClass = "persian-date-input dark";
62 | }
63 | }
64 |
65 | protected override async Task OnAfterRenderAsync(bool firstRender)
66 | {
67 | if (firstRender)
68 | {
69 | await JsInterop.AddOutSideClickHandler(ComponentId, objRef);
70 | await JsInterop.AddDateMask(InputId, objRef);
71 | }
72 | else if (Date.HasValue)
73 | SetPersianFormatText(Date.Value);
74 | else if(!inMode)
75 | ClearWithoutInvoke();
76 |
77 | }
78 |
79 |
80 | [JSInvokable]
81 | public void InvokeClickOutside()
82 | {
83 | pickerClass = "persian-date-wrapper d-none";
84 | monthClass = "month-select d-none";
85 | yearClass = "year-select d-none";
86 | calendarClass = "calendar d-none";
87 | StateHasChanged();
88 | }
89 |
90 | [JSInvokable]
91 | public async Task InvokeMakComplete()
92 | {
93 | var persiandateValue = await JsInterop.GetValue(InputId);
94 | var date = persiandateValue.ToGeorgianDate();
95 |
96 | if (date.HasValue)
97 | {
98 | SelectDate(date.Value);
99 | StateHasChanged();
100 | }
101 |
102 | }
103 | void OnKeyupHandler(KeyboardEventArgs e)
104 | {
105 | var date = persianDateFormat.ToGeorgianDate();
106 |
107 | if (!date.HasValue)
108 | {
109 | Date = null;
110 | DateChanged.InvokeAsync(Date);
111 | ValueChanged.InvokeAsync(Date);
112 | StateHasChanged();
113 | }
114 | }
115 | void OnKeydownHandler(KeyboardEventArgs e)
116 | {
117 | if (e.CtrlKey && e.Key == "Enter" && !Date.HasValue)
118 | {
119 | SelectDate(DateTime.Now);
120 | }
121 | }
122 | void GoToToday() => SelectDate(DateTime.Now);
123 | void PrepareCells(DateTime date)
124 | {
125 | cells = new();
126 | Date = date;
127 | currentYear = pc.GetYear(date);
128 | currentMonth = pc.GetMonth(date);
129 | currentDay = pc.GetDayOfMonth(date);
130 | var isLeapYear = pc.IsLeapYear(currentYear);
131 |
132 | fullMonthName = $"{currentMonth.GetMonthName()} {currentYear}";
133 |
134 | var firstDayOfDate = new DateTime(currentYear, currentMonth, 1, pc);
135 | int weekSpan = firstDayOfDate.DayOfWeek.GetWeekSpan();
136 | var maxDay = currentMonth <= 6 ? 31 : 30;
137 | if (!isLeapYear && currentMonth == 12 && maxDay == 30)
138 | {
139 | maxDay = 29;
140 | }
141 | for (int i = 1; i <= weekSpan; i++)
142 | {
143 | cells.Add(new DateCellModel
144 | {
145 | Day = 0
146 | });
147 | }
148 |
149 | for (int i = 1; i <= maxDay; i++)
150 | {
151 | cells.Add(new DateCellModel
152 | {
153 | Day = i,
154 | Date = firstDayOfDate.AddDays(i - 1).Date
155 | });
156 | }
157 |
158 | var remain = 42 - cells.Count();
159 |
160 | if (remain > 0)
161 | {
162 | for (int i = 1; i <= remain; i++)
163 | {
164 | cells.Add(new DateCellModel
165 | {
166 | Day = 0
167 | });
168 | }
169 | }
170 | }
171 | void SelectDate(DateTime date)
172 | {
173 | inMode = false;
174 | selectedDate = Date = date;
175 | pickerClass = "persian-date-wrapper d-none";
176 | calendarClass = "calendar d-none";
177 | DateChanged.InvokeAsync(selectedDate);
178 | ValueChanged.InvokeAsync(selectedDate);
179 | SetPersianFormatText(Date.Value);
180 | if (Immediate && validation is not null)
181 | {
182 | validation.Immediate();
183 | }
184 | }
185 | void PrevMonth()
186 | {
187 | inMode = true;
188 | pickerClass = "persian-date-wrapper";
189 | calendarClass = "calendar";
190 | if (currentMonth > 1)
191 | {
192 | currentMonth--;
193 | }
194 | else if (currentMonth == 1)
195 | {
196 | currentMonth = 12;
197 | currentYear--;
198 | }
199 | if (currentMonth == 12 && !pc.IsLeapYear(currentYear) && currentDay > 29)
200 | currentDay = 1;
201 |
202 | var date = new DateTime(currentYear, currentMonth, currentDay, pc);
203 | PrepareCells(date);
204 | }
205 | void NextMonth()
206 | {
207 | inMode = true;
208 | pickerClass = "persian-date-wrapper";
209 | calendarClass = "calendar";
210 | if (currentMonth < 12)
211 | {
212 | currentMonth++;
213 | }
214 | else if (currentMonth == 12)
215 | {
216 | currentMonth = 1;
217 | currentYear++;
218 | }
219 | var date = new DateTime(currentYear, currentMonth, currentDay, pc);
220 | if (currentMonth == 12 && !pc.IsLeapYear(currentYear) && currentDay > 29)
221 | currentDay = 1;
222 | PrepareCells(date);
223 | }
224 | void MonthMode()
225 | {
226 | inMode = true;
227 | monthClass = "month-select";
228 | pickerClass = "persian-date-wrapper";
229 | calendarClass = "calendar d-none";
230 | }
231 | void YearMode()
232 | {
233 | inMode = true;
234 | yearClass = "year-select";
235 | pickerClass = "persian-date-wrapper";
236 | calendarClass = "calendar d-none";
237 | }
238 | void GoToMonth(int month)
239 | {
240 | currentMonth = month;
241 | if (currentMonth == 12 && !pc.IsLeapYear(currentYear) && currentDay > 29)
242 | currentDay = 1;
243 |
244 | var date = new DateTime(currentYear, currentMonth, currentDay, pc);
245 | monthClass = "month-select d-none";
246 | calendarClass = "calendar";
247 | PrepareCells(date);
248 |
249 | }
250 | void GoToYear(int year)
251 | {
252 | currentYear = year;
253 | if (currentMonth == 12 && !pc.IsLeapYear(currentYear) && currentDay > 29)
254 | currentDay = 1;
255 |
256 | var date = new DateTime(currentYear, currentMonth, currentDay, pc);
257 | yearClass = "year-select d-none";
258 | calendarClass = "calendar";
259 | PrepareCells(date);
260 |
261 | }
262 | void OpenPicker()
263 | {
264 | if (pickerClass.Equals("persian-date-wrapper"))
265 | {
266 | pickerClass = "persian-date-wrapper d-none";
267 | calendarClass = "calendar d-none";
268 | return;
269 | }
270 | selectedDate = Date.HasValue ? Date : DateTime.Now;
271 | PrepareCells(selectedDate.Value);
272 | pickerClass = "persian-date-wrapper";
273 | calendarClass = "calendar";
274 | }
275 | void SetPersianFormatText(DateTime date)
276 | {
277 | currentYear = pc.GetYear(date);
278 | currentMonth = pc.GetMonth(date);
279 | currentDay = pc.GetDayOfMonth(date);
280 | persianDateFormat = $"{currentYear}/{currentMonth.ToString("D2")}/{currentDay.ToString("D2")}";
281 | }
282 | void Clear()
283 | {
284 | Date = selectedDate = null;
285 | persianDateFormat = "1___/__/__";
286 | pickerClass = "persian-date-wrapper d-none";
287 | calendarClass = "calendar d-none";
288 | monthClass = "month-select d-none";
289 | yearClass = "year-select d-none";
290 | DateChanged.InvokeAsync(null);
291 | ValueChanged.InvokeAsync(null);
292 | JsInterop.ResetMask(InputId);
293 | }
294 | void ClearWithoutInvoke()
295 | {
296 | Date = selectedDate = null;
297 | persianDateFormat = "1___/__/__";
298 | pickerClass = "persian-date-wrapper d-none";
299 | calendarClass = "calendar d-none";
300 | monthClass = "month-select d-none";
301 | yearClass = "year-select d-none";
302 | }
303 | void OnValidationChanged(bool status)
304 | {
305 | var hasError = !status;
306 |
307 | componentClass = hasError ? $"persian-date-input error" : "persian-date-input";
308 | }
309 | public void Dispose()
310 | {
311 | JsInterop.RemoveOutSideClickHandler(ComponentId);
312 | }
313 | }
314 |
--------------------------------------------------------------------------------
/Codecell.Component.Blazor/Components/PersianDatePickerComponent/PersianDatePicker.razor.css:
--------------------------------------------------------------------------------
1 | .persian-date-input{position:relative;font-family:IRANYekan}.persian-date-input .input-wrapper{position:relative;display:flex;flex-flow:column}.persian-date-input .input-wrapper .label{direction:rtl;padding-right:7px;margin-bottom:2px;font-size:13px;color:#5b5959}.persian-date-input .input-wrapper .p-date{font-size:14px;text-align:right;padding-right:10px;border:1px solid #ced4da;line-height:30px;color:#212529;background-color:#fff;border-radius:4px;cursor:pointer;direction:rtl;width:100%}.persian-date-input .input-wrapper .p-date:focus-visible{outline-offset:2px;outline-color:#0d6efd}.persian-date-input .input-wrapper .clear{position:absolute;width:25px;height:25px;display:flex;justify-content:center;align-items:center;text-decoration:none;font-size:14px;top:28px;left:3px;color:#bbb8b8;font-weight:100}.persian-date-input .input-wrapper .clear:hover{cursor:pointer}.persian-date-input.error .input-wrapper .p-date{border-color:#f94949}.persian-date-input.error .input-wrapper .p-date:focus-visible{outline-offset:2px;outline-color:#f94949}.persian-date-input .persian-date-wrapper{position:absolute;right:0;z-index:999;background:#fff;width:260px;direction:rtl;display:flex;justify-content:center;flex-flow:column;padding:7px;border:1px solid #dcdcdc}.persian-date-input .persian-date-wrapper .month-select{display:grid;grid-template-columns:70px 70px 70px;grid-column-gap:7px;direction:rtl}.persian-date-input .persian-date-wrapper .month-select a{width:60px;height:40px;display:flex;justify-content:center;align-items:center}.persian-date-input .persian-date-wrapper .month-select a:hover{border:1px solid #dcdcdc;border-radius:4px;background:#dcdcdc;cursor:pointer}.persian-date-input .persian-date-wrapper .month-select>*{direction:ltr}.persian-date-input .persian-date-wrapper .year-select{display:flex;justify-content:start;gap:10px;flex-wrap:wrap;max-height:280px;overflow-y:auto}.persian-date-input .persian-date-wrapper .year-select a{width:40px;height:30px;display:flex;justify-content:center;align-items:center}.persian-date-input .persian-date-wrapper .year-select a:hover{border:1px solid #dcdcdc;border-radius:4px;background:#dcdcdc;cursor:pointer}.persian-date-input .persian-date-wrapper .year{display:flex;justify-content:center;align-items:center;height:25px}.persian-date-input .persian-date-wrapper .year span{font-size:18px;font-weight:bold;cursor:pointer}.persian-date-input .persian-date-wrapper .month{display:flex;justify-content:space-between;align-items:center;height:32px}.persian-date-input .persian-date-wrapper .month span{cursor:pointer}.persian-date-input .persian-date-wrapper .month .navigation{width:35px;height:35px;display:flex;justify-content:center;align-items:center;text-decoration:none;font-size:18px}.persian-date-input .persian-date-wrapper .month .navigation:hover{border:1px solid #dcdcdc;border-radius:17px;background:#dcdcdc;cursor:pointer}.persian-date-input .persian-date-wrapper .week{display:grid;grid-template-columns:32px 32px 32px 32px 32px 32px 32px;grid-column-gap:3px;direction:rtl;margin-top:10px}.persian-date-input .persian-date-wrapper .week .day-name{font-size:14px;font-weight:bold;display:flex;justify-content:center;align-items:center;width:30px;width:30px}.persian-date-input .persian-date-wrapper .week .day{display:flex;justify-content:center;align-items:center;width:30px;height:30px;font-size:14px}.persian-date-input .persian-date-wrapper .week .day:hover{background:#dcdcdc;cursor:pointer;border-radius:15px}.persian-date-input .persian-date-wrapper .week .day.current{background-color:#46cc46;border-radius:15px;color:#fff;font-weight:bold}.persian-date-input .persian-date-wrapper .week .today{width:245px;display:flex;justify-content:center;align-items:center}.persian-date-input .persian-date-wrapper .week .today a{width:60px;height:35px;display:flex;justify-content:center;align-items:center;background:#1dba85;cursor:pointer;color:#000;border-radius:6px}.persian-date-input .persian-date-wrapper .week>*{direction:ltr}.persian-date-input.dark .input-wrapper .label{color:#fff}.persian-date-input.dark .persian-date-wrapper{color:#fff;background:#17212b}.persian-date-input.dark .persian-date-wrapper .month .navigation{background:#fff;border-radius:17px}.persian-date-input.dark .persian-date-wrapper .month .navigation:hover{border:1px solid #fff}.persian-date-input.dark .persian-date-wrapper .month-select a:hover,.persian-date-input.dark .persian-date-wrapper .year-select a:hover{background:#fff;color:#000}.persian-date-input.dark .persian-date-wrapper .week .day:hover{background:#fff;color:#000}.d-none{display:none}
--------------------------------------------------------------------------------
/Codecell.Component.Blazor/Components/PersianDatePickerComponent/PersianDatePicker.razor.scss:
--------------------------------------------------------------------------------
1 |
2 | .persian-date-input {
3 | position: relative;
4 | font-family: IRANYekan;
5 |
6 | .input-wrapper {
7 | position: relative;
8 | display: flex;
9 | flex-flow: column;
10 |
11 | .label {
12 | direction: rtl;
13 | padding-right: 7px;
14 | margin-bottom: 2px;
15 | font-size: 13px;
16 | color: #5b5959;
17 | }
18 |
19 | .p-date {
20 | font-size: 14px;
21 | text-align: right;
22 | padding-right: 10px;
23 | border: 1px solid #ced4da;
24 | line-height: 30px;
25 | color: #212529;
26 | background-color: #fff;
27 | border-radius: 4px;
28 | cursor: pointer;
29 | direction: rtl;
30 | width: 100%;
31 |
32 | &:focus-visible {
33 | outline-offset: 2px;
34 | outline-color: #0d6efd;
35 | }
36 | }
37 |
38 | .clear {
39 | position: absolute;
40 | width: 25px;
41 | height: 25px;
42 | display: flex;
43 | justify-content: center;
44 | align-items: center;
45 | text-decoration: none;
46 | font-size: 14px;
47 | top: 28px;
48 | left: 3px;
49 | color: #bbb8b8;
50 | font-weight: 100;
51 |
52 | &:hover {
53 | cursor: pointer;
54 | }
55 | }
56 | }
57 |
58 | &.error {
59 | .input-wrapper {
60 | .p-date {
61 | border-color: #f94949;
62 |
63 | &:focus-visible {
64 | outline-offset: 2px;
65 | outline-color: #f94949;
66 | }
67 | }
68 | }
69 | }
70 |
71 | .persian-date-wrapper {
72 | position: absolute;
73 | right: 0;
74 | z-index: 999;
75 | background: #fff;
76 | width: 260px;
77 | direction: rtl;
78 | display: flex;
79 | justify-content: center;
80 | flex-flow: column;
81 | padding: 7px;
82 | border: 1px solid #dcdcdc;
83 |
84 | .month-select {
85 | display: grid;
86 | grid-template-columns: 70px 70px 70px;
87 | grid-column-gap: 7px;
88 | direction: rtl;
89 |
90 | a {
91 | width: 60px;
92 | height: 40px;
93 | display: flex;
94 | justify-content: center;
95 | align-items: center;
96 |
97 | &:hover {
98 | border: 1px solid #dcdcdc;
99 | border-radius: 4px;
100 | background: #dcdcdc;
101 | cursor: pointer;
102 | }
103 | }
104 | }
105 |
106 | .month-select > * {
107 | direction: ltr;
108 | }
109 |
110 | .year-select {
111 | display: flex;
112 | justify-content: start;
113 | gap: 10px;
114 | flex-wrap: wrap;
115 | max-height: 280px;
116 | overflow-y: auto;
117 |
118 | a {
119 | width: 40px;
120 | height: 30px;
121 | display: flex;
122 | justify-content: center;
123 | align-items: center;
124 |
125 | &:hover {
126 | border: 1px solid #dcdcdc;
127 | border-radius: 4px;
128 | background: #dcdcdc;
129 | cursor: pointer;
130 | }
131 | }
132 | }
133 |
134 | .year {
135 | display: flex;
136 | justify-content: center;
137 | align-items: center;
138 | height: 25px;
139 |
140 | span {
141 | font-size: 18px;
142 | font-weight: bold;
143 | cursor: pointer;
144 | }
145 | }
146 |
147 | .month {
148 | display: flex;
149 | justify-content: space-between;
150 | align-items: center;
151 | height: 32px;
152 |
153 | span {
154 | cursor: pointer;
155 | }
156 |
157 | .navigation {
158 | width: 35px;
159 | height: 35px;
160 | display: flex;
161 | justify-content: center;
162 | align-items: center;
163 | text-decoration: none;
164 | font-size: 18px;
165 |
166 | &:hover {
167 | border: 1px solid #dcdcdc;
168 | border-radius: 17px;
169 | background: #dcdcdc;
170 | cursor: pointer;
171 | }
172 | }
173 | }
174 |
175 | .week {
176 | display: grid;
177 | grid-template-columns: 32px 32px 32px 32px 32px 32px 32px;
178 | grid-column-gap: 3px;
179 | direction: rtl;
180 | margin-top: 10px;
181 |
182 | .day-name {
183 | font-size: 14px;
184 | font-weight: bold;
185 | display: flex;
186 | justify-content: center;
187 | align-items: center;
188 | width: 30px;
189 | width: 30px;
190 | }
191 |
192 | .day {
193 | display: flex;
194 | justify-content: center;
195 | align-items: center;
196 | width: 30px;
197 | height: 30px;
198 | font-size: 14px;
199 |
200 | &:hover {
201 | background: #dcdcdc;
202 | cursor: pointer;
203 | border-radius: 15px;
204 | }
205 |
206 | &.current {
207 | background-color: #46cc46;
208 | border-radius: 15px;
209 | color: #fff;
210 | font-weight: bold;
211 | }
212 | }
213 |
214 | .today {
215 | width: 245px;
216 | display: flex;
217 | justify-content: center;
218 | align-items: center;
219 |
220 | a {
221 | width: 60px;
222 | height: 35px;
223 | display: flex;
224 | justify-content: center;
225 | align-items: center;
226 | background: #1dba85;
227 | cursor: pointer;
228 | color: #000;
229 | border-radius: 6px;
230 | }
231 | }
232 | }
233 |
234 | .week > * {
235 | direction: ltr;
236 | }
237 | }
238 |
239 | &.dark {
240 | .input-wrapper {
241 |
242 | .label {
243 | color: #fff;
244 | }
245 | }
246 |
247 | .persian-date-wrapper {
248 | color: #fff;
249 | background: #17212b;
250 |
251 | .month {
252 | .navigation {
253 | background: #fff;
254 | border-radius: 17px;
255 |
256 | &:hover {
257 | border: 1px solid #fff;
258 | }
259 | }
260 | }
261 |
262 | .month-select, .year-select {
263 | a {
264 | &:hover {
265 | background: #fff;
266 | color: #000;
267 | }
268 | }
269 | }
270 |
271 | .week {
272 | .day {
273 | &:hover {
274 | background: #fff;
275 | color: #000;
276 | }
277 | }
278 |
279 | .today {
280 | a {
281 | }
282 | }
283 | }
284 | }
285 | }
286 | }
287 |
288 | .d-none {
289 | display: none;
290 | }
291 |
--------------------------------------------------------------------------------
/Codecell.Component.Blazor/Components/Shared/CustomValidationMessage.razor:
--------------------------------------------------------------------------------
1 | @typeparam TValue
2 | @inherits ValidationMessageBase
3 |
4 | @foreach (var message in ValidationMessages)
5 | {
6 | @message
7 | }
--------------------------------------------------------------------------------
/Codecell.Component.Blazor/Components/Shared/CustomValidationMessage.razor.css:
--------------------------------------------------------------------------------
1 | .codecell-input-error{color:#f94949;font-size:13px;margin:0;padding-top:4px}
--------------------------------------------------------------------------------
/Codecell.Component.Blazor/Components/Shared/CustomValidationMessage.razor.scss:
--------------------------------------------------------------------------------
1 | .codecell-input-error {
2 | color: #f94949;
3 | font-size: 13px;
4 | margin: 0;
5 | padding-top: 4px;
6 | }
7 |
--------------------------------------------------------------------------------
/Codecell.Component.Blazor/Infrastructure/PersianDatePickerJsInterop.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.JSInterop;
2 |
3 | namespace Codecell.Component.Blazor
4 | {
5 |
6 | public class PersianDatePickerJsInterop : IAsyncDisposable
7 | {
8 | private readonly Lazy> moduleTask;
9 |
10 | public PersianDatePickerJsInterop(IJSRuntime jsRuntime)
11 | {
12 | moduleTask = new(() => jsRuntime.InvokeAsync(
13 | "import", "./_content/Codecell.Component.Blazor/persianDatePicker.js").AsTask());
14 | }
15 |
16 | public async Task AddOutSideClickHandler(string elemntId, object dotNetObject)
17 | {
18 | var module = await moduleTask.Value;
19 | await module.InvokeVoidAsync("addOutSideClickHandler", elemntId, dotNetObject);
20 | }
21 | public async Task RemoveOutSideClickHandler(string elemntId)
22 | {
23 | var module = await moduleTask.Value;
24 | await module.InvokeVoidAsync("removeOutSideClickHandler", elemntId);
25 | }
26 | public async Task AddDateMask(string elemntId, object dotNetObject)
27 | {
28 | var module = await moduleTask.Value;
29 | await module.InvokeVoidAsync("dateMask", elemntId, dotNetObject);
30 | }
31 | public async Task ResetMask(string elemntId)
32 | {
33 | var module = await moduleTask.Value;
34 | await module.InvokeVoidAsync("resetMask", elemntId);
35 | }
36 | public async Task GetValue(string elemntId)
37 | {
38 | var module = await moduleTask.Value;
39 | return await module.InvokeAsync("getValue", elemntId);
40 | }
41 |
42 | public async ValueTask DisposeAsync()
43 | {
44 | if (moduleTask.IsValueCreated)
45 | {
46 | var module = await moduleTask.Value;
47 | await module.DisposeAsync();
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Codecell.Component.Blazor/Infrastructure/ValidationMessageBase.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Components;
2 | using Microsoft.AspNetCore.Components.Forms;
3 | using System.Linq.Expressions;
4 |
5 | namespace Codecell.Component.Blazor.Infrastructure;
6 |
7 | public class ValidationMessageBase : ComponentBase, IDisposable
8 | {
9 |
10 | [CascadingParameter]
11 | public EditContext EditContext { get; set; }
12 |
13 | [Parameter]
14 | public Expression> For { get; set; }
15 |
16 | [Parameter]
17 | public EventCallback OnValidationChanged { get; set; }
18 |
19 | private FieldIdentifier _fieldIdentifier;
20 |
21 | protected IEnumerable ValidationMessages => EditContext.GetValidationMessages(_fieldIdentifier);
22 | protected override void OnInitialized()
23 | {
24 | _fieldIdentifier = FieldIdentifier.Create(For);
25 | EditContext.OnValidationStateChanged += HandleValidationStateChanged;
26 | }
27 |
28 | private void HandleValidationStateChanged(object o, ValidationStateChangedEventArgs args)
29 | {
30 | var status = EditContext.IsValid(_fieldIdentifier);
31 | OnValidationChanged.InvokeAsync(status);
32 | StateHasChanged();
33 | }
34 |
35 | public void Dispose() => EditContext.OnValidationStateChanged -= HandleValidationStateChanged;
36 |
37 | public void Immediate() => EditContext.Validate();
38 | }
39 |
--------------------------------------------------------------------------------
/Codecell.Component.Blazor/ServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 |
3 | namespace Codecell.Component.Blazor
4 | {
5 | public static class ServiceCollectionExtensions
6 | {
7 | public static IServiceCollection AddCodecellBlazor(this IServiceCollection services)
8 | {
9 | if (services == null)
10 | {
11 | throw new ArgumentNullException(nameof(services));
12 | }
13 | services.AddScoped();
14 | return services;
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Codecell.Component.Blazor/_Imports.razor:
--------------------------------------------------------------------------------
1 | @using Microsoft.AspNetCore.Components.Web
2 | @using Codecell.Component.Blazor.Components.PersianDatePickerComponent
3 | @using Codecell.Component.Blazor.Components.Shared
4 | @using Codecell.Component.Blazor.Infrastructure
5 |
--------------------------------------------------------------------------------
/Codecell.Component.Blazor/wwwroot/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codecellir/Codecell.Blazor.Components/98ea8d8edb51a1333c36eb79dbfae2ea90767d47/Codecell.Component.Blazor/wwwroot/background.png
--------------------------------------------------------------------------------
/Codecell.Component.Blazor/wwwroot/codecell.css:
--------------------------------------------------------------------------------
1 | @font-face{font-family:iranyekan;font-style:normal;font-weight:500;font-display:swap;src:url("./iranyekanwebmediumfanum.woff") format("woff")}html,body{margin:0;padding:0;font-family:"iranyekan";font-size:14px;word-spacing:-1px;height:100%}
--------------------------------------------------------------------------------
/Codecell.Component.Blazor/wwwroot/codecell.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: iranyekan;
3 | font-style: normal;
4 | font-weight: 500;
5 | font-display: swap;
6 | src: url('./iranyekanwebmediumfanum.woff') format('woff');
7 | }
8 |
9 |
10 | html, body {
11 | margin: 0;
12 | padding: 0;
13 | font-family: 'iranyekan';
14 | font-size: 14px;
15 | word-spacing: -1px;
16 | height: 100%;
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/Codecell.Component.Blazor/wwwroot/iranyekanwebmediumfanum.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codecellir/Codecell.Blazor.Components/98ea8d8edb51a1333c36eb79dbfae2ea90767d47/Codecell.Component.Blazor/wwwroot/iranyekanwebmediumfanum.woff
--------------------------------------------------------------------------------
/Codecell.Component.Blazor/wwwroot/persianDatePicker.js:
--------------------------------------------------------------------------------
1 |
2 | import './imask.js';
3 |
4 | const outsideClickHandlers = new Map();
5 | const masks = new Map();
6 |
7 | export function addOutSideClickHandler(elementId, dotnetHelper)
8 | {
9 | const handler = (e) =>
10 | {
11 | if (!document.getElementById(elementId).contains(e.target))
12 | {
13 | dotnetHelper.invokeMethodAsync("InvokeClickOutside");
14 | }
15 | };
16 |
17 | window.addEventListener("click", handler);
18 | outsideClickHandlers.set(elementId, handler);
19 | }
20 |
21 | export function removeOutSideClickHandler(elementId)
22 | {
23 | const handler = outsideClickHandlers.get(elementId);
24 |
25 | if (handler)
26 | {
27 | window.removeEventListener("click", handler);
28 | outsideClickHandlers.delete(elementId);
29 | }
30 | }
31 | export function dateMask(elementId, dotnetHelper)
32 | {
33 | var mask = IMask(document.getElementById(elementId), {
34 | mask: 'YYYY/MM/DD',
35 | lazy: false,
36 | placeholderChar: '_',
37 | blocks: {
38 | YYYY: {
39 | mask: IMask.MaskedRange,
40 | from: 1300,
41 | to: 1500,
42 | } ,
43 | MM: {
44 | mask: IMask.MaskedRange,
45 | from: 1,
46 | to: 12,
47 | maxLength: 2,
48 | },
49 | DD: {
50 | mask: IMask.MaskedRange,
51 | from: 1,
52 | to: 31,
53 | maxLength: 2,
54 | },
55 | },
56 | })
57 |
58 | mask.on('complete', () =>
59 | {
60 | dotnetHelper.invokeMethodAsync("InvokeMakComplete");
61 | });
62 |
63 | masks.set(elementId, mask);
64 | }
65 |
66 | export function resetMask(elementId)
67 | {
68 | const mask = masks.get(elementId);
69 |
70 | if (mask)
71 | {
72 | mask.reset();
73 | }
74 | }
75 | export function getValue(elementId)
76 | {
77 | return document.getElementById(elementId).value;
78 | }
79 |
80 |
--------------------------------------------------------------------------------
/CodecellBlazorComponent.Sample/App.razor:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Not found
8 |
9 | Sorry, there's nothing at this address.
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/CodecellBlazorComponent.Sample/CodecellBlazorComponent.Sample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 | service-worker-assets.js
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/CodecellBlazorComponent.Sample/Layout/MainLayout.razor:
--------------------------------------------------------------------------------
1 | @inherits LayoutComponentBase
2 |
3 |
6 |
7 |
8 |
11 |
12 |
13 | @Body
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/CodecellBlazorComponent.Sample/Layout/MainLayout.razor.css:
--------------------------------------------------------------------------------
1 | .page {
2 | position: relative;
3 | display: flex;
4 | flex-direction: column;
5 | }
6 |
7 | main {
8 | flex: 1;
9 | }
10 |
11 | .sidebar {
12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
13 | }
14 |
15 | .top-row {
16 | background-color: #f7f7f7;
17 | border-bottom: 1px solid #d6d5d5;
18 | justify-content: flex-end;
19 | height: 3.5rem;
20 | display: flex;
21 | align-items: center;
22 | }
23 |
24 | .top-row ::deep a, .top-row ::deep .btn-link {
25 | white-space: nowrap;
26 | margin-left: 1.5rem;
27 | text-decoration: none;
28 | }
29 |
30 | .top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
31 | text-decoration: underline;
32 | }
33 |
34 | .top-row ::deep a:first-child {
35 | overflow: hidden;
36 | text-overflow: ellipsis;
37 | }
38 |
39 | @media (max-width: 640.98px) {
40 | .top-row {
41 | justify-content: space-between;
42 | }
43 |
44 | .top-row ::deep a, .top-row ::deep .btn-link {
45 | margin-left: 0;
46 | }
47 | }
48 |
49 | @media (min-width: 641px) {
50 | .page {
51 | flex-direction: row;
52 | }
53 |
54 | .sidebar {
55 | width: 250px;
56 | height: 100vh;
57 | position: sticky;
58 | top: 0;
59 | }
60 |
61 | .top-row {
62 | position: sticky;
63 | top: 0;
64 | z-index: 1;
65 | }
66 |
67 | .top-row.auth ::deep a:first-child {
68 | flex: 1;
69 | text-align: right;
70 | width: 0;
71 | }
72 |
73 | .top-row, article {
74 | padding-left: 2rem !important;
75 | padding-right: 1.5rem !important;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/CodecellBlazorComponent.Sample/Layout/NavMenu.razor:
--------------------------------------------------------------------------------
1 |
9 |
10 |
29 |
30 | @code {
31 | private bool collapseNavMenu = true;
32 |
33 | private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
34 |
35 | private void ToggleNavMenu()
36 | {
37 | collapseNavMenu = !collapseNavMenu;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/CodecellBlazorComponent.Sample/Layout/NavMenu.razor.css:
--------------------------------------------------------------------------------
1 | .navbar-toggler {
2 | background-color: rgba(255, 255, 255, 0.1);
3 | }
4 |
5 | .top-row {
6 | height: 3.5rem;
7 | background-color: rgba(0,0,0,0.4);
8 | }
9 |
10 | .navbar-brand {
11 | font-size: 1.1rem;
12 | }
13 |
14 | .bi {
15 | display: inline-block;
16 | position: relative;
17 | width: 1.25rem;
18 | height: 1.25rem;
19 | margin-right: 0.75rem;
20 | top: -1px;
21 | background-size: cover;
22 | }
23 |
24 | .bi-house-door-fill-nav-menu {
25 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
26 | }
27 |
28 | .bi-plus-square-fill-nav-menu {
29 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
30 | }
31 |
32 | .bi-list-nested-nav-menu {
33 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
34 | }
35 |
36 | .nav-item {
37 | font-size: 0.9rem;
38 | padding-bottom: 0.5rem;
39 | }
40 |
41 | .nav-item:first-of-type {
42 | padding-top: 1rem;
43 | }
44 |
45 | .nav-item:last-of-type {
46 | padding-bottom: 1rem;
47 | }
48 |
49 | .nav-item ::deep a {
50 | color: #d7d7d7;
51 | border-radius: 4px;
52 | height: 3rem;
53 | display: flex;
54 | align-items: center;
55 | line-height: 3rem;
56 | }
57 |
58 | .nav-item ::deep a.active {
59 | background-color: rgba(255,255,255,0.37);
60 | color: white;
61 | }
62 |
63 | .nav-item ::deep a:hover {
64 | background-color: rgba(255,255,255,0.1);
65 | color: white;
66 | }
67 |
68 | @media (min-width: 641px) {
69 | .navbar-toggler {
70 | display: none;
71 | }
72 |
73 | .collapse {
74 | /* Never collapse the sidebar for wide screens */
75 | display: block;
76 | }
77 |
78 | .nav-scrollable {
79 | /* Allow sidebar to scroll for tall menus */
80 | height: calc(100vh - 3.5rem);
81 | overflow-y: auto;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/CodecellBlazorComponent.Sample/Pages/Counter.razor:
--------------------------------------------------------------------------------
1 | @page "/counter"
2 |
3 | Counter
4 |
5 | Counter
6 |
7 | Current count: @currentCount
8 |
9 | Click me
10 |
11 | @code {
12 | private int currentCount = 0;
13 |
14 | private void IncrementCount()
15 | {
16 | currentCount++;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/CodecellBlazorComponent.Sample/Pages/Home.razor:
--------------------------------------------------------------------------------
1 | @page "/"
2 | @using System.ComponentModel.DataAnnotations
3 | Home
4 |
5 | Hello, world!
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
26 |
27 | Submit
28 |
29 |
30 |
31 |
32 |
33 | @Model.BirthDate
34 |
35 |
36 | @code{
37 |
38 | Student Model { get; set; } = new();
39 |
40 | void DateChanged(DateTime? value)
41 | {
42 | Console.WriteLine($"Date Changed: {value}");
43 | }
44 |
45 | public class Student
46 | {
47 | public string Name { get; set; }
48 |
49 | [Required(ErrorMessage ="تاریخ تولد اجباری است")]
50 | public DateTime? BirthDate { get; set; }
51 | }
52 |
53 | void Submit()
54 | {
55 |
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/CodecellBlazorComponent.Sample/Pages/Weather.razor:
--------------------------------------------------------------------------------
1 | @page "/weather"
2 | @inject HttpClient Http
3 |
4 | Weather
5 |
6 | Weather
7 |
8 | This component demonstrates fetching data from the server.
9 |
10 | @if (forecasts == null)
11 | {
12 | Loading...
13 | }
14 | else
15 | {
16 |
17 |
18 |
19 | Date
20 | Temp. (C)
21 | Temp. (F)
22 | Summary
23 |
24 |
25 |
26 | @foreach (var forecast in forecasts)
27 | {
28 |
29 | @forecast.Date.ToShortDateString()
30 | @forecast.TemperatureC
31 | @forecast.TemperatureF
32 | @forecast.Summary
33 |
34 | }
35 |
36 |
37 | }
38 |
39 | @code {
40 | private WeatherForecast[]? forecasts;
41 |
42 | protected override async Task OnInitializedAsync()
43 | {
44 | forecasts = await Http.GetFromJsonAsync("sample-data/weather.json");
45 | }
46 |
47 | public class WeatherForecast
48 | {
49 | public DateOnly Date { get; set; }
50 |
51 | public int TemperatureC { get; set; }
52 |
53 | public string? Summary { get; set; }
54 |
55 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/CodecellBlazorComponent.Sample/Program.cs:
--------------------------------------------------------------------------------
1 | using CodecellBlazorComponent.Sample;
2 | using Codecell.Component.Blazor;
3 | using Microsoft.AspNetCore.Components.Web;
4 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
5 |
6 | var builder = WebAssemblyHostBuilder.CreateDefault(args);
7 | builder.RootComponents.Add("#app");
8 | builder.RootComponents.Add("head::after");
9 |
10 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
11 |
12 | builder.Services.AddCodecellBlazor();
13 |
14 | await builder.Build().RunAsync();
15 |
--------------------------------------------------------------------------------
/CodecellBlazorComponent.Sample/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:42275",
8 | "sslPort": 44332
9 | }
10 | },
11 | "profiles": {
12 | "http": {
13 | "commandName": "Project",
14 | "dotnetRunMessages": true,
15 | "launchBrowser": true,
16 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
17 | "applicationUrl": "http://localhost:5193",
18 | "environmentVariables": {
19 | "ASPNETCORE_ENVIRONMENT": "Development"
20 | }
21 | },
22 | "https": {
23 | "commandName": "Project",
24 | "dotnetRunMessages": true,
25 | "launchBrowser": true,
26 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
27 | "applicationUrl": "https://localhost:7105;http://localhost:5193",
28 | "environmentVariables": {
29 | "ASPNETCORE_ENVIRONMENT": "Development"
30 | }
31 | },
32 | "IIS Express": {
33 | "commandName": "IISExpress",
34 | "launchBrowser": true,
35 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
36 | "environmentVariables": {
37 | "ASPNETCORE_ENVIRONMENT": "Development"
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/CodecellBlazorComponent.Sample/_Imports.razor:
--------------------------------------------------------------------------------
1 | @using System.Net.Http
2 | @using System.Net.Http.Json
3 | @using Microsoft.AspNetCore.Components.Forms
4 | @using Microsoft.AspNetCore.Components.Routing
5 | @using Microsoft.AspNetCore.Components.Web
6 | @using Microsoft.AspNetCore.Components.Web.Virtualization
7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http
8 | @using Microsoft.JSInterop
9 | @using CodecellBlazorComponent.Sample
10 | @using CodecellBlazorComponent.Sample.Layout
11 | @using Codecell.Component.Blazor.Components.PersianDatePickerComponent
12 |
--------------------------------------------------------------------------------
/CodecellBlazorComponent.Sample/wwwroot/css/app.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
3 | }
4 |
5 | h1:focus {
6 | outline: none;
7 | }
8 |
9 | a, .btn-link {
10 | color: #0071c1;
11 | }
12 |
13 | .btn-primary {
14 | color: #fff;
15 | background-color: #1b6ec2;
16 | border-color: #1861ac;
17 | }
18 |
19 | .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
20 | box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
21 | }
22 |
23 | .content {
24 | padding-top: 1.1rem;
25 | }
26 |
27 | .valid.modified:not([type=checkbox]) {
28 | outline: 1px solid #26b050;
29 | }
30 |
31 | .invalid {
32 | outline: 1px solid red;
33 | }
34 |
35 | .validation-message {
36 | color: red;
37 | }
38 |
39 | #blazor-error-ui {
40 | background: lightyellow;
41 | bottom: 0;
42 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
43 | display: none;
44 | left: 0;
45 | padding: 0.6rem 1.25rem 0.7rem 1.25rem;
46 | position: fixed;
47 | width: 100%;
48 | z-index: 1000;
49 | }
50 |
51 | #blazor-error-ui .dismiss {
52 | cursor: pointer;
53 | position: absolute;
54 | right: 0.75rem;
55 | top: 0.5rem;
56 | }
57 |
58 | .blazor-error-boundary {
59 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
60 | padding: 1rem 1rem 1rem 3.7rem;
61 | color: white;
62 | }
63 |
64 | .blazor-error-boundary::after {
65 | content: "An error has occurred."
66 | }
67 |
68 | .loading-progress {
69 | position: relative;
70 | display: block;
71 | width: 8rem;
72 | height: 8rem;
73 | margin: 20vh auto 1rem auto;
74 | }
75 |
76 | .loading-progress circle {
77 | fill: none;
78 | stroke: #e0e0e0;
79 | stroke-width: 0.6rem;
80 | transform-origin: 50% 50%;
81 | transform: rotate(-90deg);
82 | }
83 |
84 | .loading-progress circle:last-child {
85 | stroke: #1b6ec2;
86 | stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
87 | transition: stroke-dasharray 0.05s ease-in-out;
88 | }
89 |
90 | .loading-progress-text {
91 | position: absolute;
92 | text-align: center;
93 | font-weight: bold;
94 | inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
95 | }
96 |
97 | .loading-progress-text:after {
98 | content: var(--blazor-load-percentage-text, "Loading");
99 | }
100 |
101 | code {
102 | color: #c02d76;
103 | }
104 |
--------------------------------------------------------------------------------
/CodecellBlazorComponent.Sample/wwwroot/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codecellir/Codecell.Blazor.Components/98ea8d8edb51a1333c36eb79dbfae2ea90767d47/CodecellBlazorComponent.Sample/wwwroot/favicon.png
--------------------------------------------------------------------------------
/CodecellBlazorComponent.Sample/wwwroot/icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codecellir/Codecell.Blazor.Components/98ea8d8edb51a1333c36eb79dbfae2ea90767d47/CodecellBlazorComponent.Sample/wwwroot/icon-192.png
--------------------------------------------------------------------------------
/CodecellBlazorComponent.Sample/wwwroot/icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codecellir/Codecell.Blazor.Components/98ea8d8edb51a1333c36eb79dbfae2ea90767d47/CodecellBlazorComponent.Sample/wwwroot/icon-512.png
--------------------------------------------------------------------------------
/CodecellBlazorComponent.Sample/wwwroot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | CodecellBlazorComponent.Sample
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | An unhandled error has occurred.
30 |
Reload
31 |
🗙
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/CodecellBlazorComponent.Sample/wwwroot/manifest.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "CodecellBlazorComponent.Sample",
3 | "short_name": "CodecellBlazorComponent.Sample",
4 | "id": "./",
5 | "start_url": "./",
6 | "display": "standalone",
7 | "background_color": "#ffffff",
8 | "theme_color": "#03173d",
9 | "prefer_related_applications": false,
10 | "icons": [
11 | {
12 | "src": "icon-512.png",
13 | "type": "image/png",
14 | "sizes": "512x512"
15 | },
16 | {
17 | "src": "icon-192.png",
18 | "type": "image/png",
19 | "sizes": "192x192"
20 | }
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/CodecellBlazorComponent.Sample/wwwroot/sample-data/weather.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "date": "2022-01-06",
4 | "temperatureC": 1,
5 | "summary": "Freezing"
6 | },
7 | {
8 | "date": "2022-01-07",
9 | "temperatureC": 14,
10 | "summary": "Bracing"
11 | },
12 | {
13 | "date": "2022-01-08",
14 | "temperatureC": -13,
15 | "summary": "Freezing"
16 | },
17 | {
18 | "date": "2022-01-09",
19 | "temperatureC": -16,
20 | "summary": "Balmy"
21 | },
22 | {
23 | "date": "2022-01-10",
24 | "temperatureC": -2,
25 | "summary": "Chilly"
26 | }
27 | ]
28 |
--------------------------------------------------------------------------------
/CodecellBlazorComponent.Sample/wwwroot/service-worker.js:
--------------------------------------------------------------------------------
1 | // In development, always fetch from the network and do not enable offline support.
2 | // This is because caching would make development more difficult (changes would not
3 | // be reflected on the first load after each change).
4 | self.addEventListener('fetch', () => { });
5 |
--------------------------------------------------------------------------------
/CodecellBlazorComponent.Sample/wwwroot/service-worker.published.js:
--------------------------------------------------------------------------------
1 | // Caution! Be sure you understand the caveats before publishing an application with
2 | // offline support. See https://aka.ms/blazor-offline-considerations
3 |
4 | self.importScripts('./service-worker-assets.js');
5 | self.addEventListener('install', event => event.waitUntil(onInstall(event)));
6 | self.addEventListener('activate', event => event.waitUntil(onActivate(event)));
7 | self.addEventListener('fetch', event => event.respondWith(onFetch(event)));
8 |
9 | const cacheNamePrefix = 'offline-cache-';
10 | const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`;
11 | const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/ ];
12 | const offlineAssetsExclude = [ /^service-worker\.js$/ ];
13 |
14 | // Replace with your base path if you are hosting on a subfolder. Ensure there is a trailing '/'.
15 | const base = "/";
16 | const baseUrl = new URL(base, self.origin);
17 | const manifestUrlList = self.assetsManifest.assets.map(asset => new URL(asset.url, baseUrl).href);
18 |
19 | async function onInstall(event) {
20 | console.info('Service worker: Install');
21 |
22 | // Fetch and cache all matching items from the assets manifest
23 | const assetsRequests = self.assetsManifest.assets
24 | .filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url)))
25 | .filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url)))
26 | .map(asset => new Request(asset.url, { integrity: asset.hash, cache: 'no-cache' }));
27 | await caches.open(cacheName).then(cache => cache.addAll(assetsRequests));
28 | }
29 |
30 | async function onActivate(event) {
31 | console.info('Service worker: Activate');
32 |
33 | // Delete unused caches
34 | const cacheKeys = await caches.keys();
35 | await Promise.all(cacheKeys
36 | .filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName)
37 | .map(key => caches.delete(key)));
38 | }
39 |
40 | async function onFetch(event) {
41 | let cachedResponse = null;
42 | if (event.request.method === 'GET') {
43 | // For all navigation requests, try to serve index.html from cache,
44 | // unless that request is for an offline resource.
45 | // If you need some URLs to be server-rendered, edit the following check to exclude those URLs
46 | const shouldServeIndexHtml = event.request.mode === 'navigate'
47 | && !manifestUrlList.some(url => url === event.request.url);
48 |
49 | const request = shouldServeIndexHtml ? 'index.html' : event.request;
50 | const cache = await caches.open(cacheName);
51 | cachedResponse = await cache.match(request);
52 | }
53 |
54 | return cachedResponse || fetch(event.request);
55 | }
56 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## CodecellComponent.Blazor
2 | Custom Components for Blazor
3 |
4 | ## PersianDatePicker
5 | ## Features
6 |
7 | - Two-Way Binding
8 | - Dark Mode
9 | - Month Navigation
10 | - Year Navigation
11 | - Date Change Event Callback
12 | - Validation
13 | - Mask Input
14 |
15 | ## Installation
16 | You can download the latest version of `Codecell.Component.Blazor` from [Github repository](https://github.com/codecellir/Codecell.Blazor.Components).
17 | To install via `nuget`:
18 | ```
19 | Install-Package Codecell.Component.Blazor -Version 0.2.7
20 | ```
21 | Install from [Nuget](https://www.nuget.org/packages/Codecell.Component.Blazor) directly.
22 |
23 | ## How to use
24 | Register Codecell Persian DatePicker Control to project container in `Program.cs` file:
25 | ``` C#
26 | using Codecell.Component.Blazor;
27 |
28 | builder.Services.AddCodecellBlazor();
29 | ```
30 |
31 | Add using to _imports.razor
32 | ``` C#
33 | @using Codecell.Component.Blazor.Components.PersianDatePickerComponent
34 | ```
35 |
36 | Add style references
37 | ``` Razor
38 | @page "/"
39 |
40 |
41 | ```
42 |
43 | Usage Sample
44 | ``` Razor
45 | @page "/"
46 |
47 | Home
48 |
49 | Hello, world!
50 |
51 |
52 |
61 |
62 |
63 | @myDate
64 |
65 |
66 | @code{
67 |
68 | DateTime? myDate;
69 |
70 | void DateChanged(DateTime? value)
71 | {
72 | Console.WriteLine($"Date Changed: {value}");
73 | }
74 | }
75 |
76 | ```
77 |
78 | Validation Sample
79 | ``` Razor
80 | @page "/"
81 | @using System.ComponentModel.DataAnnotations
82 | Home
83 |
84 | Hello, world!
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
102 |
103 | Submit
104 |
105 |
106 |
107 |
108 |
109 | @Model.BirthDate
110 |
111 |
112 | @code{
113 |
114 | Student Model { get; set; } = new();
115 |
116 |
117 | public class Student
118 | {
119 | public string Name { get; set; }
120 |
121 | [Required(ErrorMessage ="تاریخ تولد اجباری است")]
122 | public DateTime? BirthDate { get; set; }
123 | }
124 |
125 | void Submit()
126 | {
127 |
128 | }
129 | }
130 |
131 |
132 | ```
133 |
134 |
135 | ## Screenshots
136 | 
137 | 
138 | 
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codecellir/Codecell.Blazor.Components/98ea8d8edb51a1333c36eb79dbfae2ea90767d47/logo.png
--------------------------------------------------------------------------------
/screenshots/dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codecellir/Codecell.Blazor.Components/98ea8d8edb51a1333c36eb79dbfae2ea90767d47/screenshots/dark.png
--------------------------------------------------------------------------------
/screenshots/light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codecellir/Codecell.Blazor.Components/98ea8d8edb51a1333c36eb79dbfae2ea90767d47/screenshots/light.png
--------------------------------------------------------------------------------
/screenshots/validation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codecellir/Codecell.Blazor.Components/98ea8d8edb51a1333c36eb79dbfae2ea90767d47/screenshots/validation.png
--------------------------------------------------------------------------------