sun-sword 0.0.9 → 0.0.11

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.
@@ -1,116 +1,118 @@
1
- import {Controller} from "@hotwired/stimulus"
2
- import anime from "animejs";
1
+ import { Controller } from "@hotwired/stimulus"
3
2
 
4
3
  export default class extends Controller {
5
- confirmationDestroy(event) {
6
- const chooseTypes = document.querySelectorAll(".confirmation-destroy-" + event.params.id);
7
- chooseTypes.forEach((element) => {
8
- element.classList.remove('hidden');
9
- })
4
+ connect() {
5
+ this.sidebarEl = document.querySelector(".sidebar")
6
+ this.backdropEl = document.querySelector(".backdrop-active")
7
+ this.toggleBtns = document.querySelectorAll("[data-action~='web#sidebarToggle']")
8
+ if (!this.sidebarEl || !this.backdropEl) return
9
+
10
+ this.isOpen = false
11
+ this.prefersReduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches
12
+ this._onEsc = (e) => { if (e.key === "Escape") this.sidebarClose() }
13
+
14
+ this.backdropEl.addEventListener("click", () => this.sidebarClose(), { passive: true })
15
+
16
+ // Inisialisasi: pastikan posisi tertutup
17
+ this.sidebarEl.classList.add("-translate-x-full")
18
+ this.sidebarEl.classList.remove("translate-x-0")
19
+ this._setBackdrop(false)
10
20
  }
11
21
 
12
- confirmationDestroyCancel(event) {
13
- const chooseTypes = document.querySelectorAll(".confirmation-destroy-" + event.params.id);
14
- chooseTypes.forEach((element) => {
15
- element.classList.add('hidden');
16
- })
22
+ // === Sidebar ===
23
+ sidebarToggle(e) {
24
+ e?.preventDefault()
25
+ this.isOpen ? this.sidebarClose() : this.sidebarOpen(e?.currentTarget)
17
26
  }
18
27
 
19
- profileSetting(event) {
20
- console.log('Profile');
21
- const profileSettingBrowser = document.querySelector(".profile-browser");
22
- const profileSettingMobile = document.querySelector(".profile-mobile");
23
- if (profileSettingBrowser.classList.contains('hidden')) {
24
- profileSettingBrowser.classList.remove('hidden');
25
- console.log('show');
26
- } else {
27
- console.log('hide');
28
- profileSettingBrowser.classList.add('hidden');
29
- }
30
- if (profileSettingMobile.classList.contains('hidden')) {
31
- profileSettingMobile.classList.remove('hidden');
32
- console.log('show');
33
- } else {
34
- console.log('hide');
35
- profileSettingMobile.classList.add('hidden');
36
- }
28
+ sidebarOpen(triggerEl) {
29
+ if (this.isOpen) return
30
+ this.isOpen = true
31
+
32
+ // Kurangi jank: pastikan element “on GPU”
33
+ this.sidebarEl.style.transform = "translate3d(-100%,0,0)"
34
+ // Force reflow → lalu transisi ke posisi buka pada frame berikut
35
+ requestAnimationFrame(() => {
36
+ this.sidebarEl.classList.remove("-translate-x-full")
37
+ this.sidebarEl.classList.add("translate-x-0")
38
+ this.sidebarEl.style.transform = "" // kembalikan, biar Tailwind kelas yang mengatur
39
+ })
40
+
41
+ this._setBackdrop(true)
42
+ document.documentElement.classList.add("overflow-hidden")
43
+ triggerEl?.setAttribute("aria-expanded", "true")
44
+ this.sidebarEl.setAttribute("aria-hidden", "false")
45
+ document.addEventListener("keydown", this._onEsc, { passive: true })
46
+
47
+ // Fokus pertama
48
+ this.sidebarEl.querySelector("a, button, input, [tabindex]")
49
+ ?.focus({ preventScroll: true })
37
50
  }
38
51
 
39
- sidebarToggle(event) {
40
- const backdropActive = document.querySelector(".backdrop-active");
41
- const sidebar = document.querySelector(".sidebar");
42
- if (sidebar.classList.contains('side_hide')) {
43
- sidebar.classList.remove('side_hide');
44
- backdropActive.classList.remove('hidden');
45
- anime({
46
- targets: ".backdrop-active",
47
- translateX: 0,
48
- opacity: [0, 0.8],
49
- easing: 'easeInOutSine',
50
- complete: function (anim) {
51
- console.log('Backdrop Active');
52
- }
53
- })
54
- anime({
55
- targets: '.sidebar',
56
- translateX: 300,
57
- duration: 1000,
58
- easing: 'easeInOutExpo',
59
- complete: function (anim) {
60
- console.log('Sidebar show!');
61
- }
62
- });
63
- } else {
64
- sidebar.classList.add('side_hide');
65
- anime({
66
- targets: '.sidebar',
67
- easing: 'easeInOutExpo',
68
- translateX: -300,
69
- duration: 1000,
70
- complete: function (anim) {
71
- console.log('Sidebar Close!');
72
- }
73
- });
74
- anime({
75
- targets: ".backdrop-active",
76
- translateX: 0,
77
- opacity: [0.8, 0],
78
- easing: 'easeInOutSine',
79
- complete: function (anim) {
80
- backdropActive.classList.add('hidden');
81
- }
82
- })
83
- }
52
+ sidebarClose() {
53
+ if (!this.isOpen) return
54
+ this.isOpen = false
55
+
56
+ // Transisi balik (geser keluar)
57
+ this.sidebarEl.classList.add("-translate-x-full")
58
+ this.sidebarEl.classList.remove("translate-x-0")
59
+
60
+ this._setBackdrop(false)
61
+ document.documentElement.classList.remove("overflow-hidden")
62
+ this.sidebarEl.setAttribute("aria-hidden", "true")
63
+ this.toggleBtns.forEach((btn) => btn.setAttribute("aria-expanded", "false"))
64
+ document.removeEventListener("keydown", this._onEsc)
65
+ this.toggleBtns[0]?.focus({ preventScroll: true })
84
66
  }
85
67
 
86
68
  onSidebarClick(event) {
87
- event.preventDefault()
88
- const url = event.target.href;
89
- const backdropActive = document.querySelector(".backdrop-active");
90
- const sidebar = document.querySelector(".sidebar");
91
- if (url !== undefined) {
92
- sidebar.classList.add('side_hide');
93
- anime({
94
- targets: '.sidebar',
95
- easing: 'easeInOutExpo',
96
- translateX: -300,
97
- duration: 1000,
98
- complete: function (anim) {
99
- console.log('Sidebar Close!');
100
- }
101
- });
102
- anime({
103
- targets: ".backdrop-active",
104
- translateX: 0,
105
- opacity: [0.6, 0],
106
- easing: 'easeInOutSine',
107
- complete: function (anim) {
108
- console.log('opacity');
109
- backdropActive.classList.add('hidden');
110
- Turbo.visit(url);
69
+ const a = event.target.closest("a, button[type='submit']")
70
+ if (!a) return
71
+ setTimeout(() => this.sidebarClose(), 0)
72
+ }
73
+
74
+ // === Util ===
75
+ _setBackdrop(show) {
76
+ // Jika user minta “reduce motion”, skip animasi
77
+ if (this.prefersReduced) {
78
+ this.backdropEl.classList.toggle("hidden", !show)
79
+ this.backdropEl.classList.toggle("pointer-events-none", !show)
80
+ this.backdropEl.style.opacity = show ? "1" : "0"
81
+ return
82
+ }
83
+
84
+ // Pastikan elemen ada di flow saat mulai animasi
85
+ this.backdropEl.classList.remove("hidden")
86
+ requestAnimationFrame(() => {
87
+ if (show) {
88
+ this.backdropEl.classList.remove("opacity-0", "pointer-events-none")
89
+ this.backdropEl.classList.add("opacity-100")
90
+ } else {
91
+ // Fade out, lalu sembunyikan setelah selesai
92
+ this.backdropEl.classList.remove("opacity-100")
93
+ this.backdropEl.classList.add("opacity-0", "pointer-events-none")
94
+ const onEnd = () => {
95
+ this.backdropEl.classList.add("hidden")
96
+ this.backdropEl.removeEventListener("transitionend", onEnd)
111
97
  }
112
- })
98
+ this.backdropEl.addEventListener("transitionend", onEnd)
99
+ }
100
+ })
101
+ }
102
+
103
+ // Opsional: submenu & profil tetap bisa dipakai dari versi sebelumnya
104
+ profileSetting(event) {
105
+ const container = event.currentTarget.closest("li")
106
+ const dropdown = container?.querySelector("[class^='profile-']")
107
+ if (!dropdown) return
108
+
109
+ dropdown.classList.toggle("hidden")
110
+ const closeOnOutside = (e) => {
111
+ if (!dropdown.contains(e.target) && !event.currentTarget.contains(e.target)) {
112
+ dropdown.classList.add("hidden")
113
+ window.removeEventListener("click", closeOnOutside, true)
114
+ }
113
115
  }
116
+ window.addEventListener("click", closeOnOutside, true)
114
117
  }
115
118
  }
116
-
@@ -0,0 +1,221 @@
1
+ @import "tailwindcss";
2
+
3
+ @plugin "@tailwindcss/forms";
4
+ @plugin "@tailwindcss/typography";
5
+ @plugin "@tailwindcss/aspect-ratio";
6
+
7
+ @source "../../../app/views/**/*.html.erb";
8
+ @source "../../../app/views/**/*.rb";
9
+ @source "../../../app/helpers/**/*.rb";
10
+ @source "./app/frontend/**/*.{js,ts,jsx,tsx,css,scss,ejs}";
11
+
12
+
13
+ /* Theme tokens: memetakan screens & font family */
14
+ @theme {
15
+ /* Breakpoints (sesuai config kamu) */
16
+ --breakpoint-sm: 640px;
17
+ --breakpoint-md: 768px;
18
+ --breakpoint-lg: 1024px;
19
+ --breakpoint-xl: 1280px;
20
+ --breakpoint-2xl: 1536px;
21
+
22
+ /* Font stack: Open Sans + fallback default Tailwind */
23
+ --font-sans: "Open Sans", ui-sans-serif, system-ui, sans-serif,
24
+ "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
25
+ }
26
+
27
+
28
+ @layer base {
29
+ * {
30
+ box-sizing: border-box;
31
+ }
32
+
33
+ *:before,
34
+ *:after {
35
+ box-sizing: border-box;
36
+ }
37
+
38
+ html {
39
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
40
+ }
41
+
42
+ body {
43
+ font-family: "Open Sans", sans-serif;
44
+ font-size: 1.6rem;
45
+ line-height: 1.75;
46
+ font-weight: 300;
47
+ color: #303030;
48
+ letter-spacing: 0.045em;
49
+ background-color: #fbfbfb;
50
+ }
51
+
52
+ .tooltip {
53
+ position: relative;
54
+ }
55
+
56
+ strong {
57
+ font-weight: 500;
58
+ }
59
+
60
+ .tooltip::before {
61
+ content: "";
62
+ position: absolute;
63
+ top: -6px;
64
+ left: 50%;
65
+ transform: translateX(-50%);
66
+ border-width: 4px 6px 0 6px;
67
+ border-style: solid;
68
+ border-color: rgba(0, 0, 0, 0.7) transparent transparent transparent;
69
+ z-index: 99;
70
+ opacity: 0;
71
+ transition: .3s opacity;
72
+ }
73
+
74
+ [tooltip-position='left']::before {
75
+ left: 0%;
76
+ top: 50%;
77
+ margin-left: -12px;
78
+ transform: translatey(-50%) rotate(-90deg)
79
+ }
80
+
81
+ [tooltip-position='top']::before {
82
+ left: 50%;
83
+ }
84
+
85
+ [tooltip-position='buttom']::before {
86
+ top: 100%;
87
+ margin-top: 8px;
88
+ transform: translateX(-50%) translatey(-100%) rotate(-180deg)
89
+ }
90
+
91
+ [tooltip-position='right']::before {
92
+ left: 100%;
93
+ top: 50%;
94
+ margin-left: 1px;
95
+ transform: translatey(-50%) rotate(90deg)
96
+ }
97
+
98
+ .tooltip::after {
99
+ content: attr(data-text);
100
+ position: absolute;
101
+ min-width: 10rem;
102
+ left: 50%;
103
+ top: -6px;
104
+ transform: translateX(-50%) translateY(-100%);
105
+ background: rgba(0, 0, 0, 0.7);
106
+ text-align: center;
107
+ color: #fff;
108
+ font-size: 12px;
109
+ border-radius: 5px;
110
+ pointer-events: none;
111
+ padding: 4px 4px;
112
+ z-index: 99;
113
+ opacity: 0;
114
+ transition: .3s opacity;
115
+ }
116
+
117
+ [tooltip-position='left']::after {
118
+ left: 0;
119
+ top: 50%;
120
+ margin-left: -8px;
121
+ transform: translateX(-100%) translateY(-50%);
122
+ }
123
+
124
+ [tooltip-position='top']::after {
125
+ left: 50%;
126
+ }
127
+
128
+ [tooltip-position='buttom']::after {
129
+ top: 100%;
130
+ margin-top: 8px;
131
+ transform: translateX(-50%) translateY(0%);
132
+ }
133
+
134
+ [tooltip-position='right']::after {
135
+ left: 100%;
136
+ top: 50%;
137
+ margin-left: 8px;
138
+ transform: translateX(0%) translateY(-50%);
139
+ }
140
+
141
+ .tooltip:hover::after, .tooltip:hover::before {
142
+ opacity: 1
143
+ }
144
+
145
+
146
+ pre {
147
+ white-space: pre-wrap;
148
+ word-wrap: break-word;
149
+ }
150
+
151
+ //#------------
152
+ h1 {
153
+ @apply text-2xl;
154
+ }
155
+
156
+ h2 {
157
+ @apply text-xl;
158
+ }
159
+
160
+ h3 {
161
+ @apply text-lg;
162
+ }
163
+
164
+ .class-button {
165
+ @apply rounded-md px-3 py-2 text-center text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2;
166
+ @apply focus-visible:outline-gray-500 bg-gray-500 hover:bg-white hover:text-gray-500 hover:border-gray-50 hover:outline;
167
+ }
168
+
169
+ .class-button-outline {
170
+ @apply rounded-md px-3 py-2 text-center text-sm font-semibold text-gray-500 shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2;
171
+ @apply outline outline-1 border-gray-500 focus-visible:outline-gray-500 bg-white hover:bg-gray-500 hover:text-white hover:border-gray-500 hover:outline;
172
+ }
173
+
174
+ .class-label {
175
+ @apply text-sm font-semibold leading-6 text-gray-900;
176
+ }
177
+
178
+ .class-text-field {
179
+ @apply block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6;
180
+ }
181
+
182
+ .class-card-container {
183
+ @apply overflow-hidden bg-white shadow sm:rounded-lg p-5
184
+ }
185
+
186
+ .class-input {
187
+ @apply sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:py-6 pl-2
188
+ }
189
+
190
+ .class-tr {
191
+ @apply top-0 z-10 border-b border-gray-300 bg-white/75 px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell
192
+ }
193
+
194
+ .class-td {
195
+ @apply whitespace-nowrap px-3 py-5 text-sm text-gray-500
196
+ }
197
+
198
+ .class-menu-link {
199
+ @apply hover:font-semibold hover:text-gray-500 block rounded-md py-2 pr-2 pl-9 text-sm leading-6 text-gray-700
200
+ }
201
+
202
+ .class-text-link {
203
+ @apply hover:font-semibold hover:text-gray-500 block rounded-md text-sm leading-6 text-gray-700
204
+ }
205
+
206
+ .class-menu-active-link {
207
+ @apply font-semibold block rounded-md py-2 pr-2 pl-9 text-sm leading-6 text-gray-700
208
+ }
209
+ }
210
+
211
+ @layer utilities {
212
+ /* Chrome, Safari and Opera */
213
+ .no-scrollbar::-webkit-scrollbar {
214
+ display: none;
215
+ }
216
+
217
+ .no-scrollbar {
218
+ -ms-overflow-style: none; /* IE and Edge */
219
+ scrollbar-width: none; /* Firefox */
220
+ }
221
+ }
@@ -5,7 +5,7 @@
5
5
  </svg>
6
6
  </button>
7
7
  <div class="confirmation-destroy-<%%= value.id %> hidden relative z-10" aria-labelledby="modal-title" role="dialog" aria-modal="false">
8
- <div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity min-h-screen z-100"></div>
8
+ <div class="fixed inset-0 bg-gray-500/75 transition-opacity min-h-screen z-100"></div>
9
9
  <div class="fixed inset-0 z-100 w-screen overflow-y-auto">
10
10
  <div class="flex min-h-full justify-center p-4 text-center items-center sm:p-0">
11
11
  <div class="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
@@ -16,8 +16,7 @@
16
16
  <%%= csp_meta_tag %>
17
17
 
18
18
  <%%= vite_client_tag %>
19
- <%%= vite_stylesheet_tag 'application.scss', data: { "turbo-track": "reload" } %>
20
- <%%= vite_javascript_tag 'application' %>
19
+ <%%= vite_javascript_tag "application", "data-turbo-track": "reload", defer: true %>
21
20
  </head>
22
21
 
23
22
  <body class="h-full" data-turbo="true">
@@ -16,7 +16,7 @@
16
16
  <%%= csp_meta_tag %>
17
17
 
18
18
  <%%= vite_client_tag %>
19
- <%%= vite_javascript_tag 'application', data: { "turbo-track": "reload" } %>
19
+ <%%= vite_javascript_tag "application", "data-turbo-track": "reload", defer: true %>
20
20
  </head>
21
21
 
22
22
  <body class="h-full" data-turbo="true">
@@ -27,7 +27,11 @@
27
27
  <div class="relative isolate flex min-h-svh w-full bg-white max-lg:flex-col lg:bg-zinc-100 dark:bg-zinc-900 dark:lg:bg-zinc-950">
28
28
 
29
29
  <%%# mobile %>
30
- <div class="fixed bg-white rounded-2xl m-2 inset-y-0 w-64 -left-[300px] lg:hidden sidebar side_hide z-50" data-action="click->web#onSidebarClick">
30
+ <div
31
+ class="fixed inset-y-0 left-0 z-50 w-64 max-w-[85vw] bg-white rounded-2xl m-2
32
+ -translate-x-full will-change-transform transition-transform duration-300 ease-out
33
+ lg:hidden sidebar"
34
+ data-action="click->web#onSidebarClick">
31
35
  <nav class="flex h-full min-h-0 flex-col">
32
36
  <%%= render "components/layouts/sidebar", from: :mobile %>
33
37
  </nav>
@@ -53,7 +57,10 @@
53
57
  </div>
54
58
 
55
59
  <main class="flex flex-1 flex-col pb-2 lg:min-w-0 lg:pl-64 lg:pr-2 lg:pt-2">
56
- <div class="fixed inset-0 bg-gray-900 backdrop-blur-lg backdrop-active opacity-0 hidden lg:hidden" aria-hidden="true"></div>
60
+ <div
61
+ class="fixed inset-0 z-40 bg-black/50 opacity-0 pointer-events-none
62
+ transition-opacity duration-300 ease-out backdrop-active lg:hidden hidden"
63
+ aria-hidden="true"></div>
57
64
  <div class="grow p-6 lg:rounded-lg lg:bg-white lg:p-10 lg:shadow-sm lg:ring-1 lg:ring-zinc-950/5 dark:lg:bg-zinc-900 dark:lg:ring-white/10">
58
65
  <div class="mx-auto max-w-6xl">
59
66
  <%%= yield %>
@@ -2,9 +2,12 @@ import {defineConfig} from 'vite'
2
2
  import RubyPlugin from 'vite-plugin-ruby'
3
3
  import FullReload from 'vite-plugin-full-reload'
4
4
  import StimulusHMR from 'vite-plugin-stimulus-hmr';
5
+ // @ts-ignore
6
+ import tailwindcss from "@tailwindcss/vite";
5
7
  export default defineConfig({
6
8
  plugins: [
7
9
  RubyPlugin(),
10
+ tailwindcss(),
8
11
  FullReload(['config/routes.rb', 'app/views/**/*', 'app/frontend/pages/**/*']),
9
12
  StimulusHMR()
10
13
  ],
@@ -16,12 +19,5 @@ export default defineConfig({
16
19
  application: 'app/frontend/entrypoints/application.js'
17
20
  }
18
21
  }
19
- },
20
- css: {
21
- preprocessorOptions: {
22
- scss: {
23
- api: 'modern-compiler' // or "modern"
24
- }
25
- }
26
22
  }
27
23
  })
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SunSword
4
- VERSION = '0.0.9'
4
+ VERSION = '0.0.11'
5
5
  public_constant :VERSION
6
6
  end