funicular-datepicker 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 830a8b55ec3f3f18ecfb150d04d641e1f7831c77c144d47401719d69a00a3376
4
+ data.tar.gz: 652c287646d6fca55fda72a6565d8b4e9ac06ed63fb4a0148c00b33897baf386
5
+ SHA512:
6
+ metadata.gz: 7de6444bb5460fc04d45b42a8ecb04cf1505c70a160dc28e71b2d02b99a1e494ecd9692c95053a966dfdc9c7e2108f979f08f24cb621010da0926170df4d6cc7
7
+ data.tar.gz: 8efed1ffa7f2c60890a2e1f24d484c68084521e00ba97702a05c8ce1ad127b6531b54393380bbad572c32ff0d5f7bde40877f9594a8688897309a4714c6588a0
data/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # funicular-datepicker
2
+
3
+ Date picker component for Funicular.
4
+
5
+ Use it from an application Gemfile:
6
+
7
+ ```ruby
8
+ group :funicular do
9
+ gem "funicular-datepicker"
10
+ end
11
+ ```
@@ -0,0 +1,107 @@
1
+ .funicular-date-picker {
2
+ position: relative;
3
+ }
4
+
5
+ .funicular-date-picker__input_row {
6
+ display: flex;
7
+ gap: 0.5rem;
8
+ }
9
+
10
+ .funicular-date-picker__button,
11
+ .funicular-date-picker__nav,
12
+ .funicular-date-picker__footer_button {
13
+ border: 1px solid #d1d5db;
14
+ border-radius: 0.375rem;
15
+ background: #fff;
16
+ color: #374151;
17
+ cursor: pointer;
18
+ }
19
+
20
+ .funicular-date-picker__button {
21
+ padding: 0.5rem 0.75rem;
22
+ white-space: nowrap;
23
+ }
24
+
25
+ .funicular-date-picker__button:hover,
26
+ .funicular-date-picker__nav:hover,
27
+ .funicular-date-picker__footer_button:hover,
28
+ .funicular-date-picker__day:hover {
29
+ background: #f3f4f6;
30
+ }
31
+
32
+ .funicular-date-picker__panel {
33
+ position: absolute;
34
+ z-index: 30;
35
+ top: calc(100% + 0.5rem);
36
+ left: 0;
37
+ width: 19rem;
38
+ border: 1px solid #d1d5db;
39
+ border-radius: 0.5rem;
40
+ background: #fff;
41
+ box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
42
+ padding: 0.75rem;
43
+ }
44
+
45
+ .funicular-date-picker__header {
46
+ display: flex;
47
+ align-items: center;
48
+ justify-content: space-between;
49
+ margin-bottom: 0.75rem;
50
+ }
51
+
52
+ .funicular-date-picker__month {
53
+ font-weight: 600;
54
+ color: #111827;
55
+ }
56
+
57
+ .funicular-date-picker__nav {
58
+ width: 2rem;
59
+ height: 2rem;
60
+ }
61
+
62
+ .funicular-date-picker__grid {
63
+ display: grid;
64
+ grid-template-columns: repeat(7, 1fr);
65
+ gap: 0.25rem;
66
+ }
67
+
68
+ .funicular-date-picker__weekday {
69
+ text-align: center;
70
+ font-size: 0.75rem;
71
+ font-weight: 600;
72
+ color: #6b7280;
73
+ padding: 0.25rem 0;
74
+ }
75
+
76
+ .funicular-date-picker__empty {
77
+ height: 2rem;
78
+ }
79
+
80
+ .funicular-date-picker__day {
81
+ height: 2rem;
82
+ border: 0;
83
+ border-radius: 0.375rem;
84
+ background: transparent;
85
+ color: #111827;
86
+ cursor: pointer;
87
+ }
88
+
89
+ .funicular-date-picker__day--selected {
90
+ background: #2563eb;
91
+ color: #fff;
92
+ }
93
+
94
+ .funicular-date-picker__day--selected:hover {
95
+ background: #1d4ed8;
96
+ }
97
+
98
+ .funicular-date-picker__footer {
99
+ display: flex;
100
+ justify-content: flex-end;
101
+ gap: 0.5rem;
102
+ margin-top: 0.75rem;
103
+ }
104
+
105
+ .funicular-date-picker__footer_button {
106
+ padding: 0.375rem 0.75rem;
107
+ }
@@ -0,0 +1,181 @@
1
+ class DatePickerComponent < Funicular::Component
2
+ WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
3
+ MONTH_NAMES = [
4
+ "January", "February", "March", "April", "May", "June",
5
+ "July", "August", "September", "October", "November", "December"
6
+ ]
7
+
8
+ def initialize_state
9
+ year, month, = parse_date(props[:value])
10
+ today_year, today_month = today_parts
11
+
12
+ {
13
+ calendar_open: false,
14
+ view_year: year || today_year,
15
+ view_month: month || today_month
16
+ }
17
+ end
18
+
19
+ def open_calendar
20
+ year, month, = parse_date(props[:value])
21
+ patch(
22
+ calendar_open: true,
23
+ view_year: year || state.view_year,
24
+ view_month: month || state.view_month
25
+ )
26
+ end
27
+
28
+ def close_calendar
29
+ patch(calendar_open: false)
30
+ end
31
+
32
+ def prev_month
33
+ if state.view_month == 1
34
+ patch(view_year: state.view_year - 1, view_month: 12)
35
+ else
36
+ patch(view_month: state.view_month - 1)
37
+ end
38
+ end
39
+
40
+ def next_month
41
+ if state.view_month == 12
42
+ patch(view_year: state.view_year + 1, view_month: 1)
43
+ else
44
+ patch(view_month: state.view_month + 1)
45
+ end
46
+ end
47
+
48
+ def clear
49
+ emit_change("")
50
+ patch(calendar_open: false)
51
+ end
52
+
53
+ def handle_input(event)
54
+ emit_change(event.target[:value].to_s)
55
+ end
56
+
57
+ def select_day(day)
58
+ value = date_string(state.view_year, state.view_month, day)
59
+ emit_change(value)
60
+ patch(calendar_open: false)
61
+ end
62
+
63
+ def render
64
+ div(class: "funicular-date-picker") do
65
+ div(class: "funicular-date-picker__input_row") do
66
+ input(
67
+ type: "text",
68
+ value: props[:value].to_s,
69
+ placeholder: props[:placeholder] || "YYYY-MM-DD",
70
+ class: props[:input_class] || "funicular-date-picker__input",
71
+ oninput: :handle_input,
72
+ onfocus: -> { open_calendar }
73
+ )
74
+ button(type: "button", class: "funicular-date-picker__button", onclick: -> { open_calendar }) { "Calendar" }
75
+ end
76
+
77
+ render_calendar if state.calendar_open
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def render_calendar
84
+ div(class: "funicular-date-picker__panel") do
85
+ div(class: "funicular-date-picker__header") do
86
+ button(type: "button", class: "funicular-date-picker__nav", onclick: -> { prev_month }) { "<" }
87
+ div(class: "funicular-date-picker__month") do
88
+ "#{MONTH_NAMES[state.view_month - 1]} #{state.view_year}"
89
+ end
90
+ button(type: "button", class: "funicular-date-picker__nav", onclick: -> { next_month }) { ">" }
91
+ end
92
+
93
+ div(class: "funicular-date-picker__grid") do
94
+ WEEKDAYS.each do |day|
95
+ div(class: "funicular-date-picker__weekday") { day }
96
+ end
97
+
98
+ first_weekday(state.view_year, state.view_month).times do
99
+ div(class: "funicular-date-picker__empty") { "" }
100
+ end
101
+
102
+ selected_year, selected_month, selected_day = parse_date(props[:value])
103
+ (1..days_in_month(state.view_year, state.view_month)).each do |day|
104
+ selected = selected_year == state.view_year &&
105
+ selected_month == state.view_month &&
106
+ selected_day == day
107
+ button(
108
+ type: "button",
109
+ class: selected ? "funicular-date-picker__day funicular-date-picker__day--selected" : "funicular-date-picker__day",
110
+ onclick: -> { select_day(day) }
111
+ ) do
112
+ day.to_s
113
+ end
114
+ end
115
+ end
116
+
117
+ div(class: "funicular-date-picker__footer") do
118
+ button(type: "button", class: "funicular-date-picker__footer_button", onclick: -> { clear }) { "Clear" }
119
+ button(type: "button", class: "funicular-date-picker__footer_button", onclick: -> { close_calendar }) { "Close" }
120
+ end
121
+ end
122
+ end
123
+
124
+ def emit_change(value)
125
+ callback = props[:on_change]
126
+ callback.call(value) if callback
127
+ end
128
+
129
+ def parse_date(value)
130
+ return [nil, nil, nil] if value.nil?
131
+
132
+ parts = value.to_s.split("-")
133
+ return [nil, nil, nil] unless parts.size == 3
134
+
135
+ year = parts[0].to_i
136
+ month = parts[1].to_i
137
+ day = parts[2].to_i
138
+ return [nil, nil, nil] if year < 1 || month < 1 || month > 12
139
+ return [nil, nil, nil] if day < 1 || day > days_in_month(year, month)
140
+
141
+ [year, month, day]
142
+ end
143
+
144
+ def today_parts
145
+ now = Time.now
146
+ [now.year, now.month]
147
+ rescue
148
+ [2000, 1]
149
+ end
150
+
151
+ def days_in_month(year, month)
152
+ case month
153
+ when 2
154
+ leap_year?(year) ? 29 : 28
155
+ when 4, 6, 9, 11
156
+ 30
157
+ else
158
+ 31
159
+ end
160
+ end
161
+
162
+ def leap_year?(year)
163
+ (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
164
+ end
165
+
166
+ def first_weekday(year, month)
167
+ offsets = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]
168
+ y = month < 3 ? year - 1 : year
169
+ (y + y / 4 - y / 100 + y / 400 + offsets[month - 1] + 1) % 7
170
+ end
171
+
172
+ def date_string(year, month, day)
173
+ "#{pad(year, 4)}-#{pad(month, 2)}-#{pad(day, 2)}"
174
+ end
175
+
176
+ def pad(number, width)
177
+ text = number.to_s
178
+ text = "0#{text}" while text.length < width
179
+ text
180
+ end
181
+ end
@@ -0,0 +1,7 @@
1
+ module Funicular
2
+ module Plugins
3
+ module DatePicker
4
+ Component = DatePickerComponent
5
+ end
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,44 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: funicular-datepicker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - HASUMI Hitoshi
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: A Funicular date picker component packaged as a Ruby-script CRubygem.
13
+ email:
14
+ - hasumikin@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - README.md
20
+ - assets/date_picker.css
21
+ - lib/components/date_picker_component.rb
22
+ - lib/date_picker.rb
23
+ homepage: https://github.com/hasumikin/funicular-datepicker
24
+ licenses:
25
+ - MIT
26
+ metadata: {}
27
+ rdoc_options: []
28
+ require_paths:
29
+ - lib
30
+ required_ruby_version: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 3.2.0
35
+ required_rubygems_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ requirements: []
41
+ rubygems_version: 4.0.10
42
+ specification_version: 4
43
+ summary: Date picker component for Funicular
44
+ test_files: []