From 6992a061050d3b73a86eb1ef1ce19294ad94b9c8 Mon Sep 17 00:00:00 2001 From: Timothy Cardoza Date: Thu, 12 Feb 2026 17:10:03 -0600 Subject: [PATCH] Initial Commit --- .formatter.exs | 6 + .gitignore | 37 + AGENTS.md | 449 +++++++ README.md | 18 + assets/css/app.css | 264 +++++ assets/css/salad_ui.css | 39 + assets/js/app.js | 98 ++ assets/js/hooks/audio_recorder.js | 169 +++ assets/js/ui/components/accordion.js | 241 ++++ assets/js/ui/components/chart.js | 98 ++ assets/js/ui/components/collapsible.js | 92 ++ assets/js/ui/components/command.js | 129 +++ assets/js/ui/components/dialog.js | 165 +++ assets/js/ui/components/dropdown_menu.js | 175 +++ assets/js/ui/components/hover-card.js | 205 ++++ assets/js/ui/components/menu.js | 324 ++++++ assets/js/ui/components/popover.js | 133 +++ assets/js/ui/components/radio_group.js | 219 ++++ assets/js/ui/components/select.js | 481 ++++++++ assets/js/ui/components/slider.js | 256 ++++ assets/js/ui/components/switch.js | 112 ++ assets/js/ui/components/tabs.js | 176 +++ assets/js/ui/components/tooltip.js | 201 ++++ assets/js/ui/core/click-outside.js | 121 ++ assets/js/ui/core/collection.js | 337 ++++++ assets/js/ui/core/component.js | 548 +++++++++ assets/js/ui/core/factory.js | 30 + assets/js/ui/core/focus-trap.js | 154 +++ assets/js/ui/core/hook.js | 55 + assets/js/ui/core/portal.js | 186 +++ assets/js/ui/core/positioned-element.js | 353 ++++++ assets/js/ui/core/positioner.js | 238 ++++ assets/js/ui/core/scroll-manager.js | 178 +++ assets/js/ui/core/state-machine.js | 132 +++ assets/js/ui/core/utils.js | 164 +++ assets/js/ui/index.js | 16 + assets/js/user_socket.js | 46 + assets/tailwind.colors.json | 45 + assets/tailwind.config.js | 19 + assets/tsconfig.json | 32 + assets/vendor/daisyui-theme.js | 124 ++ assets/vendor/daisyui.js | 1031 +++++++++++++++++ assets/vendor/heroicons.js | 43 + assets/vendor/tailwindcss-animate.js | 188 +++ assets/vendor/topbar.js | 138 +++ config/config.exs | 70 ++ config/dev.exs | 92 ++ config/prod.exs | 31 + config/runtime.exs | 120 ++ config/test.exs | 41 + lib/river_connect.ex | 9 + lib/river_connect/application.ex | 36 + lib/river_connect/audio.ex | 104 ++ lib/river_connect/audio/message.ex | 21 + lib/river_connect/mailer.ex | 3 + lib/river_connect/repo.ex | 5 + .../workers/process_recording.ex | 25 + lib/river_connect_web.ex | 114 ++ .../channels/audio_channel.ex | 79 ++ lib/river_connect_web/channels/user_socket.ex | 37 + .../components/core_components.ex | 498 ++++++++ lib/river_connect_web/components/layouts.ex | 154 +++ .../components/layouts/root.html.heex | 36 + lib/river_connect_web/components/ui.ex | 220 ++++ .../components/ui/accordion.ex | 208 ++++ lib/river_connect_web/components/ui/alert.ex | 173 +++ .../components/ui/alert_dialog.ex | 331 ++++++ lib/river_connect_web/components/ui/avatar.ex | 126 ++ lib/river_connect_web/components/ui/badge.ex | 92 ++ .../components/ui/breadcrumb.ex | 295 +++++ lib/river_connect_web/components/ui/button.ex | 73 ++ lib/river_connect_web/components/ui/card.ex | 116 ++ lib/river_connect_web/components/ui/chart.ex | 77 ++ .../components/ui/checkbox.ex | 68 ++ .../components/ui/collapsible.ex | 141 +++ .../components/ui/command.ex | 326 ++++++ lib/river_connect_web/components/ui/dialog.ex | 207 ++++ .../components/ui/dropdown_menu.ex | 462 ++++++++ lib/river_connect_web/components/ui/form.ex | 187 +++ .../components/ui/helpers.ex | 344 ++++++ .../components/ui/hover_card.ex | 173 +++ lib/river_connect_web/components/ui/icon.ex | 30 + lib/river_connect_web/components/ui/input.ex | 71 ++ lib/river_connect_web/components/ui/label.ex | 31 + .../components/ui/live_view.ex | 72 ++ lib/river_connect_web/components/ui/menu.ex | 123 ++ .../components/ui/pagination.ex | 260 +++++ .../components/ui/popover.ex | 159 +++ .../components/ui/progress.ex | 59 + .../components/ui/radio_group.ex | 119 ++ .../components/ui/scroll_area.ex | 46 + lib/river_connect_web/components/ui/select.ex | 280 +++++ .../components/ui/separator.ex | 41 + lib/river_connect_web/components/ui/sheet.ex | 282 +++++ .../components/ui/sidebar.ex | 769 ++++++++++++ .../components/ui/skeleton.ex | 62 + lib/river_connect_web/components/ui/slider.ex | 122 ++ lib/river_connect_web/components/ui/switch.ex | 94 ++ lib/river_connect_web/components/ui/table.ex | 209 ++++ lib/river_connect_web/components/ui/tabs.ex | 154 +++ .../components/ui/textarea.ex | 54 + lib/river_connect_web/components/ui/toggle.ex | 91 ++ .../components/ui/toggle_group.ex | 170 +++ .../components/ui/tooltip.ex | 168 +++ .../controllers/error_html.ex | 24 + .../controllers/error_json.ex | 21 + .../controllers/page_controller.ex | 7 + .../controllers/page_html.ex | 10 + .../controllers/page_html/home.html.heex | 202 ++++ lib/river_connect_web/endpoint.ex | 65 ++ lib/river_connect_web/gettext.ex | 25 + lib/river_connect_web/live/audio_live.ex | 140 +++ lib/river_connect_web/router.ex | 45 + lib/river_connect_web/telemetry.ex | 93 ++ mix.exs | 96 ++ mix.lock | 58 + priv/gettext/en/LC_MESSAGES/errors.po | 112 ++ priv/gettext/errors.pot | 109 ++ priv/repo/migrations/.formatter.exs | 4 + .../20260211193000_create_audio_messages.exs | 14 + .../20260212010838_add_oban_jobs_table.exs | 10 + priv/repo/seeds.exs | 11 + priv/static/favicon.ico | Bin 0 -> 152 bytes priv/static/images/logo.svg | 6 + priv/static/robots.txt | 5 + river_connect_dev.db | Bin 0 -> 20480 bytes river_connect_dev.db-shm | Bin 0 -> 32768 bytes river_connect_dev.db-wal | Bin 0 -> 284312 bytes river_connect_test.db | Bin 0 -> 8192 bytes .../controllers/error_html_test.exs | 14 + .../controllers/error_json_test.exs | 12 + .../controllers/page_controller_test.exs | 8 + test/support/conn_case.ex | 38 + test/support/data_case.ex | 58 + test/test_helper.exs | 2 + 135 files changed, 18014 insertions(+) create mode 100644 .formatter.exs create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 README.md create mode 100644 assets/css/app.css create mode 100644 assets/css/salad_ui.css create mode 100644 assets/js/app.js create mode 100644 assets/js/hooks/audio_recorder.js create mode 100644 assets/js/ui/components/accordion.js create mode 100644 assets/js/ui/components/chart.js create mode 100644 assets/js/ui/components/collapsible.js create mode 100644 assets/js/ui/components/command.js create mode 100644 assets/js/ui/components/dialog.js create mode 100644 assets/js/ui/components/dropdown_menu.js create mode 100644 assets/js/ui/components/hover-card.js create mode 100644 assets/js/ui/components/menu.js create mode 100644 assets/js/ui/components/popover.js create mode 100644 assets/js/ui/components/radio_group.js create mode 100644 assets/js/ui/components/select.js create mode 100644 assets/js/ui/components/slider.js create mode 100644 assets/js/ui/components/switch.js create mode 100644 assets/js/ui/components/tabs.js create mode 100644 assets/js/ui/components/tooltip.js create mode 100644 assets/js/ui/core/click-outside.js create mode 100644 assets/js/ui/core/collection.js create mode 100644 assets/js/ui/core/component.js create mode 100644 assets/js/ui/core/factory.js create mode 100644 assets/js/ui/core/focus-trap.js create mode 100644 assets/js/ui/core/hook.js create mode 100644 assets/js/ui/core/portal.js create mode 100644 assets/js/ui/core/positioned-element.js create mode 100644 assets/js/ui/core/positioner.js create mode 100644 assets/js/ui/core/scroll-manager.js create mode 100644 assets/js/ui/core/state-machine.js create mode 100644 assets/js/ui/core/utils.js create mode 100644 assets/js/ui/index.js create mode 100644 assets/js/user_socket.js create mode 100644 assets/tailwind.colors.json create mode 100644 assets/tailwind.config.js create mode 100644 assets/tsconfig.json create mode 100644 assets/vendor/daisyui-theme.js create mode 100644 assets/vendor/daisyui.js create mode 100644 assets/vendor/heroicons.js create mode 100644 assets/vendor/tailwindcss-animate.js create mode 100644 assets/vendor/topbar.js create mode 100644 config/config.exs create mode 100644 config/dev.exs create mode 100644 config/prod.exs create mode 100644 config/runtime.exs create mode 100644 config/test.exs create mode 100644 lib/river_connect.ex create mode 100644 lib/river_connect/application.ex create mode 100644 lib/river_connect/audio.ex create mode 100644 lib/river_connect/audio/message.ex create mode 100644 lib/river_connect/mailer.ex create mode 100644 lib/river_connect/repo.ex create mode 100644 lib/river_connect/workers/process_recording.ex create mode 100644 lib/river_connect_web.ex create mode 100644 lib/river_connect_web/channels/audio_channel.ex create mode 100644 lib/river_connect_web/channels/user_socket.ex create mode 100644 lib/river_connect_web/components/core_components.ex create mode 100644 lib/river_connect_web/components/layouts.ex create mode 100644 lib/river_connect_web/components/layouts/root.html.heex create mode 100644 lib/river_connect_web/components/ui.ex create mode 100644 lib/river_connect_web/components/ui/accordion.ex create mode 100644 lib/river_connect_web/components/ui/alert.ex create mode 100644 lib/river_connect_web/components/ui/alert_dialog.ex create mode 100644 lib/river_connect_web/components/ui/avatar.ex create mode 100644 lib/river_connect_web/components/ui/badge.ex create mode 100644 lib/river_connect_web/components/ui/breadcrumb.ex create mode 100644 lib/river_connect_web/components/ui/button.ex create mode 100644 lib/river_connect_web/components/ui/card.ex create mode 100644 lib/river_connect_web/components/ui/chart.ex create mode 100644 lib/river_connect_web/components/ui/checkbox.ex create mode 100644 lib/river_connect_web/components/ui/collapsible.ex create mode 100644 lib/river_connect_web/components/ui/command.ex create mode 100644 lib/river_connect_web/components/ui/dialog.ex create mode 100644 lib/river_connect_web/components/ui/dropdown_menu.ex create mode 100644 lib/river_connect_web/components/ui/form.ex create mode 100644 lib/river_connect_web/components/ui/helpers.ex create mode 100644 lib/river_connect_web/components/ui/hover_card.ex create mode 100644 lib/river_connect_web/components/ui/icon.ex create mode 100644 lib/river_connect_web/components/ui/input.ex create mode 100644 lib/river_connect_web/components/ui/label.ex create mode 100644 lib/river_connect_web/components/ui/live_view.ex create mode 100644 lib/river_connect_web/components/ui/menu.ex create mode 100644 lib/river_connect_web/components/ui/pagination.ex create mode 100644 lib/river_connect_web/components/ui/popover.ex create mode 100644 lib/river_connect_web/components/ui/progress.ex create mode 100644 lib/river_connect_web/components/ui/radio_group.ex create mode 100644 lib/river_connect_web/components/ui/scroll_area.ex create mode 100644 lib/river_connect_web/components/ui/select.ex create mode 100644 lib/river_connect_web/components/ui/separator.ex create mode 100644 lib/river_connect_web/components/ui/sheet.ex create mode 100644 lib/river_connect_web/components/ui/sidebar.ex create mode 100644 lib/river_connect_web/components/ui/skeleton.ex create mode 100644 lib/river_connect_web/components/ui/slider.ex create mode 100644 lib/river_connect_web/components/ui/switch.ex create mode 100644 lib/river_connect_web/components/ui/table.ex create mode 100644 lib/river_connect_web/components/ui/tabs.ex create mode 100644 lib/river_connect_web/components/ui/textarea.ex create mode 100644 lib/river_connect_web/components/ui/toggle.ex create mode 100644 lib/river_connect_web/components/ui/toggle_group.ex create mode 100644 lib/river_connect_web/components/ui/tooltip.ex create mode 100644 lib/river_connect_web/controllers/error_html.ex create mode 100644 lib/river_connect_web/controllers/error_json.ex create mode 100644 lib/river_connect_web/controllers/page_controller.ex create mode 100644 lib/river_connect_web/controllers/page_html.ex create mode 100644 lib/river_connect_web/controllers/page_html/home.html.heex create mode 100644 lib/river_connect_web/endpoint.ex create mode 100644 lib/river_connect_web/gettext.ex create mode 100644 lib/river_connect_web/live/audio_live.ex create mode 100644 lib/river_connect_web/router.ex create mode 100644 lib/river_connect_web/telemetry.ex create mode 100644 mix.exs create mode 100644 mix.lock create mode 100644 priv/gettext/en/LC_MESSAGES/errors.po create mode 100644 priv/gettext/errors.pot create mode 100644 priv/repo/migrations/.formatter.exs create mode 100644 priv/repo/migrations/20260211193000_create_audio_messages.exs create mode 100644 priv/repo/migrations/20260212010838_add_oban_jobs_table.exs create mode 100644 priv/repo/seeds.exs create mode 100644 priv/static/favicon.ico create mode 100644 priv/static/images/logo.svg create mode 100644 priv/static/robots.txt create mode 100644 river_connect_dev.db create mode 100644 river_connect_dev.db-shm create mode 100644 river_connect_dev.db-wal create mode 100644 river_connect_test.db create mode 100644 test/river_connect_web/controllers/error_html_test.exs create mode 100644 test/river_connect_web/controllers/error_json_test.exs create mode 100644 test/river_connect_web/controllers/page_controller_test.exs create mode 100644 test/support/conn_case.ex create mode 100644 test/support/data_case.ex create mode 100644 test/test_helper.exs diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..ef8840c --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,6 @@ +[ + import_deps: [:ecto, :ecto_sql, :phoenix], + subdirectories: ["priv/*/migrations"], + plugins: [Phoenix.LiveView.HTMLFormatter], + inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}", "priv/*/seeds.exs"] +] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c12ac35 --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where 3rd-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Temporary files, for example, from tests. +/tmp/ + +# Ignore package tarball (built via "mix hex.build"). +river_connect-*.tar + +# Ignore assets that are produced by build tools. +/priv/static/assets/ + +# Ignore digested assets cache. +/priv/static/cache_manifest.json + +# In case you use Node.js/npm, you want to ignore these. +npm-debug.log +/assets/node_modules/ + diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..0e1fe7a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,449 @@ +This is a web application written using the Phoenix web framework. + +## Project guidelines + +- Use `mix precommit` alias when you are done with all changes and fix any pending issues +- Use the already included and available `:req` (`Req`) library for HTTP requests, **avoid** `:httpoison`, `:tesla`, and `:httpc`. Req is included by default and is the preferred HTTP client for Phoenix apps + +### Phoenix v1.8 guidelines + +- **Always** begin your LiveView templates with `` which wraps all inner content +- The `MyAppWeb.Layouts` module is aliased in the `my_app_web.ex` file, so you can use it without needing to alias it again +- Anytime you run into errors with no `current_scope` assign: + - You failed to follow the Authenticated Routes guidelines, or you failed to pass `current_scope` to `` + - **Always** fix the `current_scope` error by moving your routes to the proper `live_session` and ensure you pass `current_scope` as needed +- Phoenix v1.8 moved the `<.flash_group>` component to the `Layouts` module. You are **forbidden** from calling `<.flash_group>` outside of the `layouts.ex` module +- Out of the box, `core_components.ex` imports an `<.icon name="hero-x-mark" class="w-5 h-5"/>` component for for hero icons. **Always** use the `<.icon>` component for icons, **never** use `Heroicons` modules or similar +- **Always** use the imported `<.input>` component for form inputs from `core_components.ex` when available. `<.input>` is imported and using it will save steps and prevent errors +- If you override the default input classes (`<.input class="myclass px-2 py-1 rounded-lg">)`) class with your own values, no default classes are inherited, so your +custom classes must fully style the input + +### JS and CSS guidelines + +- **Use Tailwind CSS classes and custom CSS rules** to create polished, responsive, and visually stunning interfaces. +- Tailwindcss v4 **no longer needs a tailwind.config.js** and uses a new import syntax in `app.css`: + + @import "tailwindcss" source(none); + @source "../css"; + @source "../js"; + @source "../../lib/my_app_web"; + +- **Always use and maintain this import syntax** in the app.css file for projects generated with `phx.new` +- **Never** use `@apply` when writing raw css +- **Always** manually write your own tailwind-based components instead of using daisyUI for a unique, world-class design +- Out of the box **only the app.js and app.css bundles are supported** + - You cannot reference an external vendor'd script `src` or link `href` in the layouts + - You must import the vendor deps into app.js and app.css to use them + - **Never write inline tags within templates** + +### UI/UX & design guidelines + +- **Produce world-class UI designs** with a focus on usability, aesthetics, and modern design principles +- Implement **subtle micro-interactions** (e.g., button hover effects, and smooth transitions) +- Ensure **clean typography, spacing, and layout balance** for a refined, premium look +- Focus on **delightful details** like hover effects, loading states, and smooth page transitions + + + + + +## Elixir guidelines + +- Elixir lists **do not support index based access via the access syntax** + + **Never do this (invalid)**: + + i = 0 + mylist = ["blue", "green"] + mylist[i] + + Instead, **always** use `Enum.at`, pattern matching, or `List` for index based list access, ie: + + i = 0 + mylist = ["blue", "green"] + Enum.at(mylist, i) + +- Elixir variables are immutable, but can be rebound, so for block expressions like `if`, `case`, `cond`, etc + you *must* bind the result of the expression to a variable if you want to use it and you CANNOT rebind the result inside the expression, ie: + + # INVALID: we are rebinding inside the `if` and the result never gets assigned + if connected?(socket) do + socket = assign(socket, :val, val) + end + + # VALID: we rebind the result of the `if` to a new variable + socket = + if connected?(socket) do + assign(socket, :val, val) + end + +- **Never** nest multiple modules in the same file as it can cause cyclic dependencies and compilation errors +- **Never** use map access syntax (`changeset[:field]`) on structs as they do not implement the Access behaviour by default. For regular structs, you **must** access the fields directly, such as `my_struct.field` or use higher level APIs that are available on the struct if they exist, `Ecto.Changeset.get_field/2` for changesets +- Elixir's standard library has everything necessary for date and time manipulation. Familiarize yourself with the common `Time`, `Date`, `DateTime`, and `Calendar` interfaces by accessing their documentation as necessary. **Never** install additional dependencies unless asked or for date/time parsing (which you can use the `date_time_parser` package) +- Don't use `String.to_atom/1` on user input (memory leak risk) +- Predicate function names should not start with `is_` and should end in a question mark. Names like `is_thing` should be reserved for guards +- Elixir's builtin OTP primitives like `DynamicSupervisor` and `Registry`, require names in the child spec, such as `{DynamicSupervisor, name: MyApp.MyDynamicSup}`, then you can use `DynamicSupervisor.start_child(MyApp.MyDynamicSup, child_spec)` +- Use `Task.async_stream(collection, callback, options)` for concurrent enumeration with back-pressure. The majority of times you will want to pass `timeout: :infinity` as option + +## Mix guidelines + +- Read the docs and options before using tasks (by using `mix help task_name`) +- To debug test failures, run tests in a specific file with `mix test test/my_test.exs` or run all previously failed tests with `mix test --failed` +- `mix deps.clean --all` is **almost never needed**. **Avoid** using it unless you have good reason + +## Test guidelines + +- **Always use `start_supervised!/1`** to start processes in tests as it guarantees cleanup between tests +- **Avoid** `Process.sleep/1` and `Process.alive?/1` in tests + - Instead of sleeping to wait for a process to finish, **always** use `Process.monitor/1` and assert on the DOWN message: + + ref = Process.monitor(pid) + assert_receive {:DOWN, ^ref, :process, ^pid, :normal} + + - Instead of sleeping to synchronize before the next call, **always** use `_ = :sys.get_state/1` to ensure the process has handled prior messages + + + +## Phoenix guidelines + +- Remember Phoenix router `scope` blocks include an optional alias which is prefixed for all routes within the scope. **Always** be mindful of this when creating routes within a scope to avoid duplicate module prefixes. + +- You **never** need to create your own `alias` for route definitions! The `scope` provides the alias, ie: + + scope "/admin", AppWeb.Admin do + pipe_through :browser + + live "/users", UserLive, :index + end + + the UserLive route would point to the `AppWeb.Admin.UserLive` module + +- `Phoenix.View` no longer is needed or included with Phoenix, don't use it + + + +## Ecto Guidelines + +- **Always** preload Ecto associations in queries when they'll be accessed in templates, ie a message that needs to reference the `message.user.email` +- Remember `import Ecto.Query` and other supporting modules when you write `seeds.exs` +- `Ecto.Schema` fields always use the `:string` type, even for `:text`, columns, ie: `field :name, :string` +- `Ecto.Changeset.validate_number/2` **DOES NOT SUPPORT the `:allow_nil` option**. By default, Ecto validations only run if a change for the given field exists and the change value is not nil, so such as option is never needed +- You **must** use `Ecto.Changeset.get_field(changeset, :field)` to access changeset fields +- Fields which are set programatically, such as `user_id`, must not be listed in `cast` calls or similar for security purposes. Instead they must be explicitly set when creating the struct +- **Always** invoke `mix ecto.gen.migration migration_name_using_underscores` when generating migration files, so the correct timestamp and conventions are applied + + + +## Phoenix HTML guidelines + +- Phoenix templates **always** use `~H` or .html.heex files (known as HEEx), **never** use `~E` +- **Always** use the imported `Phoenix.Component.form/1` and `Phoenix.Component.inputs_for/1` function to build forms. **Never** use `Phoenix.HTML.form_for` or `Phoenix.HTML.inputs_for` as they are outdated +- When building forms **always** use the already imported `Phoenix.Component.to_form/2` (`assign(socket, form: to_form(...))` and `<.form for={@form} id="msg-form">`), then access those forms in the template via `@form[:field]` +- **Always** add unique DOM IDs to key elements (like forms, buttons, etc) when writing templates, these IDs can later be used in tests (`<.form for={@form} id="product-form">`) +- For "app wide" template imports, you can import/alias into the `my_app_web.ex`'s `html_helpers` block, so they will be available to all LiveViews, LiveComponent's, and all modules that do `use MyAppWeb, :html` (replace "my_app" by the actual app name) + +- Elixir supports `if/else` but **does NOT support `if/else if` or `if/elsif`**. **Never use `else if` or `elseif` in Elixir**, **always** use `cond` or `case` for multiple conditionals. + + **Never do this (invalid)**: + + <%= if condition do %> + ... + <% else if other_condition %> + ... + <% end %> + + Instead **always** do this: + + <%= cond do %> + <% condition -> %> + ... + <% condition2 -> %> + ... + <% true -> %> + ... + <% end %> + +- HEEx require special tag annotation if you want to insert literal curly's like `{` or `}`. If you want to show a textual code snippet on the page in a `
` or `` block you *must* annotate the parent tag with `phx-no-curly-interpolation`:
+
+      
+        let obj = {key: "val"}
+      
+
+  Within `phx-no-curly-interpolation` annotated tags, you can use `{` and `}` without escaping them, and dynamic Elixir expressions can still be used with `<%= ... %>` syntax
+
+- HEEx class attrs support lists, but you must **always** use list `[...]` syntax. You can use the class list syntax to conditionally add classes, **always do this for multiple class values**:
+
+      Text
+
+  and **always** wrap `if`'s inside `{...}` expressions with parens, like done above (`if(@other_condition, do: "...", else: "...")`)
+
+  and **never** do this, since it's invalid (note the missing `[` and `]`):
+
+       ...
+      => Raises compile syntax error on invalid HEEx attr syntax
+
+- **Never** use `<% Enum.each %>` or non-for comprehensions for generating template content, instead **always** use `<%= for item <- @collection do %>`
+- HEEx HTML comments use `<%!-- comment --%>`. **Always** use the HEEx HTML comment syntax for template comments (`<%!-- comment --%>`)
+- HEEx allows interpolation via `{...}` and `<%= ... %>`, but the `<%= %>` **only** works within tag bodies. **Always** use the `{...}` syntax for interpolation within tag attributes, and for interpolation of values within tag bodies. **Always** interpolate block constructs (if, cond, case, for) within tag bodies using `<%= ... %>`.
+
+  **Always** do this:
+
+      
+ {@my_assign} + <%= if @some_block_condition do %> + {@another_assign} + <% end %> +
+ + and **Never** do this – the program will terminate with a syntax error: + + <%!-- THIS IS INVALID NEVER EVER DO THIS --%> +
+ {if @invalid_block_construct do} + {end} +
+ + + +## Phoenix LiveView guidelines + +- **Never** use the deprecated `live_redirect` and `live_patch` functions, instead **always** use the `<.link navigate={href}>` and `<.link patch={href}>` in templates, and `push_navigate` and `push_patch` functions LiveViews +- **Avoid LiveComponent's** unless you have a strong, specific need for them +- LiveViews should be named like `AppWeb.WeatherLive`, with a `Live` suffix. When you go to add LiveView routes to the router, the default `:browser` scope is **already aliased** with the `AppWeb` module, so you can just do `live "/weather", WeatherLive` + +### LiveView streams + +- **Always** use LiveView streams for collections for assigning regular lists to avoid memory ballooning and runtime termination with the following operations: + - basic append of N items - `stream(socket, :messages, [new_msg])` + - resetting stream with new items - `stream(socket, :messages, [new_msg], reset: true)` (e.g. for filtering items) + - prepend to stream - `stream(socket, :messages, [new_msg], at: -1)` + - deleting items - `stream_delete(socket, :messages, msg)` + +- When using the `stream/3` interfaces in the LiveView, the LiveView template must 1) always set `phx-update="stream"` on the parent element, with a DOM id on the parent element like `id="messages"` and 2) consume the `@streams.stream_name` collection and use the id as the DOM id for each child. For a call like `stream(socket, :messages, [new_msg])` in the LiveView, the template would be: + +
+
+ {msg.text} +
+
+ +- LiveView streams are *not* enumerable, so you cannot use `Enum.filter/2` or `Enum.reject/2` on them. Instead, if you want to filter, prune, or refresh a list of items on the UI, you **must refetch the data and re-stream the entire stream collection, passing reset: true**: + + def handle_event("filter", %{"filter" => filter}, socket) do + # re-fetch the messages based on the filter + messages = list_messages(filter) + + {:noreply, + socket + |> assign(:messages_empty?, messages == []) + # reset the stream with the new messages + |> stream(:messages, messages, reset: true)} + end + +- LiveView streams *do not support counting or empty states*. If you need to display a count, you must track it using a separate assign. For empty states, you can use Tailwind classes: + +
+ +
+ {task.name} +
+
+ + The above only works if the empty state is the only HTML block alongside the stream for-comprehension. + +- When updating an assign that should change content inside any streamed item(s), you MUST re-stream the items + along with the updated assign: + + def handle_event("edit_message", %{"message_id" => message_id}, socket) do + message = Chat.get_message!(message_id) + edit_form = to_form(Chat.change_message(message, %{content: message.content})) + + # re-insert message so @editing_message_id toggle logic takes effect for that stream item + {:noreply, + socket + |> stream_insert(:messages, message) + |> assign(:editing_message_id, String.to_integer(message_id)) + |> assign(:edit_form, edit_form)} + end + + And in the template: + +
+
+ {message.username} + <%= if @editing_message_id == message.id do %> + <%!-- Edit mode --%> + <.form for={@edit_form} id="edit-form-#{message.id}" phx-submit="save_edit"> + ... + + <% end %> +
+
+ +- **Never** use the deprecated `phx-update="append"` or `phx-update="prepend"` for collections + +### LiveView JavaScript interop + +- Remember anytime you use `phx-hook="MyHook"` and that JS hook manages its own DOM, you **must** also set the `phx-update="ignore"` attribute +- **Always** provide an unique DOM id alongside `phx-hook` otherwise a compiler error will be raised + +LiveView hooks come in two flavors, 1) colocated js hooks for "inline" scripts defined inside HEEx, +and 2) external `phx-hook` annotations where JavaScript object literals are defined and passed to the `LiveSocket` constructor. + +#### Inline colocated js hooks + +**Never** write raw embedded ` + +- colocated hooks are automatically integrated into the app.js bundle +- colocated hooks names **MUST ALWAYS** start with a `.` prefix, i.e. `.PhoneNumber` + +#### External phx-hook + +External JS hooks (`
`) must be placed in `assets/js/` and passed to the +LiveSocket constructor: + + const MyHook = { + mounted() { ... } + } + let liveSocket = new LiveSocket("/live", Socket, { + hooks: { MyHook } + }); + +#### Pushing events between client and server + +Use LiveView's `push_event/3` when you need to push events/data to the client for a phx-hook to handle. +**Always** return or rebind the socket on `push_event/3` when pushing events: + + # re-bind socket so we maintain event state to be pushed + socket = push_event(socket, "my_event", %{...}) + + # or return the modified socket directly: + def handle_event("some_event", _, socket) do + {:noreply, push_event(socket, "my_event", %{...})} + end + +Pushed events can then be picked up in a JS hook with `this.handleEvent`: + + mounted() { + this.handleEvent("my_event", data => console.log("from server:", data)); + } + +Clients can also push an event to the server and receive a reply with `this.pushEvent`: + + mounted() { + this.el.addEventListener("click", e => { + this.pushEvent("my_event", { one: 1 }, reply => console.log("got reply from server:", reply)); + }) + } + +Where the server handled it via: + + def handle_event("my_event", %{"one" => 1}, socket) do + {:reply, %{two: 2}, socket} + end + +### LiveView tests + +- `Phoenix.LiveViewTest` module and `LazyHTML` (included) for making your assertions +- Form tests are driven by `Phoenix.LiveViewTest`'s `render_submit/2` and `render_change/2` functions +- Come up with a step-by-step test plan that splits major test cases into small, isolated files. You may start with simpler tests that verify content exists, gradually add interaction tests +- **Always reference the key element IDs you added in the LiveView templates in your tests** for `Phoenix.LiveViewTest` functions like `element/2`, `has_element/2`, selectors, etc +- **Never** tests again raw HTML, **always** use `element/2`, `has_element/2`, and similar: `assert has_element?(view, "#my-form")` +- Instead of relying on testing text content, which can change, favor testing for the presence of key elements +- Focus on testing outcomes rather than implementation details +- Be aware that `Phoenix.Component` functions like `<.form>` might produce different HTML than expected. Test against the output HTML structure, not your mental model of what you expect it to be +- When facing test failures with element selectors, add debug statements to print the actual HTML, but use `LazyHTML` selectors to limit the output, ie: + + html = render(view) + document = LazyHTML.from_fragment(html) + matches = LazyHTML.filter(document, "your-complex-selector") + IO.inspect(matches, label: "Matches") + +### Form handling + +#### Creating a form from params + +If you want to create a form based on `handle_event` params: + + def handle_event("submitted", params, socket) do + {:noreply, assign(socket, form: to_form(params))} + end + +When you pass a map to `to_form/1`, it assumes said map contains the form params, which are expected to have string keys. + +You can also specify a name to nest the params: + + def handle_event("submitted", %{"user" => user_params}, socket) do + {:noreply, assign(socket, form: to_form(user_params, as: :user))} + end + +#### Creating a form from changesets + +When using changesets, the underlying data, form params, and errors are retrieved from it. The `:as` option is automatically computed too. E.g. if you have a user schema: + + defmodule MyApp.Users.User do + use Ecto.Schema + ... + end + +And then you create a changeset that you pass to `to_form`: + + %MyApp.Users.User{} + |> Ecto.Changeset.change() + |> to_form() + +Once the form is submitted, the params will be available under `%{"user" => user_params}`. + +In the template, the form form assign can be passed to the `<.form>` function component: + + <.form for={@form} id="todo-form" phx-change="validate" phx-submit="save"> + <.input field={@form[:field]} type="text" /> + + +Always give the form an explicit, unique DOM ID, like `id="todo-form"`. + +#### Avoiding form errors + +**Always** use a form assigned via `to_form/2` in the LiveView, and the `<.input>` component in the template. In the template **always access forms this**: + + <%!-- ALWAYS do this (valid) --%> + <.form for={@form} id="my-form"> + <.input field={@form[:field]} type="text" /> + + +And **never** do this: + + <%!-- NEVER do this (invalid) --%> + <.form for={@changeset} id="my-form"> + <.input field={@changeset[:field]} type="text" /> + + +- You are FORBIDDEN from accessing the changeset in the template as it will cause errors +- **Never** use `<.form let={f} ...>` in the template, instead **always use `<.form for={@form} ...>`**, then drive all form references from the form assign as in `@form[:field]`. The UI should **always** be driven by a `to_form/2` assigned in the LiveView module that is derived from a changeset + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f68f0e1 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# RiverConnect + +To start your Phoenix server: + +* Run `mix setup` to install and setup dependencies +* Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server` + +Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. + +Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). + +## Learn more + +* Official website: https://www.phoenixframework.org/ +* Guides: https://hexdocs.pm/phoenix/overview.html +* Docs: https://hexdocs.pm/phoenix +* Forum: https://elixirforum.com/c/phoenix-forum +* Source: https://github.com/phoenixframework/phoenix diff --git a/assets/css/app.css b/assets/css/app.css new file mode 100644 index 0000000..e0701a5 --- /dev/null +++ b/assets/css/app.css @@ -0,0 +1,264 @@ +/* See the Tailwind configuration guide for advanced usage + https://tailwindcss.com/docs/configuration */ + +@import "tailwindcss" source(none); +@import "./salad_ui.css"; +@source "../css"; +@source "../js"; +@source "../../lib/river_connect_web"; + +/* A Tailwind plugin that makes "hero-#{ICON}" classes available. + The heroicons installation itself is managed by your mix.exs */ +@plugin "../vendor/heroicons"; + +/* daisyUI Tailwind Plugin. You can update this file by fetching the latest version with: + curl -sLO https://github.com/saadeghi/daisyui/releases/latest/download/daisyui.js + Make sure to look at the daisyUI changelog: https://daisyui.com/docs/changelog/ */ +@plugin "../vendor/daisyui" { + themes: false; +} + +/* daisyUI theme plugin. You can update this file by fetching the latest version with: + curl -sLO https://github.com/saadeghi/daisyui/releases/latest/download/daisyui-theme.js + We ship with two themes, a light one inspired on Phoenix colors and a dark one inspired + on Elixir colors. Build your own at: https://daisyui.com/theme-generator/ */ +@plugin "../vendor/daisyui-theme" { + name: "dark"; + default: false; + prefersdark: true; + color-scheme: "dark"; + --color-base-100: oklch(30.33% 0.016 252.42); + --color-base-200: oklch(25.26% 0.014 253.1); + --color-base-300: oklch(20.15% 0.012 254.09); + --color-base-content: oklch(97.807% 0.029 256.847); + --color-primary: oklch(58% 0.233 277.117); + --color-primary-content: oklch(96% 0.018 272.314); + --color-secondary: oklch(58% 0.233 277.117); + --color-secondary-content: oklch(96% 0.018 272.314); + --color-accent: oklch(60% 0.25 292.717); + --color-accent-content: oklch(96% 0.016 293.756); + --color-neutral: oklch(37% 0.044 257.287); + --color-neutral-content: oklch(98% 0.003 247.858); + --color-info: oklch(58% 0.158 241.966); + --color-info-content: oklch(97% 0.013 236.62); + --color-success: oklch(60% 0.118 184.704); + --color-success-content: oklch(98% 0.014 180.72); + --color-warning: oklch(66% 0.179 58.318); + --color-warning-content: oklch(98% 0.022 95.277); + --color-error: oklch(58% 0.253 17.585); + --color-error-content: oklch(96% 0.015 12.422); + --radius-selector: 0.25rem; + --radius-field: 0.25rem; + --radius-box: 0.5rem; + --size-selector: 0.21875rem; + --size-field: 0.21875rem; + --border: 1.5px; + --depth: 1; + --noise: 0; +} + +@plugin "../vendor/daisyui-theme" { + name: "light"; + default: true; + prefersdark: false; + color-scheme: "light"; + --color-base-100: oklch(98% 0 0); + --color-base-200: oklch(96% 0.001 286.375); + --color-base-300: oklch(92% 0.004 286.32); + --color-base-content: oklch(21% 0.006 285.885); + --color-primary: oklch(70% 0.213 47.604); + --color-primary-content: oklch(98% 0.016 73.684); + --color-secondary: oklch(55% 0.027 264.364); + --color-secondary-content: oklch(98% 0.002 247.839); + --color-accent: oklch(0% 0 0); + --color-accent-content: oklch(100% 0 0); + --color-neutral: oklch(44% 0.017 285.786); + --color-neutral-content: oklch(98% 0 0); + --color-info: oklch(62% 0.214 259.815); + --color-info-content: oklch(97% 0.014 254.604); + --color-success: oklch(70% 0.14 182.503); + --color-success-content: oklch(98% 0.014 180.72); + --color-warning: oklch(66% 0.179 58.318); + --color-warning-content: oklch(98% 0.022 95.277); + --color-error: oklch(58% 0.253 17.585); + --color-error-content: oklch(96% 0.015 12.422); + --radius-selector: 0.25rem; + --radius-field: 0.25rem; + --radius-box: 0.5rem; + --size-selector: 0.21875rem; + --size-field: 0.21875rem; + --border: 1.5px; + --depth: 1; + --noise: 0; +} + +/* Add variants based on LiveView classes */ +@custom-variant phx-click-loading (.phx-click-loading&, .phx-click-loading &); +@custom-variant phx-submit-loading (.phx-submit-loading&, .phx-submit-loading &); +@custom-variant phx-change-loading (.phx-change-loading&, .phx-change-loading &); + +/* Use the data attribute for dark mode */ +@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *)); + +/* Make LiveView wrapper divs transparent for layout */ +[data-phx-session], [data-phx-teleported-src] { display: contents } + +/* This file is for your main application CSS */ + + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 224 71.4% 4.1%; + --card: 0 0% 100%; + --card-foreground: 224 71.4% 4.1%; + --popover: 0 0% 100%; + --popover-foreground: 224 71.4% 4.1%; + --primary: 220.9 39.3% 11%; + --primary-foreground: 210 20% 98%; + --secondary: 220 14.3% 95.9%; + --secondary-foreground: 220.9 39.3% 11%; + --muted: 220 14.3% 95.9%; + --muted-foreground: 220 8.9% 46.1%; + --accent: 220 14.3% 95.9%; + --accent-foreground: 220.9 39.3% 11%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 20% 98%; + --border: 220 13% 91%; + --input: 220 13% 91%; + --ring: 224 71.4% 4.1%; + --radius: 1rem; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --sidebar-background: 0 0% 98%; + --sidebar-foreground: 240 5.3% 26.1%; + --sidebar-primary: 240 5.9% 10%; + --sidebar-primary-foreground: 0 0% 98%; + --sidebar-accent: 240 4.8% 95.9%; + --sidebar-accent-foreground: 240 5.9% 10%; + --sidebar-border: 220 13% 91%; + --sidebar-ring: 217.2 91.2% 59.8%; +} + +.dark { + --background: 224 71.4% 4.1%; + --foreground: 210 20% 98%; + --card: 224 71.4% 4.1%; + --card-foreground: 210 20% 98%; + --popover: 224 71.4% 4.1%; + --popover-foreground: 210 20% 98%; + --primary: 210 20% 98%; + --primary-foreground: 220.9 39.3% 11%; + --secondary: 215 27.9% 16.9%; + --secondary-foreground: 210 20% 98%; + --muted: 215 27.9% 16.9%; + --muted-foreground: 217.9 10.6% 64.9%; + --accent: 215 27.9% 16.9%; + --accent-foreground: 210 20% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 20% 98%; + --border: 215 27.9% 16.9%; + --input: 215 27.9% 16.9%; + --ring: 216 12.2% 83.9%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + --sidebar-background: 240 5.9% 10%; + --sidebar-foreground: 240 4.8% 95.9%; + --sidebar-primary: 224.3 76.3% 48%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 240 3.7% 15.9%; + --sidebar-accent-foreground: 240 4.8% 95.9%; + --sidebar-border: 240 3.7% 15.9%; + --sidebar-ring: 217.2 91.2% 59.8%; +} + + * { + @apply border-border !important; + } +} + + + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 224 71.4% 4.1%; + --card: 0 0% 100%; + --card-foreground: 224 71.4% 4.1%; + --popover: 0 0% 100%; + --popover-foreground: 224 71.4% 4.1%; + --primary: 220.9 39.3% 11%; + --primary-foreground: 210 20% 98%; + --secondary: 220 14.3% 95.9%; + --secondary-foreground: 220.9 39.3% 11%; + --muted: 220 14.3% 95.9%; + --muted-foreground: 220 8.9% 46.1%; + --accent: 220 14.3% 95.9%; + --accent-foreground: 220.9 39.3% 11%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 20% 98%; + --border: 220 13% 91%; + --input: 220 13% 91%; + --ring: 224 71.4% 4.1%; + --radius: 1rem; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --sidebar-background: 0 0% 98%; + --sidebar-foreground: 240 5.3% 26.1%; + --sidebar-primary: 240 5.9% 10%; + --sidebar-primary-foreground: 0 0% 98%; + --sidebar-accent: 240 4.8% 95.9%; + --sidebar-accent-foreground: 240 5.9% 10%; + --sidebar-border: 220 13% 91%; + --sidebar-ring: 217.2 91.2% 59.8%; +} + +.dark { + --background: 224 71.4% 4.1%; + --foreground: 210 20% 98%; + --card: 224 71.4% 4.1%; + --card-foreground: 210 20% 98%; + --popover: 224 71.4% 4.1%; + --popover-foreground: 210 20% 98%; + --primary: 210 20% 98%; + --primary-foreground: 220.9 39.3% 11%; + --secondary: 215 27.9% 16.9%; + --secondary-foreground: 210 20% 98%; + --muted: 215 27.9% 16.9%; + --muted-foreground: 217.9 10.6% 64.9%; + --accent: 215 27.9% 16.9%; + --accent-foreground: 210 20% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 20% 98%; + --border: 215 27.9% 16.9%; + --input: 215 27.9% 16.9%; + --ring: 216 12.2% 83.9%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + --sidebar-background: 240 5.9% 10%; + --sidebar-foreground: 240 4.8% 95.9%; + --sidebar-primary: 224.3 76.3% 48%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 240 3.7% 15.9%; + --sidebar-accent-foreground: 240 4.8% 95.9%; + --sidebar-border: 240 3.7% 15.9%; + --sidebar-ring: 217.2 91.2% 59.8%; +} + + * { + @apply border-border !important; + } +} + diff --git a/assets/css/salad_ui.css b/assets/css/salad_ui.css new file mode 100644 index 0000000..1795a01 --- /dev/null +++ b/assets/css/salad_ui.css @@ -0,0 +1,39 @@ +/* Scroll area */ + +/* firefox */ +.salad-scroll-area { + scrollbar-width: thin; + scrollbar-color: transparent transparent; +} + +.salad-scroll-area:hover { + scrollbar-color: #c2c2c2 transparent; +} + +/* Chrome, Edge, and Safari */ +.salad-scroll-area::-webkit-scrollbar { + width: 6px; + background-color: transparent; +} + +.salad-scroll-area::-webkit-scrollbar-track { + background: transparent; + border-radius: 5px; +} + +.salad-scroll-area::-webkit-scrollbar-thumb { + background-color: #c2c2c2; + border-radius: 14px; +} + +.animate-indeterminate-progress { + animation: indeterminate-progress 1.5s infinite linear; + background: linear-gradient( + to right, + transparent, + currentColor, + transparent + ); + background-size: 200% 100%; + width: 100%; +} diff --git a/assets/js/app.js b/assets/js/app.js new file mode 100644 index 0000000..5c0b314 --- /dev/null +++ b/assets/js/app.js @@ -0,0 +1,98 @@ +// If you want to use Phoenix channels, run `mix help phx.gen.channel` +// to get started and then uncomment the line below. +// import "./user_socket.js" + +// You can include dependencies in two ways. +// +// The simplest option is to put them in assets/vendor and +// import them using relative paths: +// +// import "../vendor/some-package.js" +// +// Alternatively, you can `npm install some-package --prefix assets` and import +// them using a path starting with the package name: +// +// import "some-package" +// +// If you have dependencies that try to import CSS, esbuild will generate a separate `app.css` file. +// To load it, simply add a second `` to your `root.html.heex` file. + +// Include phoenix_html to handle method=PUT/DELETE in forms and buttons. +import "phoenix_html" +// Establish Phoenix Socket and LiveView configuration. +import { Socket } from "phoenix" +import { LiveSocket } from "phoenix_live_view" +import { hooks as colocatedHooks } from "phoenix-colocated/river_connect" +import topbar from "../vendor/topbar" + +import { AudioRecorder } from "./hooks/audio_recorder" +import SaladUI from "./ui/index.js"; +import "./ui/components/dialog.js"; +import "./ui/components/select.js"; +import "./ui/components/tabs.js"; +import "./ui/components/radio_group.js"; +import "./ui/components/popover.js"; +import "./ui/components/hover-card.js"; +import "./ui/components/collapsible.js"; +import "./ui/components/tooltip.js"; +import "./ui/components/accordion.js"; +import "./ui/components/slider.js"; +import "./ui/components/switch.js"; +import "./ui/components/dropdown_menu.js"; + +const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") +const liveSocket = new LiveSocket("/live", Socket, { + longPollFallbackMs: 2500, + params: { _csrf_token: csrfToken }, + hooks: { ...colocatedHooks, AudioRecorder }, +}) + +// Show progress bar on live navigation and form submits +topbar.config({ barColors: { 0: "#29d" }, shadowColor: "rgba(0, 0, 0, .3)" }) +window.addEventListener("phx:page-loading-start", _info => topbar.show(300)) +window.addEventListener("phx:page-loading-stop", _info => topbar.hide()) + +// connect if there are any LiveViews on the page +liveSocket.connect() + +// expose liveSocket on window for web console debug logs and latency simulation: +// >> liveSocket.enableDebug() +// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session +// >> liveSocket.disableLatencySim() +window.liveSocket = liveSocket + +// The lines below enable quality of life phoenix_live_reload +// development features: +// +// 1. stream server logs to the browser console +// 2. click on elements to jump to their definitions in your code editor +// +if (process.env.NODE_ENV === "development") { + window.addEventListener("phx:live_reload:attached", ({ detail: reloader }) => { + // Enable server log streaming to client. + // Disable with reloader.disableServerLogs() + reloader.enableServerLogs() + + // Open configured PLUG_EDITOR at file:line of the clicked element's HEEx component + // + // * click with "c" key pressed to open at caller location + // * click with "d" key pressed to open at function component definition location + let keyDown + window.addEventListener("keydown", e => keyDown = e.key) + window.addEventListener("keyup", _e => keyDown = null) + window.addEventListener("click", e => { + if (keyDown === "c") { + e.preventDefault() + e.stopImmediatePropagation() + reloader.openEditorAtCaller(e.target) + } else if (keyDown === "d") { + e.preventDefault() + e.stopImmediatePropagation() + reloader.openEditorAtDef(e.target) + } + }, true) + + window.liveReloader = reloader + }) +} + diff --git a/assets/js/hooks/audio_recorder.js b/assets/js/hooks/audio_recorder.js new file mode 100644 index 0000000..0640a30 --- /dev/null +++ b/assets/js/hooks/audio_recorder.js @@ -0,0 +1,169 @@ +import socket from "../user_socket" + +export const AudioRecorder = { + mounted() { + this.mediaRecorder = null; + this.isRecording = false; + this.audioCtx = new (window.AudioContext || window.webkitAudioContext)(); + this.queue = []; + this.sourceBuffer = null; + this.mediaSource = null; + this.liveAudio = new Audio(); + this.liveAudio.autoplay = true; + + // Join channel + this.channel = socket.channel("audio:lobby", {}) + this.channel.join() + .receive("ok", resp => { console.log("Joined audio lobby", resp) }) + .receive("error", resp => { console.error("Unable to join audio lobby", resp) }) + + // Listen for start of others' audio + this.channel.on("audio_start", (payload) => { + this.prepareLivePlayback(); + }); + + // Listen for incoming chunks (others talking) + this.channel.on("audio_chunk", (payload) => { + this.queueChunk(payload.data) + }) + + // UI Events + const btn = this.el.querySelector("button#record-btn"); + + btn.addEventListener("click", (e) => { + e.preventDefault(); + if (this.isRecording) { + this.stopRecording(); + } else { + this.startRecording(crypto.randomUUID()); + } + }); + + // External event to play live stream (from UI button) + this.el.addEventListener("play_live", () => { + console.log("Play Live triggered"); + console.log("MediaSource state:", this.mediaSource ? this.mediaSource.readyState : "null"); + console.log("SourceBuffer state:", this.sourceBuffer ? (this.sourceBuffer.updating ? "updating" : "ready") : "null"); + console.log("Buffer Queue length:", this.queue.length); + + if (this.liveAudio) { + console.log("Attempting to play live audio..."); + this.liveAudio.play() + .then(() => console.log("Live audio playing started")) + .catch(err => console.error("Error playing live audio", err)); + } else { + console.error("Live audio object not initialized"); + } + }); + }, + + prepareLivePlayback() { + console.log("Preparing live playback MediaSource..."); + this.queue = []; + this.mediaSource = new MediaSource(); + this.liveAudio.src = URL.createObjectURL(this.mediaSource); + + this.mediaSource.addEventListener('sourceopen', () => { + console.log("MediaSource open"); + try { + if (this.mediaSource.sourceBuffers.length > 0) return; + + this.sourceBuffer = this.mediaSource.addSourceBuffer('audio/webm; codecs="opus"'); + this.sourceBuffer.mode = 'sequence'; + + this.sourceBuffer.addEventListener('updateend', () => { + this.processQueue(); + }); + console.log("SourceBuffer added and mode set to sequence"); + } catch (e) { + console.error("Error adding SourceBuffer", e); + } + }); + }, + + queueChunk(base64) { + // console.log("Received chunk, queueing..."); + const binaryString = window.atob(base64); + const len = binaryString.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + this.queue.push(bytes); + this.processQueue(); + }, + + processQueue() { + if (!this.sourceBuffer) { + // console.warn("No SourceBuffer available to process queue"); + return; + } + if (this.sourceBuffer.updating) { + // console.log("SourceBuffer updating, waiting..."); + return; + } + if (this.queue.length === 0) { + return; + } + + const chunk = this.queue.shift(); + try { + this.sourceBuffer.appendBuffer(chunk); + // console.log("Appended buffer. Queue size:", this.queue.length); + } catch (e) { + console.error("Error appending to SourceBuffer", e); + } + }, + + startRecording(id) { + console.log("Starting recording...", id); + if (this.isRecording) return; + this.isRecording = true; + this.currentId = id; + this.startTime = Date.now(); + this.el.setAttribute("data-recording", "true"); + + // Notify channel we are starting + this.channel.push("audio_start", { id: this.currentId }); + + navigator.mediaDevices.getUserMedia({ audio: true }) + .then(stream => { + console.log("Microphone access granted"); + this.mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm;codecs=opus' }); + + this.mediaRecorder.ondataavailable = (e) => { + if (e.data.size > 0 && this.channel) { + const reader = new FileReader(); + reader.onload = () => { + this.channel.push("audio_chunk", reader.result); + }; + reader.readAsArrayBuffer(e.data); + } + }; + + this.mediaRecorder.start(100); // chunk every 100ms + this.pushEvent("recording_started", { id }); + }) + .catch(err => { + console.error("Error accessing microphone", err); + this.isRecording = false; + }); + }, + + stopRecording() { + console.log("Stopping recording..."); + if (!this.isRecording) return; + this.isRecording = false; + this.el.removeAttribute("data-recording"); + + if (this.mediaRecorder && this.mediaRecorder.state !== "inactive") { + this.mediaRecorder.stop(); + this.mediaRecorder.stream.getTracks().forEach(track => track.stop()); + } + + const duration = Date.now() - this.startTime; + this.channel.push("audio_end", { id: this.currentId, duration_ms: duration }); + this.pushEvent("recording_stopped", { id: this.currentId, duration: duration }); + } +} diff --git a/assets/js/ui/components/accordion.js b/assets/js/ui/components/accordion.js new file mode 100644 index 0000000..2d7c0dd --- /dev/null +++ b/assets/js/ui/components/accordion.js @@ -0,0 +1,241 @@ +// saladui/components/accordion.js +import Component from "../core/component"; +import Collection from "../core/collection"; +import SaladUI from "../index"; + +/** + * AccordionItem class to manage individual accordion items + * Handles state transitions and events for a single accordion item + */ +class AccordionItem extends Component { + constructor(itemElement, parentComponent, options) { + const { initialState = "closed" } = options || {}; + super(itemElement, { initialState, ignoreItems: false }); + this.parent = parentComponent; + this.value = itemElement.dataset.value; + this.disabled = itemElement.dataset.disabled === "true"; + + this.trigger = itemElement.querySelector("[data-part='item-trigger']"); + this.content = itemElement.querySelector("[data-part='item-content']"); + this.initialize(); + this.setupEvents(); + } + + getComponentConfig() { + return { + stateMachine: { + closed: { + transitions: { + open: "open", + }, + }, + open: { + transitions: { + close: "closed", + }, + }, + }, + events: { + closed: { + mouseMap: { + "item-trigger": { + click: "handleTriggerActivation", + }, + }, + keyMap: { + Enter: "handleTriggerActivation", + " ": "handleTriggerActivation", + }, + }, + open: { + mouseMap: { + "item-trigger": { + click: "handleTriggerActivation", + }, + }, + keyMap: { + Enter: "handleTriggerActivation", + " ": "handleTriggerActivation", + }, + }, + }, + hiddenConfig: { + closed: { + "item-content": true, + }, + open: { + "item-content": false, + }, + }, + ariaConfig: { + "item-trigger": { + all: { + controls: () => this.content?.id, + }, + open: { + expanded: "true", + }, + closed: { + expanded: "false", + }, + }, + "item-content": { + all: { + labelledby: () => this.trigger?.id, + }, + }, + }, + }; + } + + initialize() { + if (this.disabled) { + this.trigger.setAttribute("tabindex", "-1"); + } else { + this.trigger.setAttribute("tabindex", "0"); + } + } + + handleEvent(eventType) { + switch (eventType) { + case "select": + return this.transition("open"); + case "unselect": + return this.transition("close"); + case "focus": + if (this.trigger && !this.disabled) { + this.trigger.focus(); + } + return true; + case "blur": + return true; + } + } + + handleTriggerActivation(event) { + event.preventDefault(); + if (!this.disabled && !this.parent.disabled) { + this.parent.toggleItem(this); + } + } +} + +/** + * AccordionComponent class for SaladUI framework + * Manages a collection of accordion items with state transitions + */ +class AccordionComponent extends Component { + constructor(el, hookContext) { + super(el, { hookContext }); + + // Initialize properties + this.type = this.options.type || "single"; + this.disabled = this.options.disabled || false; + + // Initialize collection manager + this.collection = new Collection({ + type: this.type, + defaultValue: this.options.defaultValue, + value: this.options.value, + getItemValue: (item) => item.value, + isItemDisabled: (item) => item.disabled || this.disabled, + }); + + // Set keyboard navigation defaults + this.config.preventDefaultKeys = [ + "ArrowUp", + "ArrowDown", + "Home", + "End", + "Enter", + " ", + ]; + + // Initialize accordion items + this.initializeItems(); + } + + getComponentConfig() { + return { + stateMachine: { + idle: { + enter: () => {}, + exit: () => {}, + transitions: {}, + }, + }, + events: { + idle: { + keyMap: { + ArrowUp: () => this.navigateItem("prev"), + ArrowDown: () => this.navigateItem("next"), + Home: () => this.navigateItem("first"), + End: () => this.navigateItem("last"), + }, + }, + }, + }; + } + + initializeItems() { + const itemElements = Array.from( + this.el.querySelectorAll("[data-part='item']"), + ); + + this.items = itemElements.map((element) => { + // Initialize AccordionItem without hook context + const itemValue = element.dataset.value; + element.id = `${this.el.id}-item-${itemValue}`; + + // Check if this item is initially open + const isOpen = this.collection.getValue(true).includes(itemValue); + const item = new AccordionItem(element, this, { + initialState: isOpen ? "open" : "closed", + }); + this.collection.add(item); + return item; + }); + } + + toggleItem(item) { + const collectionItem = this.collection.getItemByInstance(item); + if (!collectionItem) return; + + // Toggle item selection + this.collection.select(collectionItem); + + // Emit event with current value + const value = this.collection.getValue(); + this.pushEvent("value-changed", { value }); + } + + navigateItem(direction) { + const currentFocus = document.activeElement; + const currentItemElement = currentFocus?.closest("[data-part='item']"); + let currentItem = null; + + if (currentItemElement) { + currentItem = this.items.find((item) => item.el === currentItemElement); + } + + let referenceCollectionItem = null; + if (currentItem) { + referenceCollectionItem = this.collection.getItemByInstance(currentItem); + } + + // Get the target item using collection manager's navigation methods + const targetItem = this.collection.getItem( + direction, + referenceCollectionItem, + ); + + if (targetItem) { + this.collection.focus(targetItem); + } + } +} + +// Register the component +SaladUI.register("accordion", AccordionComponent); + +export default AccordionComponent; diff --git a/assets/js/ui/components/chart.js b/assets/js/ui/components/chart.js new file mode 100644 index 0000000..be18602 --- /dev/null +++ b/assets/js/ui/components/chart.js @@ -0,0 +1,98 @@ +// saladui/components/chart.js +import Component from "../core/component"; +import SaladUI from "../index"; +import Chart from "chart.js/auto"; + +function cssvar(name) { + return getComputedStyle(document.documentElement).getPropertyValue(name); +} + +const RESERVED_CONFIG_KEYS = ["labels", "type", "options"]; +const RESERVED_DATASET_KEYS = ["datakey"]; +const DEFAULT_CHART_TYPE = "line"; + +class ChartComponent extends Component { + constructor(el, hookContext) { + super(el, { hookContext }); + this.chartOptions = JSON.parse(this.el.dataset.chartOptions || "{}"); + this.chartType = this.el.dataset.chartType || DEFAULT_CHART_TYPE; + this.initializeChart(); + } + + getComponentConfig() { + return { + stateMachine: { + idle: { + transitions: { select: "idle" }, + }, + }, + }; + } + + handleCommand(command, params) { + if (command === "update") { + this.updateChart(params); + return true; + } + } + + initializeChart() { + const data = JSON.parse(this.el.dataset.chartData); + + this.chart = new Chart(this.el, { + type: this.chartType, + data: data, + options: this.chartOptions, + }); + } + + updateChart(payload) { + if (payload.data) { + this.chart.data = { ...this.chart.data, ...payload.data }; + } + + if (payload.options) { + this.chartOptions = { ...this.chartOptions, ...payload.options }; + this.chart.options = this.chartOptions; + } + + this.chart.update(); + } + + extractDatasets(config) { + return Object.entries(config) + .filter(([key]) => !RESERVED_CONFIG_KEYS.includes(key)) + .map(([, value]) => { + const dataset = { ...value }; + + Object.keys(dataset).forEach((key) => { + // support css variables for colors + if (dataset[key].includes("var(--")) { + const colorName = dataset[key].split("--")[1].split(")")[0].trim(); + dataset[key] = dataset[key].replace( + `var(--${colorName})`, + cssvar(`--${colorName}`), + ); + } + }); + + return Object.fromEntries( + Object.entries(dataset).filter( + ([key]) => !RESERVED_DATASET_KEYS.includes(key), + ), + ); + }); + } + + destroy() { + if (this.chart) { + this.chart.destroy(); + this.chart = null; + } + } +} + +// Register the component +SaladUI.register("chart", ChartComponent); + +export default ChartComponent; diff --git a/assets/js/ui/components/collapsible.js b/assets/js/ui/components/collapsible.js new file mode 100644 index 0000000..dff9dec --- /dev/null +++ b/assets/js/ui/components/collapsible.js @@ -0,0 +1,92 @@ +// saladui/components/collapsible.js +import Component from "../core/component"; +import SaladUI from "../index"; + +class CollapsibleComponent extends Component { + constructor(el, hookContext) { + super(el, { hookContext }); + + // Initialize core properties + this.trigger = this.getPart("trigger"); + this.content = this.getPart("content"); + + // Set keyboard navigation defaults + this.config.preventDefaultKeys = ["Enter", " "]; + } + + getComponentConfig() { + return { + stateMachine: { + closed: { + enter: "onClosedEnter", + transitions: { + toggle: "open", + open: "open", + }, + }, + open: { + enter: "onOpenEnter", + transitions: { + toggle: "closed", + close: "closed", + }, + }, + }, + events: { + closed: { + keyMap: { + Enter: "toggle", + " ": "toggle", + }, + }, + open: { + keyMap: { + Enter: "toggle", + " ": "toggle", + }, + }, + }, + hiddenConfig: { + closed: { + content: true, + }, + open: { + content: false, + }, + }, + ariaConfig: { + trigger: { + all: { + controls: () => this.getPartId("content"), + }, + open: { + expanded: "true", + }, + closed: { + expanded: "false", + }, + }, + content: { + all: { + labelledby: () => this.getPartId("trigger"), + role: "region", + }, + }, + }, + }; + } + + // State handlers + onOpenEnter() { + this.pushEvent("opened"); + } + + onClosedEnter() { + this.pushEvent("closed"); + } +} + +// Register the component +SaladUI.register("collapsible", CollapsibleComponent); + +export default CollapsibleComponent; diff --git a/assets/js/ui/components/command.js b/assets/js/ui/components/command.js new file mode 100644 index 0000000..95cb5fd --- /dev/null +++ b/assets/js/ui/components/command.js @@ -0,0 +1,129 @@ +import Component from "../core/component"; +import SaladUI from ".."; + +/** + * CommandComponent for SaladUI + * Implements filtering, keyboard navigation, and selection for a command palette/list. + */ +class CommandComponent extends Component { + constructor(el, hookContext) { + super(el, { hookContext, ignoreItems: false }); + + // Set default field state + this.currentItemIdx = 0; + + // Core elements + this.input = this.getPart("input"); + this.list = this.getPart("list"); + this.empty = this.getPart("empty"); + this.groups = this.getAllParts("group"); + this.items = this.getAllParts("item"); + + // Bind event handlers + this.input.addEventListener("input", this.handleSearch); + + // Initial search/filter + this.handleSearch(); + + // Prevent default for navigation keys + this.config.preventDefaultKeys = ["Escape", "ArrowDown", "ArrowUp"]; + } + + getComponentConfig() { + return { + stateMachine: { + idle: { transitions: {} }, + }, + events: { + idle: { + keyMap: { + Enter: "selectItem", + ArrowDown: "focusNextItem", + ArrowUp: "focusPrevItem", + Escape: "blurInput", + }, + }, + }, + }; + } + + // Focus item by index, wrap around if needed + focusItem(index) { + if (!this.selectableItems?.length) return; + + // Wrap index + if (index < 0) index = this.selectableItems.length - 1; + if (index >= this.selectableItems.length) index = 0; + + this.currentItemIdx = index; + + // Deselect all + this.items.forEach((item) => { + item.setAttribute("data-selected", "false"); + item.setAttribute("aria-selected", "false"); + }); + + // Select current + const selectedItem = this.selectableItems[index]; + selectedItem.setAttribute("data-selected", "true"); + selectedItem.setAttribute("aria-selected", "true"); + } + + focusNextItem = () => this.focusItem(this.currentItemIdx + 1); + focusPrevItem = () => this.focusItem(this.currentItemIdx - 1); + + blurInput = () => this.input?.blur(); + + selectItem = () => { + if (this.currentItemIdx === -1) return; + const item = this.selectableItems[this.currentItemIdx]; + item.click(); + }; + + // Handle search/filtering + handleSearch = () => { + const query = this.input.value.trim().toLowerCase(); + + // Filter items + this.items.forEach((item) => { + const text = item.textContent.trim().toLowerCase(); + const visible = query === "" || text.includes(query); + item.setAttribute("data-visible", visible ? "true" : "false"); + }); + + this.visibleItems = this.items.filter( + (el) => el.getAttribute("data-visible") === "true", + ); + + this.selectableItems = this.visibleItems.filter( + (el) => !el.hasAttribute("disabled"), + ); + + this.groups.forEach((group) => { + const visibleOptions = group.querySelectorAll("[data-visible='true']"); + group.setAttribute( + "data-visible", + visibleOptions.length > 0 ? "true" : "false", + ); + }); + + this.focusItem(0); + + const noItems = this.visibleItems.length === 0; + + if (noItems) { + this.empty.setAttribute("data-visible", "true"); + } else { + this.empty.setAttribute("data-visible", "false"); + } + }; + + beforeDestroy() { + this.input.removeEventListener("input", this.handleSearch); + } +} + +// Register the component +SaladUI.register("command", CommandComponent); + +export default CommandComponent; diff --git a/assets/js/ui/components/dialog.js b/assets/js/ui/components/dialog.js new file mode 100644 index 0000000..f23c067 --- /dev/null +++ b/assets/js/ui/components/dialog.js @@ -0,0 +1,165 @@ +// saladui/components/dialog.js +import Component from "../core/component"; +import SaladUI from "../index"; +import FocusTrap from "../core/focus-trap"; +import ClickOutsideMonitor from "../core/click-outside"; + +class DialogComponent extends Component { + constructor(el, hookContext) { + super(el, { hookContext, initialState: "closed" }); + + // Initialize properties + this.root = this.el; + this.content = this.getPart("content"); + this.contentPanel = this.getPart("content-panel"); + this.config.preventDefaultKeys = ["Escape"]; + + this.setupEvents(); + this.transition(this.el.dataset.open == "true" ? "open" : "close"); + } + + getComponentConfig() { + return { + stateMachine: { + closed: { + enter: "onClosedEnter", + exit: "onClosedExit", + transitions: { + open: "open", + }, + }, + open: { + enter: "onOpenEnter", + transitions: { + close: "closed", + }, + }, + }, + events: { + closed: { + keyMap: {}, + }, + open: { + keyMap: { + Escape: "close", + }, + }, + }, + hiddenConfig: { + closed: { + content: true, + }, + open: { + content: false, + }, + }, + ariaConfig: { + content: { + all: { + role: "dialog", + }, + open: { + hidden: "false", + modal: "true", + }, + closed: { + hidden: "true", + }, + }, + "content-panel": { + open: { + labelledby: () => this.getPartId("title"), + describedby: () => this.getPartId("description"), + }, + }, + "close-trigger": { + all: { + label: "Close dialog", + }, + }, + }, + }; + } + + // Setup component events + setupComponentEvents() { + super.setupComponentEvents(); + + // Only setup click handler if closeOnOutsideClick is enabled + if (this.options.closeOnOutsideClick) { + this.setupOutsideClickDetection(); + } + } + + setupOutsideClickDetection() { + // Create click outside monitor to handle clicks on the overlay + this.clickOutsideMonitor = new ClickOutsideMonitor( + [this.contentPanel], + (event) => { + // Only close if click was directly on the content container (overlay area) + if ( + event.target === this.content || + event.target.dataset.part === "overlay" + ) { + this.transition("close"); + } + }, + ); + } + + // State machine handlers + onClosedEnter() { + // Clean up focus trap + if (this.focusTrap) { + this.focusTrap.deactivate(); + } + + // Clean up click outside monitor + if (this.clickOutsideMonitor) { + this.clickOutsideMonitor.stop(); + } + + // Notify the server of the state change + this.pushEvent("closed"); + } + + onClosedExit() { + // No special handling needed + } + + onOpenEnter() { + // Initialize focus trap if not already created + this.el.focus(); + if (!this.focusTrap) { + this.focusTrap = new FocusTrap(this.contentPanel); + } + + // Activate focus trap + this.focusTrap.activate(); + + // Activate click outside monitor if enabled + if (this.clickOutsideMonitor) { + this.clickOutsideMonitor.start(); + } + + // Setup escape key handling - now handled by the component's keyMap + + // Notify the server of the state change + this.pushEvent("opened"); + } + + beforeDestroy() { + // Clean up focus trap + this.focusTrap?.destroy(); + this.focusTrap = null; + + // Clean up click outside monitor + this.clickOutsideMonitor?.destroy(); + this.clickOutsideMonitor = null; + } +} + +// Register the component +SaladUI.register("dialog", DialogComponent); + +export default DialogComponent; diff --git a/assets/js/ui/components/dropdown_menu.js b/assets/js/ui/components/dropdown_menu.js new file mode 100644 index 0000000..66d6c96 --- /dev/null +++ b/assets/js/ui/components/dropdown_menu.js @@ -0,0 +1,175 @@ +// saladui/components/dropdown_menu.js +import Component from "../core/component"; +import SaladUI from "../index"; +import PositionedElement from "../core/positioned-element"; +import Menu from "./menu"; + +/** + * DropdownMenuComponent class for SaladUI framework + * Manages a dropdown menu with support for keyboard navigation and accessibility + */ +class DropdownMenuComponent extends Component { + constructor(el, hookContext) { + super(el, { hookContext }); + + // Initialize core properties + this.trigger = this.getPart("trigger"); + this.positioner = this.getPart("positioner"); + this.content = this.positioner.querySelector("[data-part='content']"); + + this.menu = new Menu(this.content, { + hookContext, + onItemSelect: this.onItemSelect.bind(this), + }); + + // Set keyboard navigation defaults + this.config.preventDefaultKeys = ["Escape", "ArrowDown", " ", "Enter"]; + } + + getComponentConfig() { + return { + stateMachine: { + closed: { + enter: "onClosedEnter", + transitions: { + open: "open", + toggle: "open", + }, + }, + open: { + enter: "onOpenEnter", + transitions: { + close: "closed", + toggle: "closed", + }, + }, + }, + events: { + closed: { + keyMap: { + ArrowDown: "open", + " ": "open", + Enter: "open", + }, + mouseMap: { + trigger: { + click: (_e) => { + this.transition("open"); + }, + }, + }, + }, + open: { + keyMap: { + Escape: "close", + }, + }, + }, + hiddenConfig: { + closed: { + positioner: true, // Hide the positioner in closed state + }, + open: { + positioner: false, // Show the positioner in open state + }, + }, + ariaConfig: { + trigger: { + all: { + haspopup: "menu", + controls: () => + this.content ? this.content.id || `${this.el.id}-content` : null, + }, + open: { + expanded: "true", + }, + closed: { + expanded: "false", + }, + }, + content: { + all: { + role: "menu", + }, + }, + }, + }; + } + + initializePositionedElement() { + if (this.positioner && this.trigger && !this.positionedElement) { + const side = this.positioner.getAttribute("data-side") || "bottom"; + const align = this.positioner.getAttribute("data-align") || "start"; + const sideOffset = parseInt( + this.positioner.getAttribute("data-side-offset") || "4", + 10, + ); + const alignOffset = parseInt( + this.positioner.getAttribute("data-align-offset") || "0", + 10, + ); + + // Get portal options + const usePortal = this.options.usePortal === true; + let portalContainer = null; + if (this.options.portalContainer) { + portalContainer = document.querySelector(this.options.portalContainer); + } + + this.positionedElement = new PositionedElement( + this.positioner, + this.trigger, + { + placement: side, + alignment: align, + sideOffset, + alignOffset, + flip: true, + usePortal, + portalContainer: portalContainer || document.body, + trapFocus: false, + onOutsideClick: () => this.transition("close"), + }, + ); + } + } + + onOpenEnter() { + this.previousFocusEl = document.activeElement; + + this.initializePositionedElement(); + this.positionedElement?.activate(); + this.menu.activate(); + this.pushEvent("opened"); + } + + onClosedEnter() { + this.positionedElement?.deactivate(); + this.pushEvent("closed"); + this.previousFocusEl?.focus(); + this.previousFocusEl = null; + } + + onItemSelect(_item) { + this.transition("close"); + } + + beforeDestroy() { + // Clean up the positioned element + if (this.positionedElement) { + this.positionedElement.destroy(); + this.positionedElement = null; + } + + // Clean up menu items + if (this.menu) { + this.menu.destroy(); + this.menu = null; + } + } +} + +// Register the component +SaladUI.register("dropdown-menu", DropdownMenuComponent); + +export default DropdownMenuComponent; diff --git a/assets/js/ui/components/hover-card.js b/assets/js/ui/components/hover-card.js new file mode 100644 index 0000000..522e754 --- /dev/null +++ b/assets/js/ui/components/hover-card.js @@ -0,0 +1,205 @@ +// saladui/components/hover_card.js +import Component from "../core/component"; +import SaladUI from "../index"; +import PositionedElement from "../core/positioned-element"; + +// Define constants at the module level outside the class +const DEFAULT_POSITION_CONFIG = { + placement: "top", + alignment: "center", + sideOffset: 8, + alignOffset: 0, +}; + +const DEFAULT_TIMING_CONFIG = { + openDelay: 300, + closeDelay: 200, +}; + +class HoverCardComponent extends Component { + constructor(el, hookContext) { + super(el, { hookContext }); + + // Initialize core properties + this.trigger = this.getPart("trigger"); + this.content = this.getPart("content"); + + // Set config from options with fallbacks to defaults + this.config.openDelay = + this.options.openDelay || DEFAULT_TIMING_CONFIG.openDelay; + this.config.closeDelay = + this.options.closeDelay || DEFAULT_TIMING_CONFIG.closeDelay; + + // Track timer IDs for delayed open/close + this.openTimer = null; + this.closeTimer = null; + } + + getComponentConfig() { + return { + stateMachine: { + closed: { + enter: "onClosedEnter", + transitions: { + open: "open", + }, + }, + open: { + enter: "onOpenEnter", + exit: "onOpenExit", + transitions: { + close: "closed", + }, + }, + }, + events: { + closed: { + mouseMap: { + trigger: { + mouseenter: "delayOpen", + focus: "delayOpen", + }, + }, + }, + open: { + mouseMap: { + trigger: { + mouseleave: "delayClose", + blur: "delayClose", + }, + content: { + mouseenter: "clearTimers", + mouseleave: "delayClose", + }, + }, + }, + }, + hiddenConfig: { + closed: { + content: true, + }, + open: { + content: false, + }, + }, + ariaConfig: { + trigger: { + all: { + haspopup: "dialog", + }, + open: { + expanded: "true", + }, + closed: { + expanded: "false", + }, + }, + content: { + all: { + role: "dialog", + }, + }, + }, + }; + } + + // Generic methods for delayed state transitions + delayOpen() { + this.clearTimers(); + this.openTimer = setTimeout(() => { + this.transition("open"); + }, this.config.openDelay); + } + + delayClose() { + this.clearTimers(); + this.closeTimer = setTimeout(() => { + this.transition("close"); + }, this.config.closeDelay); + } + + clearTimers() { + if (this.openTimer) { + clearTimeout(this.openTimer); + this.openTimer = null; + } + + if (this.closeTimer) { + clearTimeout(this.closeTimer); + this.closeTimer = null; + } + } + + // Initialize the positioned element + initializePositionedElement() { + if (this.positionedElement) return; + + if (!this.trigger || !this.content) return; + + // Get positioning configuration from content attributes with fallbacks to defaults + const positionConfig = { + placement: + this.content.getAttribute("data-side") || + DEFAULT_POSITION_CONFIG.placement, + alignment: + this.content.getAttribute("data-align") || + DEFAULT_POSITION_CONFIG.alignment, + sideOffset: + parseInt(this.content.getAttribute("data-side-offset"), 10) || + DEFAULT_POSITION_CONFIG.sideOffset, + alignOffset: + parseInt(this.content.getAttribute("data-align-offset"), 10) || + DEFAULT_POSITION_CONFIG.alignOffset, + flip: true, + usePortal: false, // Don't use portal to keep DOM structure + trapFocus: false, + }; + + // Create positioned element + this.positionedElement = new PositionedElement( + this.content, + this.trigger, + positionConfig, + ); + } + + // State machine handlers + onOpenEnter() { + // Initialize positioned element if needed + this.initializePositionedElement(); + + // Activate positioned element + if (this.positionedElement) { + this.positionedElement.activate(); + } + + // Notify the server of the state change + this.pushEvent("opened"); + } + + onOpenExit() { + // Deactivate positioned element + if (this.positionedElement) { + this.positionedElement.deactivate(); + } + } + + onClosedEnter() { + // Notify the server of the state change + this.pushEvent("closed"); + } + + beforeDestroy() { + this.clearTimers(); + + if (this.positionedElement) { + this.positionedElement.destroy(); + this.positionedElement = null; + } + } +} + +// Register the component +SaladUI.register("hover-card", HoverCardComponent); + +export default HoverCardComponent; diff --git a/assets/js/ui/components/menu.js b/assets/js/ui/components/menu.js new file mode 100644 index 0000000..6522ef5 --- /dev/null +++ b/assets/js/ui/components/menu.js @@ -0,0 +1,324 @@ +// saladui/components/dropdown_menu.js +import Component from "../core/component"; +import Collection from "../core/collection"; + +/** + * Base class for dropdown menu items that provides common functionality + */ +class MenuItemBase extends Component { + constructor(itemElement, parentComponent, options) { + super(itemElement, { + ...options, + initialState: "idle", + ignoreItems: false, + }); + + this.parent = parentComponent; + // share the same hook context with the parent + this.hook = this.parent.hook; + this.value = + itemElement.value || + itemElement.getAttribute("data-value") || + itemElement.textContent.trim(); + this.disabled = itemElement.getAttribute("data-disabled") !== null; + this.config.preventDefaultKeys = [" ", "Enter"]; + this.setupEvents(); + } + + getComponentConfig() { + return { + stateMachine: { + idle: {}, + }, + events: { + idle: { + mouseMap: { + item: { + click: "handleActivation", + mouseenter: "handleMouseEnter", + }, + }, + keyMap: { + " ": "handleActivation", + Enter: "handleActivation", + }, + }, + }, + ariaConfig: { + item: { + all: { + role: "menuitem", + disabled: () => (this.disabled ? "true" : null), + }, + }, + }, + }; + } + + handleEvent(eventType) { + switch (eventType) { + case "focus": + if (!this.disabled) { + this.transition("focus"); + this.el.focus(); + } + return true; + case "blur": + this.transition("blur"); + return true; + } + } + + handleActivation(event) { + if (this.disabled) return; + this.pushEvent( + "item-selected", + { + value: this.value, + }, + this.parent.el, + ); + + this.parent.selectItem(this); + } + + handleMouseEnter() { + if (!this.disabled) { + this.parent.handleItemFocus(this); + } + } +} + +/** + * Regular dropdown menu item implementation + */ +class MenuItem extends MenuItemBase { + constructor(itemElement, parentComponent, options) { + super(itemElement, parentComponent, options); + } +} + +/** + * Checkbox item implementation that can toggle between checked states + */ +class MenuCheckboxItem extends MenuItemBase { + constructor(itemElement, parentComponent, options) { + super(itemElement, parentComponent, options); + } + + getComponentConfig() { + return { + stateMachine: { + checked: { + transitions: { + toggle: "unchecked", + }, + }, + unchecked: { + transitions: { + toggle: "checked", + }, + }, + }, + events: { + checked: { + mouseMap: { + "checkbox-item": { + click: "handleActivation", + mouseleave: "handleMouseLeave", + }, + }, + keyMap: { + " ": "handleActivation", + Enter: "handleActivation", + }, + }, + unchecked: { + mouseMap: { + "checkbox-item": { + click: "handleActivation", + mouseleave: "handleMouseLeave", + }, + }, + keyMap: { + " ": "handleActivation", + Enter: "handleActivation", + }, + }, + }, + hiddenConfig: { + checked: { + "item-indicator": false, + }, + unchecked: { + "item-indicator": true, + }, + }, + + ariaConfig: { + item: { + all: { + role: "menuitemcheckbox", + disabled: () => (this.disabled ? "true" : null), + checked: () => (this.state == "checked" ? "true" : "false"), + }, + }, + }, + }; + } + + handleActivation(event) { + super.handleActivation(event); + if (event) { + event.preventDefault(); + event.stopPropagation(); + event.stopImmediatePropagation(); + } + + if (this.disabled) return; + + this.transition("toggle"); + + this.pushEvent( + "checked-changed", + { + value: this.value, + checked: this.state == "checked", + }, + this.parent.el, + ); + } +} + +/** + * MenuComponent class for SaladUI framework + * Manages a dropdown menu with support for keyboard navigation and accessibility + */ +class Menu extends Component { + constructor(el, { hookContext, onItemSelect }) { + super(el, { hookContext }); + + // callback for item selection + this.onItemSelect = onItemSelect || (() => {}); + this.menuItems = []; + + // Set keyboard navigation defaults + this.config.preventDefaultKeys = ["ArrowDown", "ArrowUp", "Home", "End"]; + + // Initialize items and collection + this.initializeItems(); + this.initializeCollection(); + this.setupEvents(); + } + + getComponentConfig() { + return { + stateMachine: { + idle: { + transitions: {}, + }, + }, + events: { + _all: { + keyMap: { + ArrowDown: () => this.navigateItem("next"), + ArrowUp: () => this.navigateItem("prev"), + Home: () => this.navigateItem("first"), + End: () => this.navigateItem("last"), + }, + }, + }, + ariaConfig: {}, + }; + } + + initializeItems() { + // Get all items in the correct DOM order + const allItemElements = Array.from( + this.el.querySelectorAll( + "[data-part='item'], [data-part='checkbox-item']", + ), + ); + + // Create appropriate item components while preserving original order + this.menuItems = allItemElements.map((element) => { + const itemType = element.getAttribute("data-part"); + + switch (itemType) { + case "checkbox-item": + return new MenuCheckboxItem(element, this, { + initialState: "normal", + }); + default: // Regular item + return new MenuItem(element, this, { + initialState: "normal", + }); + } + }); + } + + initializeCollection() { + // Initialize collection manager for navigation + this.collection = new Collection({ + type: "single", + getItemValue: (item) => item.value, + isItemDisabled: (item) => item.disabled, + }); + + // Register items with the collection + this.menuItems.forEach((item) => { + this.collection.add(item); + }); + } + + // Activate menu, focusthe first item + activate() { + const firstItem = this.collection.getItem("first"); + if (firstItem) { + this.collection.focus(firstItem); + } + } + + selectItem(item) { + if (item.disabled) return; + this.onItemSelect(item); + } + + handleItemFocus(item) { + const collectionItem = this.collection.getItemByInstance(item); + if (!collectionItem) return; + + this.collection.focus(collectionItem); + } + + navigateItem(direction) { + // Check if we have an active focused item + let currentItem = this.collection.focusedItem; + + // Get target item using collection's navigation methods + const targetItem = this.collection.getItem(direction, currentItem); + + if (targetItem) { + this.collection.focus(targetItem); + } + } + + beforeDestroy() { + // Clean up menu items + if (this.menuItems) { + this.menuItems.forEach((item) => { + if (typeof item.destroy === "function") { + item.destroy(); + } + }); + this.menuItems = null; + } + + // Clean up collection + this.collection = null; + } +} + +// Register the component + +export default Menu; diff --git a/assets/js/ui/components/popover.js b/assets/js/ui/components/popover.js new file mode 100644 index 0000000..2e8652b --- /dev/null +++ b/assets/js/ui/components/popover.js @@ -0,0 +1,133 @@ +// saladui/components/popover.js +import Component from "../core/component"; +import SaladUI from "../index"; +import PositionedElement from "../core/positioned-element"; + +class PopoverComponent extends Component { + constructor(el, hookContext) { + super(el, { hookContext }); + + // Initialize core properties + this.trigger = this.getPart("trigger"); + this.positioner = this.getPart("positioner"); + this.content = this.positioner + ? this.positioner.querySelector("[data-part='content']") + : null; + + // Set keyboard navigation defaults + this.config.preventDefaultKeys = ["Escape"]; + } + + getComponentConfig() { + return { + stateMachine: { + closed: { + enter: "onClosedEnter", + transitions: { + open: "open", + toggle: "open", + }, + }, + open: { + enter: "onOpenEnter", + transitions: { + close: "closed", + toggle: "closed", + }, + }, + }, + events: { + closed: { + keyMap: {}, + }, + open: { + keyMap: { + Escape: "close", + }, + }, + }, + hiddenConfig: { + closed: { + positioner: true, // Hide the positioner in closed state + }, + open: { + positioner: false, // Show the positioner in open state + }, + }, + ariaConfig: { + trigger: { + all: { + haspopup: "dialog", + }, + open: { + expanded: "true", + }, + closed: { + expanded: "false", + }, + }, + content: { + all: { + role: "dialog", + }, + }, + }, + }; + } + + /** + * Initializes the positioned element if the positioner and trigger exist and the positioned element is not already created. + * Extracts placement configuration from DOM attributes and creates a new PositionedElement instance. + */ + initializePositionedElement() { + if (this.positioner && this.trigger && !this.positionedElement) { + const placement = this.positioner.getAttribute("data-side") || "bottom"; + const alignment = this.positioner.getAttribute("data-align") || "center"; + const sideOffset = parseInt( + this.positioner.getAttribute("data-side-offset") || "8", + 10, + ); + const alignOffset = parseInt( + this.positioner.getAttribute("data-align-offset") || "0", + 10, + ); + + this.positionedElement = new PositionedElement( + this.positioner, + this.trigger, + { + placement, + alignment, + sideOffset, + alignOffset, + flip: true, + usePortal: true, + portalContainer: document.querySelector(this.options.portalContainer), + trapFocus: true, + onOutsideClick: () => this.transition("close"), + }, + ); + } + } + + onOpenEnter(params = {}) { + this.initializePositionedElement(); + this.positionedElement?.activate(); + this.pushEvent("opened"); + } + + onClosedEnter() { + this.positionedElement?.deactivate(); + this.pushEvent("closed"); + } + + beforeDestroy() { + this.positionedElement?.destroy(); + this.positionedElement = null; + } +} + +// Register the component +SaladUI.register("popover", PopoverComponent); + +export default PopoverComponent; diff --git a/assets/js/ui/components/radio_group.js b/assets/js/ui/components/radio_group.js new file mode 100644 index 0000000..3d1f18c --- /dev/null +++ b/assets/js/ui/components/radio_group.js @@ -0,0 +1,219 @@ +// saladui/components/radio_group.js +import Component from "../core/component"; +import SaladUI from "../index"; +import Collection from "../core/collection"; + +class RadioGroupComponent extends Component { + constructor(el, hookContext) { + super(el, { hookContext, ignoreItems: false }); + + // Initialize properties + this.items = this.getAllParts("item"); + + // Set keyboard navigation defaults + this.config.preventDefaultKeys = [ + "ArrowLeft", + "ArrowRight", + "ArrowUp", + "ArrowDown", + "Home", + "End", + ]; + + // Initialize collection manager for radio items + this.initializeCollection(); + } + + getComponentConfig() { + return { + stateMachine: { + idle: { + enter: "onIdleEnter", + transitions: { + valueChanged: "idle", + }, + }, + }, + events: { + idle: { + keyMap: { + ArrowLeft: () => this.navigateItem("prev"), + ArrowRight: () => this.navigateItem("next"), + ArrowUp: () => this.navigateItem("prev"), + ArrowDown: () => this.navigateItem("next"), + Home: () => this.navigateItem("first"), + End: () => this.navigateItem("last"), + }, + mouseMap: { + item: { + click: "handleItemClick", + }, + }, + }, + }, + ariaConfig: { + root: { + all: { + role: "radiogroup", + }, + }, + item: { + all: { + role: "radio", + }, + }, + }, + }; + } + + initializeCollection() { + this.collection = new Collection({ + type: "single", + defaultValue: this.options.initialValue, + getItemValue: (item) => item.getAttribute("data-value"), + isItemDisabled: (item) => item.getAttribute("data-disabled") === "true", + }); + + // Register items with the collection + this.items.forEach((item) => { + this.collection.add(item); + + // Set initial ID if not present + if (!item.id) { + const value = item.getAttribute("data-value"); + item.id = `${this.el.id}-item-${value}`; + } + }); + + // Initialize UI state + this.updateItemStates(); + } + + handleItemClick(event) { + const item = event.currentTarget; + if (item.getAttribute("data-disabled") === "true") return; + + this.selectItem(item); + } + + selectItem(item) { + const value = item.getAttribute("data-value"); + const previousValue = this.collection.getValue(); + + // Get the collection item + const collectionItem = this.collection.getItemByInstance(item); + + // Only proceed if we have a valid item and it's not already selected + if (collectionItem && value !== previousValue) { + // Transition the state machine to apply any state-specific behavior + this.transition("valueChanged", { value, previousValue }); + + this.collection.select(collectionItem); + this.updateItemStates(); + + // Emit value changed event + this.pushEvent("value-changed", { + value, + previousValue, + }); + } + } + + updateItemStates() { + const selectedValue = this.collection.getValue(); + + // Loop over collection items instead of DOM elements directly + this.collection.items.forEach((collectionItem) => { + const item = collectionItem.instance; + const value = collectionItem.value; + const isSelected = value === selectedValue; + const isDisabled = item.getAttribute("data-disabled") === "true"; + + // Update visual state + item.setAttribute("data-state", isSelected ? "checked" : "unchecked"); + + // Update ARIA attributes + item.setAttribute("aria-checked", isSelected.toString()); + + // Update tabindex for keyboard navigation + item.setAttribute("tabindex", isSelected ? "0" : "-1"); + + // Update native radio input if present + const input = item.querySelector('input[type="radio"]'); + if (input) { + input.checked = isSelected; + input.disabled = isDisabled; + + // Ensure name attribute is set for form submission + if (!input.name && this.options.name) { + input.name = this.options.name; + } + } + }); + } + + navigateItem(direction) { + const currentValue = this.collection.getValue(); + const currentItem = this.collection.getItemByValue(currentValue); + + // Get next item based on direction + const nextItem = this.collection.getItem(direction, currentItem); + if (!nextItem) return; + + // Focus the item + if (typeof nextItem.instance.focus === "function") { + nextItem.instance.focus(); + } else if (nextItem.instance) { + // Focus the item element directly + nextItem.instance.focus(); + } + + // Automatically select the focused item + this.selectItem(nextItem.instance); + } + + onIdleEnter() { + // If no item is selected, make first enabled item focusable + if (!this.collection.getValue()) { + const firstItem = this.collection.getItem("first"); + if (firstItem && firstItem.instance) { + firstItem.instance.setAttribute("tabindex", "0"); + } + } + } + + // Handle focus management for the entire group + setupComponentEvents() { + super.setupComponentEvents(); + + this.el.addEventListener("focus", (e) => { + // Only handle focus if the group itself was focused (not a child) + if (e.target === this.el) { + const selectedValue = this.collection.getValue(); + if (selectedValue) { + // Focus the selected item + const selectedItem = this.collection.getItemByValue(selectedValue); + if (selectedItem && selectedItem.instance) { + selectedItem.instance.focus(); + } + } else { + // Focus the first enabled item if none is selected + const firstItem = this.collection.getItem("first"); + if (firstItem && firstItem.instance) { + firstItem.instance.focus(); + } + } + } + }); + } + + // Clean up when the component is destroyed + beforeDestroy() { + this.collection = null; + } +} + +// Register the component +SaladUI.register("radio-group", RadioGroupComponent); + +export default RadioGroupComponent; diff --git a/assets/js/ui/components/select.js b/assets/js/ui/components/select.js new file mode 100644 index 0000000..272728e --- /dev/null +++ b/assets/js/ui/components/select.js @@ -0,0 +1,481 @@ +// saladui/components/select.js +import Component from "../core/component"; +import SaladUI from "../index"; +import Collection from "../core/collection"; +import PositionedElement from "../core/positioned-element"; + +/** + * SelectItem class to manage individual select options + * Handles state transitions and events for a single select item + */ +class SelectItem extends Component { + constructor(itemElement, parentComponent, options) { + const { initialState = "normal" } = options || {}; + super(itemElement, { initialState, ignoreItems: false }); + + this.parent = parentComponent; + this.value = itemElement.dataset.value; + this.disabled = itemElement.dataset.disabled === "true"; + this.label = itemElement.textContent.trim(); + + this.setupEvents(); + } + + getComponentConfig() { + return { + stateMachine: { + unchecked: { + transitions: { + check: "checked", + }, + }, + checked: { + transitions: { + uncheck: "unchecked", + }, + }, + }, + events: { + unchecked: { + mouseMap: { + item: { + click: "handleActivation", + mouseenter: "handleMouseEnter", + mouseleave: "handleMouseLeave", + }, + }, + keyMap: { + Enter: "handleActivation", + " ": "handleActivation", + }, + }, + checked: { + mouseMap: { + item: { + click: "handleActivation", + mouseenter: "handleMouseEnter", + mouseleave: "handleMouseLeave", + }, + }, + keyMap: { + Enter: "handleActivation", + " ": "handleActivation", + }, + }, + }, + hiddenConfig: { + checked: { + "item-indicator": false, + }, + unchecked: { + "item-indicator": true, + }, + }, + ariaConfig: { + item: { + all: { + role: "option", + }, + checked: { + selected: "true", + }, + unchecked: { + selected: "false", + }, + }, + }, + }; + } + + handleEvent(eventType) { + switch (eventType) { + case "select": + return this.transition("check"); + case "unselect": + return this.transition("uncheck"); + case "focus": + if (!this.disabled) { + // Just mark as highlighted without direct focus + this.el.focus(); + } + return true; + case "blur": + return true; + } + } + + handleActivation(event) { + event.preventDefault(); + event.stopPropagation(); + if (!this.disabled) { + this.parent.selectValue(this.value); + } + } + + handleMouseEnter() { + if (!this.disabled) { + this.parent.handleItemFocus(this); + } + } +} + +/** + * SelectComponent class for SaladUI framework + * Manages a collection of select items with state transitions + */ +class SelectComponent extends Component { + constructor(el, hookContext) { + super(el, { hookContext }); + + // Initialize core properties + this.trigger = this.getPart("trigger"); + this.valueDisplay = this.getPart("value"); + this.content = this.getPart("content"); + this.disabled = this.el.dataset.disabled === "true"; + + // Get configuration from options + this.multiple = this.options.multiple || false; + this.usePortal = this.options.hasOwnProperty("usePortal") + ? this.options.usePortal + : false; + this.portalContainer = this.options.portalContainer || null; + + // Initialize collection manager + this.collection = new Collection({ + type: this.multiple ? "multiple" : "single", + defaultValue: this.options.defaultValue, + value: this.options.value, + getItemValue: (item) => item.value, + isItemDisabled: (item) => item.disabled || this.disabled, + }); + + // Set keyboard navigation defaults + this.config.preventDefaultKeys = [ + "ArrowUp", + "ArrowDown", + "Home", + "End", + "Enter", + " ", + "Escape", + ]; + + // Initialize select items + this.initializeItems(); + this.initializePlaceholder(); + } + + getComponentConfig() { + return { + stateMachine: { + closed: { + enter: "onClosedEnter", + exit: "onClosedExit", + transitions: { + open: "open", + toggle: "open", + }, + }, + open: { + enter: "onOpenEnter", + exit: "onOpenExit", + transitions: { + close: "closed", + toggle: "closed", + select: "closed", + }, + }, + }, + events: { + closed: { + keyEventTarget: "content", + keyMap: { + ArrowDown: "open", + ArrowUp: "open", + Enter: "open", + " ": "open", + }, + mouseMap: { + trigger: { + click: "toggle", + }, + }, + }, + open: { + keyEventTarget: "content", + keyMap: { + Escape: "close", + ArrowUp: () => this.navigateItem("prev"), + ArrowDown: () => this.navigateItem("next"), + Home: () => this.navigateItem("first"), + End: () => this.navigateItem("last"), + }, + }, + }, + hiddenConfig: { + closed: { + content: true, + }, + open: { + content: false, + }, + }, + ariaConfig: { + trigger: { + all: { + haspopup: "listbox", + }, + open: { + expanded: "true", + }, + closed: { + expanded: "false", + }, + }, + content: { + all: { + role: "listbox", + }, + }, + }, + }; + } + + initializeItems() { + const itemElements = Array.from( + this.el.querySelectorAll("[data-part='item']"), + ); + + itemElements.map((element) => { + // Create a SelectItem instance for each item + const value = element.dataset.value; + + // Check if this item is initially selected + const isSelected = this.collection.isValueSelected(value); + const initialState = isSelected ? "checked" : "unchecked"; + + const item = new SelectItem(element, this, { initialState }); + this.collection.add(item); + }); + + // Update value display based on initial selection + this.updateValueDisplay(); + } + + initializePlaceholder() { + if (!this.valueDisplay) return; + + const placeholder = + this.valueDisplay.getAttribute("data-placeholder") || "Select an option"; + + // If no selection, display the placeholder + if (this.collection.getValue(true).length === 0) { + this.valueDisplay.setAttribute("data-content", placeholder); + } + } + + initializePositionedElement() { + if (this.content && this.trigger && !this.positionedElement) { + // Extract position config from content attributes + const side = this.content.getAttribute("data-side") || "bottom"; + + // Get portal container if specified + let portalContainer = null; + if (this.portalContainer) { + portalContainer = document.querySelector(this.portalContainer); + } + + // Create positioned element with modular architecture + this.positionedElement = new PositionedElement(this.content, this.el, { + placement: side, + alignment: "start", + sideOffset: 4, + flip: true, + usePortal: this.usePortal, + portalContainer: portalContainer || document.body, + trapFocus: false, + onOutsideClick: () => this.transition("close"), + }); + } + } + + // State machine handlers + onClosedEnter() { + // Update hidden input(s) + this.syncHiddenInputs(); + this.pushEvent("closed"); + } + + onOpenEnter() { + // Initialize positioned element + this.initializePositionedElement(); + + // Activate positioned element + if (this.positionedElement) { + this.positionedElement.activate(); + } + + // Highlight first selected item or first item + this.highlightFirstSelectedOrFirstItem(); + + this.pushEvent("opened"); + } + + onOpenExit() { + // Deactivate positioned element + if (this.positionedElement) { + this.positionedElement.deactivate(); + } + } + + // Item management + selectValue(value) { + const collectionItem = this.collection.getItemByValue(value); + if (!collectionItem) return; + + // Toggle item selection + this.collection.select(collectionItem); + + // Update value display + this.updateValueDisplay(); + + // Close dropdown if single select + if (!this.multiple) { + this.transition("select"); + } + + // Emit event with current value + const selectedValue = this.collection.getValue(); + this.pushEvent("value-changed", { value: selectedValue }); + } + + handleItemFocus(item) { + const collectionItem = this.collection.getItemByInstance(item); + if (!collectionItem) return; + + this.collection.focus(collectionItem); + } + + updateValueDisplay() { + if (!this.valueDisplay) return; + + const selectedValues = this.collection.getValue(true); + const placeholder = + this.valueDisplay.getAttribute("data-placeholder") || "Select an option"; + + if (selectedValues.length === 0) { + // No selection, show placeholder + this.valueDisplay.setAttribute("data-content", placeholder); + } else if (this.multiple) { + // Multiple selection + if (selectedValues.length === 1) { + // Get the label from the selected item + const selectedItem = this.collection.getItemByValue(selectedValues[0]); + this.valueDisplay.setAttribute( + "data-content", + selectedItem.instance.label, + ); + } else { + // Show count for multiple selections + this.valueDisplay.setAttribute( + "data-content", + `${selectedValues.length} items selected`, + ); + } + } else { + // Single selection - get label from the selected item + const selectedItem = this.collection.getItemByValue(selectedValues[0]); + this.valueDisplay.setAttribute( + "data-content", + selectedItem.instance.label, + ); + } + } + + // Navigation methods + navigateItem(direction) { + // Check if we have an active highlighted item + let currentItem = this.collection.focusedItem; + + // If not, use the first selected item or null + if (!currentItem) { + currentItem = + this.collection.getValue(true).length > 0 + ? this.collection.getItemByValue(this.collection.getValue(true)[0]) + : null; + } + + // Get target item using collection's navigation methods + const targetItem = this.collection.getItem(direction, currentItem); + + if (targetItem) { + this.collection.focus(targetItem); + } + } + + highlightFirstSelectedOrFirstItem() { + // Try to highlight the first selected item + const selectedValue = this.collection.getValue(); + + const selectedItem = + this.collection.getItemByValue(selectedValue) || + this.collection.getItem("first"); + if (selectedItem) { + this.collection.focus(selectedItem); + } + } + + // Form integration + syncHiddenInputs() { + // Get the selected values + const values = this.collection.getValue(true); + const name = this.options.name || ""; + + // Remove existing hidden inputs + const existingInputs = this.el.querySelectorAll("input[type='hidden']"); + existingInputs.forEach((input) => input.remove()); + + // Create new hidden inputs + if (this.multiple) { + // Multiple select - create multiple inputs with array notation + const inputName = name ? `${name}[]` : ""; + + values.forEach((value) => { + const input = document.createElement("input"); + input.type = "hidden"; + input.name = inputName; + input.value = value; + this.el.appendChild(input); + }); + } else if (values.length > 0) { + // Single select - create one input + const input = document.createElement("input"); + input.type = "hidden"; + input.name = name; + input.value = values[0]; + this.el.appendChild(input); + } + } + + // Cleanup + beforeDestroy() { + if (this.positionedElement) { + this.positionedElement.destroy(); + this.positionedElement = null; + } + + // Clean up item instances + this.collection.each((item) => { + if (typeof item.destroy === "function") { + item.destroy(); + } + }); + + this.collection = null; + } +} + +// Register the component +SaladUI.register("select", SelectComponent); + +export default SelectComponent; diff --git a/assets/js/ui/components/slider.js b/assets/js/ui/components/slider.js new file mode 100644 index 0000000..4b37454 --- /dev/null +++ b/assets/js/ui/components/slider.js @@ -0,0 +1,256 @@ +// saladui/components/slider.js +import Component from "../core/component"; +import SaladUI from "../index"; + +class SliderComponent extends Component { + constructor(el, hookContext) { + super(el, { hookContext }); + + // Get slider elements + this.track = this.getPart("track"); + this.range = this.getPart("range"); + this.thumb = this.getPart("thumb"); + + // Initialize values + this.parseValues(); + + // Add drag handling + this.isDragging = false; + this.setupDragHandling(); + + // Set initial position + this.updatePosition(); + } + + parseValues() { + // Get values from options with defaults + this.min = parseFloat(this.options.min || 0); + this.max = parseFloat(this.options.max || 100); + this.step = parseFloat(this.options.step || 1); + this.disabled = !!this.options.disabled; + + // Get value from data attribute, fallback to defaultValue from options, then to min + const dataValue = this.el.dataset.value; + const defaultValue = this.options.defaultValue; + this.value = parseFloat( + dataValue !== undefined && dataValue !== null + ? dataValue + : (defaultValue !== undefined ? defaultValue : this.min), + ); + + // Clamp value to min/max + this.value = Math.max(this.min, Math.min(this.max, this.value)); + } + + getComponentConfig() { + return { + stateMachine: { + idle: { + enter: "onIdleEnter", + transitions: { + drag: "dragging", + }, + }, + dragging: { + enter: "onDraggingEnter", + exit: "onDraggingExit", + transitions: { + end: "idle", + }, + }, + }, + events: { + idle: { + keyMap: { + ArrowLeft: () => this.decrementValue(), + ArrowRight: () => this.incrementValue(), + ArrowDown: () => this.decrementValue(), + ArrowUp: () => this.incrementValue(), + Home: () => this.setValueAndUpdate(this.min), + End: () => this.setValueAndUpdate(this.max), + }, + }, + dragging: { + // These are handled by event listeners + }, + }, + ariaConfig: { + root: { + all: { + role: "slider", + valuemin: () => this.min?.toString(), + valuemax: () => this.max?.toString(), + valuenow: () => this.value?.toString(), + valuetext: () => this.value?.toString(), + orientation: "horizontal", + disabled: () => (this.disabled ? "true" : null), + }, + }, + }, + }; + } + + setupDragHandling() { + // Set up event handlers with proper binding + this.onPointerMove = this.onPointerMove.bind(this); + this.onPointerUp = this.onPointerUp.bind(this); + this.onPointerDown = this.onPointerDown.bind(this); + // Mouse events + this.el.addEventListener("mousedown", this.onPointerDown); + + // Touch events + this.el.addEventListener("touchstart", this.onPointerDown, { + passive: false, + }); + } + + onIdleEnter() { + // Set proper tabindex when component is idle (not being dragged) + this.el.setAttribute("tabindex", "0"); + } + + onDraggingEnter() { + // Add global event listeners + document.addEventListener("mousemove", this.onPointerMove); + document.addEventListener("touchmove", this.onPointerMove, { + passive: false, + }); + document.addEventListener("mouseup", this.onPointerUp); + document.addEventListener("touchend", this.onPointerUp); + } + + onDraggingExit() { + // Remove global event listeners + document.removeEventListener("mousemove", this.onPointerMove); + document.removeEventListener("touchmove", this.onPointerMove); + document.removeEventListener("mouseup", this.onPointerUp); + document.removeEventListener("touchend", this.onPointerUp); + } + + onPointerDown(event) { + // Skip if disabled + if (this.disabled) return; + + // Prevent default to avoid text selection during drag + event.preventDefault(); + + // Handle mousedown or touchstart + this.transition("drag"); + + // Update value based on pointer position + this.updateValueFromPointer(event); + } + + onPointerMove(event) { + // Prevent default to avoid scrolling during drag + event.preventDefault(); + + // Update value based on pointer position + this.updateValueFromPointer(event); + } + + onPointerUp() { + // End dragging + this.transition("end"); + + // Notify of value change + this.pushEvent("value-changed", { value: this.value }); + } + + updateValueFromPointer(event) { + // Get coordinates depending on event type + const clientX = event.touches ? event.touches[0].clientX : event.clientX; + + // Get track bounds + const trackRect = this.track.getBoundingClientRect(); + + // Calculate percentage within track + let percentage = Math.max( + 0, + Math.min(1, (clientX - trackRect.left) / trackRect.width), + ); + + // Calculate value based on percentage + const rawValue = this.min + percentage * (this.max - this.min); + + // Snap to step + const steppedValue = Math.round(rawValue / this.step) * this.step; + + // Set and update + this.setValueAndUpdate(steppedValue); + } + + incrementValue() { + this.setValueAndUpdate(Math.min(this.max, this.value + this.step)); + this.pushEvent("value-changed", { value: this.value }); + } + + decrementValue() { + this.setValueAndUpdate(Math.max(this.min, this.value - this.step)); + this.pushEvent("value-changed", { value: this.value }); + } + + setValueAndUpdate(newValue) { + // Ensure value is within bounds + this.value = Math.max(this.min, Math.min(this.max, newValue)); + + // Update visual position + this.updatePosition(); + + // Update ARIA attributes + this.el.setAttribute("aria-valuenow", this.value.toString()); + this.el.setAttribute("aria-valuetext", this.value.toString()); + } + + updatePosition() { + // Calculate percentage for positioning + const percentage = ((this.value - this.min) / (this.max - this.min)) * 100; + + // Update range width + this.range.style.width = `${percentage}%`; + + // Get track and thumb dimensions + const trackRect = this.track.getBoundingClientRect(); + const thumbRect = this.thumb.getBoundingClientRect(); + + // Calculate the percentage offset needed to keep thumb fully within track + // This accounts for the thumb's width relative to the track + const thumbHalfWidthPercentage = + (thumbRect.width / 2 / trackRect.width) * 100; + + // Constrain the thumb position to keep it fully inside the track + const thumbPercentage = Math.max( + thumbHalfWidthPercentage, + Math.min(100 - thumbHalfWidthPercentage, percentage), + ); + + // Update thumb position + this.thumb.style.left = `${thumbPercentage}%`; + this.thumb.style.transform = "translateX(-50%)"; + } + + // Handle direct server-side commands + handleCommand(command, params) { + if (command === "setValue") { + this.setValueAndUpdate(parseFloat(params.value)); + return true; + } + return super.handleCommand(command, params); + } + + // Clean up + beforeDestroy() { + document.removeEventListener("mousemove", this.onPointerMove); + document.removeEventListener("touchmove", this.onPointerMove); + document.removeEventListener("mouseup", this.onPointerUp); + document.removeEventListener("touchend", this.onPointerUp); + // Remove local event listeners + this.el.removeEventListener("mousedown", this.onPointerDown); + this.el.removeEventListener("touchstart", this.onPointerDown); + } +} + +// Register component +SaladUI.register("slider", SliderComponent); + +export default SliderComponent; diff --git a/assets/js/ui/components/switch.js b/assets/js/ui/components/switch.js new file mode 100644 index 0000000..e04ff03 --- /dev/null +++ b/assets/js/ui/components/switch.js @@ -0,0 +1,112 @@ +// saladui/components/switch.js +import Component from "../core/component"; +import SaladUI from "../index"; + +class SwitchComponent extends Component { + constructor(el, hookContext) { + super(el, { hookContext }); + + // Initialize state based on checked attribute or data-state + this.initialState = this.el.getAttribute("data-state"); + } + + getComponentConfig() { + return { + stateMachine: { + checked: { + enter: "onCheckedEnter", + transitions: { + toggle: "unchecked", + }, + }, + unchecked: { + enter: "onUncheckedEnter", + transitions: { + toggle: "checked", + }, + }, + }, + events: { + checked: { + mouseMap: { + root: { + click: "toggleSwitch", + }, + }, + keyMap: { + " ": "toggleSwitch", + Enter: "toggleSwitch", + }, + }, + unchecked: { + mouseMap: { + root: { + click: "toggleSwitch", + }, + }, + keyMap: { + " ": "toggleSwitch", + Enter: "toggleSwitch", + }, + }, + }, + ariaConfig: { + root: { + all: { + role: "switch", + }, + checked: { + checked: "true", + }, + unchecked: { + checked: "false", + }, + }, + }, + }; + } + + toggleSwitch(e) { + if (this.disabled) return; + this.transition("toggle"); + // prevent click event handler from handling twice after the first one toggle the state + // the second one reverse state immediately + e.stopImmediatePropagation(); + } + + setupComponentEvents() { + // Set up keyboard navigation + this.el.setAttribute( + "tabindex", + this.el.getAttribute("disabled") === "true" ? "-1" : "0", + ); + this.config.preventDefaultKeys = [" ", "Enter"]; + } + + onCheckedEnter(e) { + // Update hidden checkbox input + const checkbox = this.el.querySelector('input[type="checkbox"]'); + if (checkbox) { + checkbox.checked = true; + } + + // Notify of value change + this.pushEvent("checked-changed", { value: true }); + } + + onUncheckedEnter(e) { + // Update hidden checkbox input + const checkbox = this.el.querySelector('input[type="checkbox"]'); + if (checkbox) { + checkbox.checked = false; + } + + // Notify of value change + this.pushEvent("checked-changed", { value: false }); + } +} + +// Register the component +SaladUI.register("switch", SwitchComponent); + +export default SwitchComponent; diff --git a/assets/js/ui/components/tabs.js b/assets/js/ui/components/tabs.js new file mode 100644 index 0000000..bcee415 --- /dev/null +++ b/assets/js/ui/components/tabs.js @@ -0,0 +1,176 @@ +// saladui/components/tabs.js +import Component from "../core/component"; +import SaladUI from "../index"; +import Collection from "../core/collection"; + +class TabsComponent extends Component { + constructor(el, hookContext) { + super(el, { hookContext }); + + // Initialize core properties + this.list = this.getPart("list"); + this.triggers = this.getAllParts("trigger"); + this.contents = this.getAllParts("content"); + + // Set keyboard navigation defaults + this.config.preventDefaultKeys = [ + "ArrowLeft", + "ArrowRight", + "Home", + "End", + "Enter", + " ", + ]; + + // Initialize tabs + this.initialize(); + } + + initialize() { + // Initialize collection manager for tabs + this.collection = new Collection({ + type: "single", + defaultValue: this.options.defaultValue, + value: this.options.value, + getItemValue: (item) => item.getAttribute("data-value"), + isItemDisabled: (item) => item.getAttribute("data-disabled") === "true", + }); + + // Register triggers with collection manager + this.triggers.forEach((trigger) => this.collection.add(trigger)); + + // Setup accessibility attributes + this.setupAriaAttributes(); + + // Select first tab if none selected + if (!this.collection.getValue() && this.triggers.length > 0) { + const firstTrigger = this.collection.getItem("first"); + if (firstTrigger) this.collection.select(firstTrigger); + } + + // Initial UI update + this.updateActiveTab(); + } + + getComponentConfig() { + return { + stateMachine: { + idle: { + transitions: { select: "idle" }, + }, + }, + events: { + idle: { + keyMap: { + ArrowLeft: () => this.navigateTab("prev"), + ArrowRight: () => this.navigateTab("next"), + Home: () => this.navigateTab("first"), + End: () => this.navigateTab("last"), + }, + mouseMap: { + trigger: { click: (event) => this.handleTriggerClick(event) }, + }, + }, + }, + ariaConfig: { + list: { + all: { role: "tablist" }, + }, + trigger: { + all: { + role: "tab", + controls: (el) => + `${this.el.id}-content-${el.getAttribute("data-value")}`, + }, + }, + content: { + all: { + role: "tabpanel", + tabindex: "0", + }, + }, + }, + }; + } + + setupAriaAttributes() { + // Set IDs and ARIA attributes for triggers + this.triggers.forEach((trigger) => { + const value = trigger.getAttribute("data-value"); + if (!trigger.id) trigger.id = `${this.el.id}-trigger-${value}`; + }); + + // Set IDs and ARIA attributes for content panels + this.contents.forEach((content) => { + const value = content.getAttribute("data-value"); + if (!content.id) content.id = `${this.el.id}-content-${value}`; + content.setAttribute("aria-labelledby", `${this.el.id}-trigger-${value}`); + }); + } + + handleTriggerClick(event) { + const trigger = event.currentTarget; + if (trigger.getAttribute("data-disabled") === "true") return; + + this.selectTab(trigger.getAttribute("data-value")); + } + + selectTab(value) { + // Find the trigger item + const triggerItem = this.collection.getItemByValue(value); + if (!triggerItem || this.collection.isValueSelected(value)) return; + + // Select the tab + this.collection.select(triggerItem); + this.updateActiveTab(); + + // Focus the selected trigger + triggerItem.instance.focus(); + + // Emit event + this.pushEvent("tab-changed", { value: value, tab: value }); + } + + updateActiveTab() { + const selectedValue = this.collection.getValue(); + + // Update triggers + this.triggers.forEach((trigger) => { + const value = trigger.getAttribute("data-value"); + const isActive = value === selectedValue; + + trigger.setAttribute("data-state", isActive ? "active" : "inactive"); + trigger.setAttribute("aria-selected", isActive.toString()); + trigger.tabIndex = isActive ? 0 : -1; + }); + + // Update content panels + this.contents.forEach((content) => { + const value = content.getAttribute("data-value"); + const isActive = value === selectedValue; + + content.setAttribute("data-state", isActive ? "active" : "inactive"); + content.hidden = !isActive; + }); + } + + navigateTab(direction) { + const currentItem = this.collection.getItemByValue( + this.collection.getValue(), + ); + + const nextItem = this.collection.getItem(direction, currentItem); + if (nextItem) this.selectTab(nextItem.value); + } + + // Cleanup + destroy() { + this.collection = null; + super.destroy(); + } +} + +// Register the component +SaladUI.register("tabs", TabsComponent); + +export default TabsComponent; diff --git a/assets/js/ui/components/tooltip.js b/assets/js/ui/components/tooltip.js new file mode 100644 index 0000000..d18160d --- /dev/null +++ b/assets/js/ui/components/tooltip.js @@ -0,0 +1,201 @@ +import Component from "../core/component"; +import SaladUI from "../index"; +import PositionedElement from "../core/positioned-element"; + +// Define constants at the module level outside the class +const DEFAULT_POSITION_CONFIG = { + placement: "top", + alignment: "center", + sideOffset: 8, + alignOffset: 0, +}; + +const DEFAULT_TIMING_CONFIG = { + openDelay: 150, + closeDelay: 100, +}; + +class TooltipComponent extends Component { + constructor(el, hookContext) { + super(el, { hookContext }); + + // Initialize core properties + this.trigger = + this.getPart("trigger") || this.el.querySelector(":first-child"); + this.content = this.getPart("content"); + + // Set config from options with fallbacks to defaults + this.config.openDelay = + this.options.openDelay || DEFAULT_TIMING_CONFIG.openDelay; + this.config.closeDelay = + this.options.closeDelay || DEFAULT_TIMING_CONFIG.closeDelay; + + // Track timer IDs for delayed open/close + this.openTimer = null; + this.closeTimer = null; + } + + getComponentConfig() { + return { + stateMachine: { + closed: { + enter: "onClosedEnter", + transitions: { + open: "open", + }, + }, + open: { + enter: "onOpenEnter", + exit: "onOpenExit", + transitions: { + close: "closed", + }, + }, + }, + events: { + closed: { + mouseMap: { + trigger: { + mouseenter: "delayOpen", + focus: "delayOpen", + }, + }, + }, + open: { + mouseMap: { + trigger: { + mouseleave: "delayClose", + blur: "delayClose", + }, + content: { + mouseenter: "clearTimers", + mouseleave: "delayClose", + }, + }, + }, + }, + hiddenConfig: { + closed: { + content: true, + }, + open: { + content: false, + }, + }, + ariaConfig: { + trigger: { + all: { + describedby: () => this.getPartId("content"), + }, + }, + content: { + all: { + role: "tooltip", + }, + }, + }, + }; + } + + // Generic methods for delayed state transitions + delayOpen() { + this.clearTimers(); + this.openTimer = setTimeout(() => { + this.transition("open"); + }, this.config.openDelay); + } + + delayClose() { + this.clearTimers(); + this.closeTimer = setTimeout(() => { + this.transition("close"); + }, this.config.closeDelay); + } + + clearTimers() { + if (this.openTimer) { + clearTimeout(this.openTimer); + this.openTimer = null; + } + + if (this.closeTimer) { + clearTimeout(this.closeTimer); + this.closeTimer = null; + } + } + + // Initialize the positioned element + initializePositionedElement() { + if (this.positionedElement) return; + + if (!this.trigger || !this.content) return; + + // Get positioning configuration from content attributes with fallbacks to defaults + const positionConfig = { + placement: + this.content.getAttribute("data-side") || + DEFAULT_POSITION_CONFIG.placement, + alignment: + this.content.getAttribute("data-align") || + DEFAULT_POSITION_CONFIG.alignment, + sideOffset: + parseInt(this.content.getAttribute("data-side-offset"), 10) || + DEFAULT_POSITION_CONFIG.sideOffset, + alignOffset: + parseInt(this.content.getAttribute("data-align-offset"), 10) || + DEFAULT_POSITION_CONFIG.alignOffset, + flip: true, + usePortal: false, + trapFocus: false, + }; + + // Create positioned element + this.positionedElement = new PositionedElement( + this.content, + this.trigger, + positionConfig, + ); + } + + // State machine handlers + onOpenEnter() { + // Initialize positioned element if needed + this.initializePositionedElement(); + + // Activate positioned element + if (this.positionedElement) { + this.positionedElement.activate(); + } + + // Notify the server of the state change + this.pushEvent("opened"); + } + + onOpenExit() { + // Destroy the positioned element because there are too many tooltips, if we keep them all, it will costs more memory + if (this.positionedElement) { + this.positionedElement.destroy(); + this.positionedElement = null; + } + } + + onClosedEnter() { + // Notify the server of the state change + this.pushEvent("closed"); + } + + beforeDestroy() { + this.clearTimers(); + + // Clean up the positioned element if it exists + if (this.positionedElement) { + this.positionedElement.destroy(); + this.positionedElement = null; + } + } +} + +// Register the component +SaladUI.register("tooltip", TooltipComponent); + +export default TooltipComponent; diff --git a/assets/js/ui/core/click-outside.js b/assets/js/ui/core/click-outside.js new file mode 100644 index 0000000..8a0929b --- /dev/null +++ b/assets/js/ui/core/click-outside.js @@ -0,0 +1,121 @@ +// saladui/core/click-outside.js +/** + * ClickOutsideMonitor utility for SaladUI components + * Detects clicks outside of specified elements and triggers a callback + */ +class ClickOutsideMonitor { + /** + * Create a click outside monitor + * + * @param {HTMLElement|HTMLElement[]} elements - Element(s) to monitor clicks outside of + * @param {Function} callback - Function to call when click outside is detected + * @param {Object} options - Additional options + */ + constructor(elements, callback, options = {}) { + // Normalize elements to an array + this.elements = Array.isArray(elements) ? elements : [elements]; + this.callback = callback; + this.options = { + // Whether to also monitor touchend events (for mobile) + trackTouch: true, + // Optional filter function to determine if a click should trigger the callback + filter: null, + ...options, + }; + + this.active = false; + + // Bind methods to maintain correct this context + this.handleClick = this.handleClick.bind(this); + this.handleTouchEnd = this.handleTouchEnd.bind(this); + } + + /** + * Start monitoring clicks outside the element(s) + */ + start() { + if (this.active) return; + + // Add document-level event listeners + document.addEventListener("click", this.handleClick); + if (this.options.trackTouch) { + document.addEventListener("touchend", this.handleTouchEnd); + } + + this.active = true; + } + + /** + * Stop monitoring clicks + */ + stop() { + if (!this.active) return; + + // Remove document-level event listeners + document.removeEventListener("click", this.handleClick); + if (this.options.trackTouch) { + document.removeEventListener("touchend", this.handleTouchEnd); + } + + this.active = false; + } + + /** + * Handle click events + */ + handleClick(event) { + this.checkOutsideClick(event); + } + + /** + * Handle touchend events + */ + handleTouchEnd(event) { + this.checkOutsideClick(event); + } + + /** + * Check if click/touch was outside monitored elements + */ + checkOutsideClick(event) { + // Skip if not active or no callback + if (!this.active || !this.callback) return; + + // Apply custom filter if provided + if (this.options.filter && !this.options.filter(event)) { + return; + } + + // Get the event target + const target = event.target; + + // Check if click was outside all monitored elements + const isOutside = !this.elements.some((element) => { + return element && (element === target || element.contains(target)); + }); + + // If click was outside, call the callback + if (isOutside) { + this.callback(event); + } + } + + /** + * Update the monitored elements + */ + updateElements(elements) { + this.elements = Array.isArray(elements) ? elements : [elements]; + } + + /** + * Clean up all references + */ + destroy() { + this.stop(); + this.elements = null; + this.callback = null; + this.options = null; + } +} + +export default ClickOutsideMonitor; diff --git a/assets/js/ui/core/collection.js b/assets/js/ui/core/collection.js new file mode 100644 index 0000000..d4b504c --- /dev/null +++ b/assets/js/ui/core/collection.js @@ -0,0 +1,337 @@ +// saladui/core/collection-manager.js +/** + * Collection utility for SaladUI components + * Manages collections of items with focus, highlight, and selection states + */ +class Collection { + /** + * Create a collection manager + * + * @param {Object} options - Configuration options + * @param {string} options.type - Collection type: 'single' or 'multiple' + * @param {Array|*} options.defaultValue - Default value(s) if none provided + * @param {Array|*} options.value - Initial value(s) for the collection + * @param {function} options.getItemValue - Function to get value from an item instance + * @param {function} options.isItemDisabled - Function to check if an item is disabled + */ + constructor(options = {}) { + this.options = { + type: "single", + defaultValue: null, + value: null, + getItemValue: (item) => item.value, + isItemDisabled: (item) => item.disabled, + ...options, + }; + + // Initialize collection + this.items = []; + this.focusedItem = null; + + // Initialize values + this.values = []; + if (this.options.value !== null && this.options.value !== undefined) { + this.setValues(this.options.value); + } else if ( + this.options.defaultValue !== null && + this.options.defaultValue !== undefined + ) { + this.setValues(this.options.defaultValue); + } + } + + /** + * Reset the collection + */ + reset() { + this.items = []; + this.values = Array.isArray(this.options.defaultValue) + ? [...this.options.defaultValue] + : this.options.defaultValue + ? [this.options.defaultValue] + : []; + this.focusedItem = null; + } + + /** + * Set collection values + * + * @param {Array|*} values - Value or array of values + */ + setValues(values) { + if (values === undefined || values === null) { + this.values = Array.isArray(this.options.defaultValue) + ? [...this.options.defaultValue] + : this.options.defaultValue + ? [this.options.defaultValue] + : []; + return; + } + + if (this.options.type === "single") { + this.values = Array.isArray(values) ? [values[0]] : [values]; + } else { + this.values = Array.isArray(values) ? [...values] : [values]; + } + + // Update selected state for all items + this.updateSelectedStates(); + } + + /** + * Get the selected value(s) + * For 'multiple' type collections, returns an array of all selected values + * For 'single' type collections, returns just the first selected value (or null if none) + * + * @param {boolean} asArray - Force return value to be an array even for single selection + * @returns {*|Array} Selected value(s) + */ + getValue(asArray = false) { + if (this.options.type === "multiple" || asArray) { + return [...this.values]; + } + return this.values.length > 0 ? this.values[0] : null; + } + + /** + * Add an item to the collection + * + * @param {Object} item - Item instance to add + * @param {*} value - Item value + * @returns {Object} The collection item wrapper + */ + add(item) { + const itemValue = this.options.getItemValue(item); + + const isSelected = this.values.includes(itemValue); + + const collectionItem = { + instance: item, + value: itemValue, + focused: false, + selected: isSelected, + }; + + this.items.push(collectionItem); + + // Initialize item state + if (isSelected && typeof item.handleEvent === "function") { + item.handleEvent("select"); + } + + return collectionItem; + } + + /** + * Remove an item from the collection + * + * @param {Object} itemInstance - Item instance to remove + */ + remove(itemInstance) { + const index = this.items.findIndex( + (item) => item.instance === itemInstance, + ); + if (index >= 0) { + const [removedItem] = this.items.splice(index, 1); + + if (this.focusedItem === removedItem) { + this.focusedItem = null; + } + + // Remove from values if selected + if (removedItem.selected) { + this.values = this.values.filter( + (value) => value !== removedItem.value, + ); + } + } + } + + /** + * Clear all items from the collection + */ + clear() { + this.items = []; + this.focusedItem = null; + } + + /** + * Get item by its instance + * + * @param {Object} itemInstance - Item instance to find + * @returns {Object} Collection item or null if not found + */ + getItemByInstance(itemInstance) { + return this.items.find((item) => item.instance === itemInstance) || null; + } + + /** + * Get item by its value + * + * @param {*} value - Value to find + * @returns {Object} Collection item or null if not found + */ + getItemByValue(value) { + return this.items.find((item) => item.value === value) || null; + } + + /** + * Get an item from the collection based on navigation direction + * + * @param {string} direction - Navigation direction: 'first', 'last', 'next', or 'prev'/'previous' + * @param {Object} referenceItem - Reference item for 'next' and 'prev' directions (optional) + * @param {boolean} loop - Whether to loop when reaching boundaries (optional, default: true) + * @returns {Object} The requested item or null if not found + */ + getItem(direction, referenceItem = null, loop = true) { + const enabledItems = this.items.filter( + (item) => !this.options.isItemDisabled(item.instance), + ); + if (enabledItems.length === 0) return null; + + switch (direction) { + case "first": + return enabledItems[0]; + + case "last": + return enabledItems[enabledItems.length - 1]; + + case "next": + if (!referenceItem) return this.getItem("first"); + + const nextIndex = enabledItems.indexOf(referenceItem) + 1; + if (nextIndex >= enabledItems.length) { + return loop ? enabledItems[0] : null; + } + return enabledItems[nextIndex]; + + case "prev": + case "previous": + if (!referenceItem) return this.getItem("last"); + + const currentIndex = enabledItems.indexOf(referenceItem); + if (currentIndex === -1) return enabledItems[enabledItems.length - 1]; + + const prevIndex = currentIndex - 1; + if (prevIndex < 0) { + return loop ? enabledItems[enabledItems.length - 1] : null; + } + return enabledItems[prevIndex]; + + default: + return null; + } + } + + /** + * Focus an item + * + * @param {Object} item - Item to focus + * @returns {boolean} Whether the operation was successful + */ + focus(item) { + if (!item || this.options.isItemDisabled(item.instance)) return false; + + // Clear previous focus + if (this.focusedItem) { + this.focusedItem.focused = false; + if (typeof this.focusedItem.instance.handleEvent === "function") { + this.focusedItem.instance.handleEvent("blur"); + } + } + + // Set new focus + this.focusedItem = item; + item.focused = true; + + if (typeof item.instance.handleEvent === "function") { + return item.instance.handleEvent("focus") !== false; + } + + return true; + } + + /** + * Select an item + * + * @param {Object} item - Item to select + * @returns {boolean} Whether the operation was successful + */ + select(item) { + if (!item || this.options.isItemDisabled(item.instance)) return false; + + const isMultiple = this.options.type === "multiple"; + + // If it's already selected in single mode, do nothing + if (!isMultiple && item.selected && this.values.length === 1) { + return true; + } + + // For single selection, clear all other selections + if (!isMultiple) { + this.items.forEach((existingItem) => { + if (existingItem !== item && existingItem.selected) { + existingItem.selected = false; + if (typeof existingItem.instance.handleEvent === "function") { + existingItem.instance.handleEvent("unselect"); + } + } + }); + this.values = []; + } + + // Toggle selection for the item + if (item.selected) { + // Unselect the item + item.selected = false; + this.values = this.values.filter((value) => value !== item.value); + + if (typeof item.instance.handleEvent === "function") { + return item.instance.handleEvent("unselect") !== false; + } + } else { + // Select the item + item.selected = true; + this.values.push(item.value); + + if (typeof item.instance.handleEvent === "function") { + return item.instance.handleEvent("select") !== false; + } + } + + return true; + } + + /** + * Update all item selected states based on values + */ + updateSelectedStates() { + this.items.forEach((item) => { + const shouldBeSelected = this.values.includes(item.value); + + if (item.selected !== shouldBeSelected) { + item.selected = shouldBeSelected; + + if (typeof item.instance.handleEvent === "function") { + item.instance.handleEvent(shouldBeSelected ? "select" : "unselect"); + } + } + }); + } + + /** + * Check if a value is selected + * + * @param {*} value - Value to check + * @returns {boolean} Whether the value is selected + */ + isValueSelected(value) { + return this.values.includes(value); + } + + each(callback) { + this.items.forEach((item) => callback(item.instance)); + } +} + +export default Collection; diff --git a/assets/js/ui/core/component.js b/assets/js/ui/core/component.js new file mode 100644 index 0000000..cc98a3b --- /dev/null +++ b/assets/js/ui/core/component.js @@ -0,0 +1,548 @@ +// saladui/core/component.js +/** + * Base Component class for SaladUI framework + * Provides state management, event handling, and ARIA support + */ +import StateMachine from "./state-machine"; +import { animateTransition, queryDOM } from "./utils"; + +class Component { + constructor(el, options) { + const { hookContext, initialState = "idle", ignoreItems = true } = options; + + this.el = el; + this.hook = hookContext; + + this.config = { + preventDefaultKeys: [], + }; + + this.initialState = initialState; + this.eventConfig = {}; + this.componentConfig = {}; + this.hiddenConfig = {}; + this.ariaConfig = {}; + + // Initialize component + this.parseOptions(); + this.disabled = !!this.options.disabled; + this.initEventMappings(); + this.initConfig(); + this.initStateMachine(this.componentConfig.stateMachine, this.initialState); + this.ariaManager = new AriaManager(this, this.ariaConfig); + + // ignore item's part + this.allParts = this.queryParts(); + if (ignoreItems) { + this.allParts = this.allParts.filter( + (element) => + !element.dataset.part.startsWith("item") && + !element.dataset.part.endsWith("-item"), + ); + } + + this.updateUI(); + this.updatePartsVisibility(); + + // Map to store event handlers for each part element + this.partMouseEventHandlers = new Map(); + this.keyEventHandlers = new Map(); + } + + parseOptions() { + try { + const optionsString = this.el.getAttribute("data-options"); + this.options = optionsString ? JSON.parse(optionsString) : {}; + this.initialState = + this.el.getAttribute("data-state") || this.initialState; + } catch (error) { + console.error("SaladUI: Error parsing component options:", error); + this.options = {}; + } + } + + queryParts() { + return queryDOM(this.el, (node) => { + if (!node.dataset?.part) return 0; + if (node.getAttribute("phx-hook") != null) return -1; + return 1; + }).concat([this.el]); + } + + initEventMappings() { + this.onClientCommand = this.onClientCommand.bind(this); + + try { + const mappingsString = this.el.getAttribute("data-event-mappings"); + this.eventMappings = mappingsString ? JSON.parse(mappingsString) : {}; + } catch (error) { + console.error("SaladUI: Error parsing event mappings:", error); + this.eventMappings = {}; + } + } + + /** + * Initialize component configuration + * This method should set up the componentConfig object with stateMachine, events, and ariaConfig + */ + initConfig() { + this.componentConfig = this.getComponentConfig(); + + // Add default configs if not provided + if (!this.componentConfig.stateMachine) { + this.componentConfig.stateMachine = { + idle: { + enter: () => {}, + exit: () => {}, + transitions: {}, + }, + }; + } else { + this.componentConfig.stateMachine = this.bindStateHandlers( + this.componentConfig.stateMachine, + ); + } + + this.eventConfig = this.componentConfig.events || {}; + this.hiddenConfig = this.componentConfig.hiddenConfig || {}; + this.ariaConfig = this.componentConfig.ariaConfig || {}; + } + + /** + * Get component configuration + * Override in subclasses to provide component-specific configuration + * @returns {Object} Configuration object with stateMachine, events, and ariaConfig + */ + getComponentConfig() { + throw new Error("getComponentConfig() must be implemented in subclass"); + } + + initStateMachine(stateMachineConfig, initialState) { + this.stateMachine = new StateMachine(stateMachineConfig, initialState, { + onStateChanged: this.onStateChanged.bind(this), + }); + } + + // Handle client commands + onClientCommand(event) { + console.log(event); + const { command, params } = event.detail; + if (command) { + this.handleCommand(command, params); + } + } + + onStateChanged(prevState, nextState, params) { + // Check if we should animate + const transitionName = `${prevState}_to_${nextState}`; + const animConfig = this.options.animations?.[transitionName]; + this.updateUI(); + + if (!animConfig) { + // No animation, update visibility immediately + this.updatePartsVisibility(nextState); + return null; // No promise + } + + // Get target element for animation + const targetElement = animConfig.target_part + ? this.getPart(animConfig.target_part) + : this.el; + + // Animate with the config + return animateTransition(animConfig, targetElement).then(() => { + this.updatePartsVisibility(nextState); + }); + } + + /** + * Process the state machine configuration to automatically bind string method references + * to instance methods for enter and exit handlers + * + * @param {Object} config - The original state machine configuration + * @returns {Object} - The processed configuration with bound enter/exit methods + */ + bindStateHandlers(stateMachineConfig) { + // Process each state + Object.keys(stateMachineConfig).forEach((stateName) => { + const stateConfig = stateMachineConfig[stateName]; + + ["enter", "exit"].forEach((handlerName) => { + // Process handler if it's a string + if (typeof stateConfig[handlerName] === "string") { + const methodName = stateConfig[handlerName]; + if (typeof this[methodName] === "function") { + stateConfig[handlerName] = this[methodName].bind(this); + } else { + console.warn( + `Method ${methodName} not found for ${handlerName} handler in state ${stateName}`, + ); + } + } + }); + }); + + return stateMachineConfig; + } + + setupEvents() { + if (this.eventSetupCompleted) { + this.removeAllEvents(); + } + + this.el.addEventListener("salad_ui:command", this.onClientCommand); + + this.el.addEventListener("click", this.handleActionClick.bind(this)); + + this.setupKeyEventHandlers(); + this.setupMouseEventHandlers(); + + this.setupComponentEvents(); + this.eventSetupCompleted = true; + } + + /** + * Handle click events on action elements + * Transition with the action attribute value + */ + handleActionClick(event) { + const actionElement = event.target.closest("[data-action]"); + if (!actionElement) return; + + const action = actionElement.getAttribute("data-action"); + this.transition(action, { + originalEvent: event, + target: actionElement, + }); + } + + setupComponentEvents() { + // Override in component subclasses + } + + /** + * Set up event listeners for mouse events based on the current state + */ + setupKeyEventHandlers() { + Object.keys(this.eventConfig).forEach((stateName) => { + const stateEvents = this.eventConfig[stateName]; + if (!stateEvents || !stateEvents.keyMap) return; + + // Create a bound handler that will check the current state before executing + const boundHandler = (event) => { + if (stateName == "_all" || this.stateMachine.state === stateName) { + const key = event.key; + const action = stateEvents.keyMap[key]; + + if (action) { + this.executeHandler(action, event); + if (this.config.preventDefaultKeys.includes(key)) { + event.preventDefault(); + } + } + } + }; + + // Get the target element for key events, if not specified, use the root element + const element = this.getPart(stateEvents.keyEventTarget) || this.el; + + element.addEventListener("keydown", boundHandler); + this.keyEventHandlers.set(element, boundHandler); + }); + } + + /** + * Set up event listeners for mouse events based on the current state + */ + setupMouseEventHandlers() { + // Process all states for their mouse events + Object.keys(this.eventConfig).forEach((stateName) => { + const stateEvents = this.eventConfig[stateName]; + if (!stateEvents || !stateEvents.mouseMap) return; + + const mouseMap = stateEvents.mouseMap; + + // For each part in the mouseMap + Object.keys(mouseMap).forEach((partName) => { + // Get all elements with this part name + const partElements = this.getAllParts(partName); + + if (!partElements.length) return; + + // For each event type on this part + Object.keys(mouseMap[partName]).forEach((eventType) => { + const handlerAction = mouseMap[partName][eventType]; + + // Create a bound handler that will check the current state before executing + const boundHandler = (event) => { + // Only execute the handler if we're in the correct state + const currentState = this.stateMachine.state; + if (currentState === stateName) { + this.executeHandler(handlerAction, event); + } + }; + + // For each element with this part + partElements.forEach((element) => { + // Add event listener directly to the part element + element.addEventListener(eventType, boundHandler); + + // Store the handler reference for removal later + if (!this.partMouseEventHandlers.has(element)) { + this.partMouseEventHandlers.set(element, new Map()); + } + + const elementHandlers = this.partMouseEventHandlers.get(element); + if (!elementHandlers.has(eventType)) { + elementHandlers.set(eventType, []); + } + + elementHandlers.get(eventType).push(boundHandler); + }); + }); + }); + }); + } + + removeKeyEventHandlers() { + if (this.keyEventHandlers) { + // For each element that has event handlers + this.keyEventHandlers.forEach((handler, element) => { + element.removeEventListener("keydown", handler); + }); + + // Clear the map for future use + this.keyEventHandlers.clear(); + } + } + + /** + * Remove all active mouse event listeners + */ + removeMouseEventListeners() { + if (this.partMouseEventHandlers) { + // For each element that has event handlers + this.partMouseEventHandlers.forEach((eventHandlers, element) => { + // For each event type on this element + eventHandlers.forEach((handlers, eventType) => { + // Remove all handlers for this event type + handlers.forEach((handler) => { + element.removeEventListener(eventType, handler); + }); + }); + }); + + // Clear the map for future use + this.partMouseEventHandlers.clear(); + } + } + + /** + * Execute a handler from a mouseMap or keyMap + */ + executeHandler(handler, event, targetElement) { + if (typeof handler === "function") { + handler.call(this, event); + } else if (typeof handler === "string") { + if (typeof this[handler] === "function") { + this[handler](event); + } else { + // If it's not a method name, treat as transition + this.transition(handler, { + originalEvent: event, + target: targetElement, + }); + } + } + } + + /** + * Transition to a new state - delegates to the state machine + */ + transition(event, params = {}) { + return this.stateMachine.transition(event, params); + } + + /** + * Update UI to reflect current state + * @param {Object} params - Optional parameters from state transition + */ + updateUI(params = {}) { + const currentState = this.stateMachine.state; + + // Update data-state attributes on all parts and root element + this.allParts.forEach((el) => el.setAttribute("data-state", currentState)); + this.el.setAttribute("data-state", currentState); + + // Apply ARIA attributes + this.ariaManager.applyAriaAttributes(currentState); + } + + /** + * Update part visibility based on current state configuration + */ + updatePartsVisibility() { + const currentState = this.stateMachine.state; + const stateVisibility = this.hiddenConfig[currentState]; + if (!stateVisibility) return; + + Object.entries(stateVisibility).forEach(([partName, hidden]) => { + const partElements = this.getAllParts(partName); + partElements.forEach((element) => { + if (element) { + element.hidden = hidden; + } + }); + }); + } + + getPart(name) { + return this.allParts.find((part) => part.dataset.part === name); + } + + getAllParts(name) { + return this.allParts.filter((part) => part.dataset.part === name); + } + + getPartId(partName) { + const part = this.getPart(partName); + if (!part) return null; + + if (!part.id) { + part.id = `${this.el.id}-${partName}`; + } + return part.id; + } + + // Push event to server (for frameworks like Phoenix LiveView) + pushEvent(clientEvent, payload = {}, context) { + if (!this.hook || !this.hook.pushEventTo) return; + + const eventHandler = this.eventMappings[clientEvent]; + const el = context || this.el; + + if (eventHandler) { + if (typeof eventHandler === "string") { + const fullPayload = { + ...payload, + componentId: el.id, + component: el.getAttribute("data-component"), + }; + + this.hook.pushEventTo(this.el, eventHandler, fullPayload); + } else { + this.hook.liveSocket.execJS(this.el, JSON.stringify(eventHandler)); + } + } + } + + // Get current state from state machine + get state() { + return this.stateMachine.state; + } + + // Get previous state from state machine + get previousState() { + return this.stateMachine.previousState; + } + + removeAllEvents() { + this.el.removeEventListener("salad_ui:command", this.onClientCommand); + this.el.removeEventListener("click", this.handleActionClick); + this.removeKeyEventHandlers(); + this.removeMouseEventListeners(); + } + + // Cleanup method to remove event listeners and references + destroy() { + // Lifecycle hook before destruction + this.beforeDestroy(); + + // Remove event listeners + this.removeAllEvents(); + this.ariaManager = null; + + // Allow garbage collection + this.stateMachine = null; + this.el = null; + this.hook = null; + this.options = null; + this.componentConfig = null; + } + + // Lifecycle hooks + beforeDestroy() {} + + // Alias for transition() + handleCommand(command, params = {}) { + return this.transition(command, params); + } + + // Alias for transition() + trigger(event, params = {}) { + return this.transition(event, params); + } +} + +/** + * AriaManager class for handling accessibility attributes + */ +class AriaManager { + constructor(component, ariaConfig) { + this.component = component; + this.ariaConfig = ariaConfig || {}; + } + + applyAriaAttributes(currentState) { + if (!this.ariaConfig) return; + + Object.entries(this.ariaConfig).forEach(([partName, states]) => { + // Get all elements with this data-part, not just the first one + const parts = this.component.getAllParts(partName); + if (!parts || parts.length === 0) return; + + // Apply attributes to all matching elements + parts.forEach((part, index) => { + // Set ID if not already defined + if (!part.id) { + part.id = `${this.component.el.id}-${partName}${parts.length > 1 ? `-${index}` : ""}`; + } + + this.applyGlobalAriaAttributes(part, states); + this.applyStateSpecificAriaAttributes(part, states, currentState); + }); + }); + } + + applyGlobalAriaAttributes(part, states) { + if (!states.all) return; + + Object.entries(states.all).forEach(([attr, value]) => { + this.applyAriaAttribute(part, attr, value); + }); + } + + applyStateSpecificAriaAttributes(part, states, currentState) { + const stateConfig = states[currentState]; + if (!stateConfig) return; + + Object.entries(stateConfig).forEach(([attr, value]) => { + this.applyAriaAttribute(part, attr, value); + }); + } + + applyAriaAttribute(part, attr, value) { + const resolvedValue = + typeof value === "function" ? value.call(this.component, part) : value; + + if (resolvedValue === null || resolvedValue === undefined) return; + + if (attr === "role") { + part.setAttribute("role", resolvedValue); + } else { + part.setAttribute(`aria-${attr}`, resolvedValue); + } + } +} + +export default Component; diff --git a/assets/js/ui/core/factory.js b/assets/js/ui/core/factory.js new file mode 100644 index 0000000..4d56bdd --- /dev/null +++ b/assets/js/ui/core/factory.js @@ -0,0 +1,30 @@ +// saladui/core/factory.js +class ComponentRegistry { + constructor() { + this.registry = new Map(); + } + + register(type, ComponentClass) { + this.registry.set(type, ComponentClass); + return this; + } + + create(type, el, hookContext) { + const ComponentClass = this.registry.get(type); + if (!ComponentClass) { + console.error(`Component type '${type}' not registered`); + return null; + } + + const instance = new ComponentClass(el, hookContext); + + // Call setupEvents after the component is fully initialized + instance.setupEvents(); + + return instance; + } +} + +const registry = new ComponentRegistry(); + +export { registry }; diff --git a/assets/js/ui/core/focus-trap.js b/assets/js/ui/core/focus-trap.js new file mode 100644 index 0000000..4ccfcab --- /dev/null +++ b/assets/js/ui/core/focus-trap.js @@ -0,0 +1,154 @@ +// saladui/core/focus-trap.js +/** + * FocusTrap utility for SaladUI components + * Manages focus behavior for modals, popovers, and similar components + */ +class FocusTrap { + /** + * Create a focus trap for a specific element + * + * @param {HTMLElement} element - The element to trap focus within + * @param {Object} options - Focus trap options + */ + constructor(element, options = {}) { + this.element = element; + this.options = { + focusableSelector: + 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])', + ...options, + }; + + this.previouslyFocused = null; + this.active = false; + + // Bind methods that will be used as event handlers + this.handleKeyDown = this.handleKeyDown.bind(this); + } + + /** + * Activate the focus trap + */ + activate() { + if (this.active) return; + + // Store currently focused element to restore later + this.previouslyFocused = document.activeElement; + this.active = true; + + // Set up event listener for keyboard navigation + this.element.addEventListener("keydown", this.handleKeyDown); + + // Focus the first focusable element or the element itself + this.setInitialFocus(); + } + + /** + * Deactivate the focus trap and restore previous focus + */ + deactivate() { + if (!this.active) return; + + // Remove event listeners + this.element.removeEventListener("keydown", this.handleKeyDown); + + // Restore focus if possible + if ( + this.previouslyFocused && + this.previouslyFocused.focus && + this.isElementInViewport(this.previouslyFocused) + ) { + setTimeout(() => { + this.previouslyFocused.focus(); + this.previouslyFocused = null; + }, 0); + } + + this.active = false; + } + + /** + * Set initial focus when trap is activated + */ + setInitialFocus() { + // Find all focusable elements + const focusableElements = this.getFocusableElements(); + + setTimeout(() => { + if (focusableElements.length > 0) { + // Look for an element with autofocus attribute first + const autoFocusEl = this.element.querySelector("[autofocus]"); + const initialFocusEl = autoFocusEl || focusableElements[0]; + initialFocusEl.focus(); + } else { + // If no focusable elements, make the element itself focusable + this.element.setAttribute("tabindex", "-1"); + this.element.focus(); + } + }, 50); // Small delay to ensure DOM is ready + } + + /** + * Handle keydown events for tab trapping and escape handling + */ + handleKeyDown(event) { + // Handle Tab key for focus trapping + if (event.key === "Tab") { + const focusableElements = this.getFocusableElements(); + + if (focusableElements.length === 0) return; + + const firstElement = focusableElements[0]; + const lastElement = focusableElements[focusableElements.length - 1]; + const activeElement = document.activeElement; + + // Create focus loop + if (!event.shiftKey && activeElement === lastElement) { + firstElement.focus(); + event.preventDefault(); + } else if (event.shiftKey && activeElement === firstElement) { + lastElement.focus(); + event.preventDefault(); + } + } + } + + /** + * Get all focusable elements within the trap + */ + getFocusableElements() { + return Array.from( + this.element.querySelectorAll(this.options.focusableSelector), + ); + } + + /** + * Check if an element is currently visible in the viewport + */ + isElementInViewport(element) { + if (!element || !document.body.contains(element)) { + return false; + } + + const rect = element.getBoundingClientRect(); + + return ( + rect.top >= 0 && + rect.left >= 0 && + rect.bottom <= + (window.innerHeight || document.documentElement.clientHeight) && + rect.right <= (window.innerWidth || document.documentElement.clientWidth) + ); + } + + /** + * Clean up all references when no longer needed + */ + destroy() { + this.deactivate(); + this.element = null; + this.options = null; + this.previouslyFocused = null; + } +} + +export default FocusTrap; diff --git a/assets/js/ui/core/hook.js b/assets/js/ui/core/hook.js new file mode 100644 index 0000000..e11658d --- /dev/null +++ b/assets/js/ui/core/hook.js @@ -0,0 +1,55 @@ +// saladui/core/hook.js +import { registry } from "./factory"; + +const SaladUIHook = { + mounted() { + this.initComponent(); + this.setupServerEvents(); + }, + + initComponent() { + const el = this.el; + const componentType = el.getAttribute("data-component"); + + if (!componentType) { + console.error( + "SaladUI: Component element is missing data-component attribute", + ); + return; + } + + // The registry.create method will handle creating the component and calling setupEvents + this.component = registry.create(componentType, el, this); + }, + + setupServerEvents() { + if (!this.component) return; + + this.handleEvent("saladui:command", ({ command, params = {}, target }) => { + if (target && target !== this.el.id) return; + + if (this.component) { + this.component.handleCommand(command, params); + } + }); + }, + + updated() { + if (this.component) { + this.component.destroy(); + this.component = null; + this.initComponent(); + // this.component.parseOptions(); + // this.component.setupEvents(); + // this.component.updatePartsVisibility(); + // this.component.updateUI(); + } + }, + + destroyed() { + this.component?.destroy(); + this.component = null; + }, +}; + +export { SaladUIHook }; diff --git a/assets/js/ui/core/portal.js b/assets/js/ui/core/portal.js new file mode 100644 index 0000000..979db39 --- /dev/null +++ b/assets/js/ui/core/portal.js @@ -0,0 +1,186 @@ +// saladui/core/portal.js +/** + * Portal utility for SaladUI components + * Manages moving elements to a different DOM context (usually body) + * to avoid z-index and overflow issues + */ +class Portal { + // Static storage for element metadata + static portalRegistry = new WeakMap(); + + /** + * Move an element to a portal container + * + * @param {HTMLElement} element - Element to move to the portal + * @param {HTMLElement} container - Container to move the element to (default: document.body) + * @returns {boolean} Success status + */ + static move(element, container = document.body) { + if (!element) return false; + + // Store original information for restoration + const originalData = { + parent: element.parentElement, + styles: { + position: element.style.position, + top: element.style.top, + left: element.style.left, + zIndex: element.style.zIndex, + margin: element.style.margin, + transform: element.style.transform, + pointerEvents: element.style.pointerEvents, + }, + inPortal: true, + }; + + // Store the metadata in our registry + this.portalRegistry.set(element, originalData); + + // Move the element to the portal container + container.appendChild(element); + + // Apply portal styles + element.style.position = "absolute"; + element.style.zIndex = "9999"; + + return true; + } + + /** + * Restore an element from portal to its original position + * + * @param {HTMLElement} element - Element to restore + * @returns {boolean} Success status + */ + static restore(element) { + if (!element) return false; + + // Get the original data from our registry + const originalData = this.portalRegistry.get(element); + + if (!originalData || !originalData.parent) { + return false; + } + + try { + // Move back to original parent + originalData.parent.appendChild(element); + + // Restore original styles + const styles = originalData.styles; + element.style.position = styles.position || ""; + element.style.top = styles.top || ""; + element.style.left = styles.left || ""; + element.style.zIndex = styles.zIndex || ""; + element.style.margin = styles.margin || ""; + element.style.transform = styles.transform || ""; + element.style.pointerEvents = styles.pointerEvents || ""; + + // Update portal state + originalData.inPortal = false; + + return true; + } catch (error) { + console.warn("SaladUI Portal: Failed to restore element", error); + return false; + } + } + + /** + * Check if an element is currently in a portal + * + * @param {HTMLElement} element - Element to check + * @returns {boolean} Whether the element is in a portal + */ + static isInPortal(element) { + if (!element) return false; + const data = this.portalRegistry.get(element); + return data?.inPortal === true; + } + + /** + * Setup scroll event passthrough for a portal element + * Makes the portal element transparent to pointer events except for interactive elements + * + * @param {HTMLElement} element - Portal element to set up scroll passthrough for + */ + static setupScrollPassthrough(element) { + if (!element) return; + + // Get original data from registry to ensure styles are properly tracked + const originalData = this.portalRegistry.get(element); + if (originalData) { + originalData.styles.pointerEvents = element.style.pointerEvents; + } + + // Make the portal element transparent to pointer events + element.style.pointerEvents = "none"; + + Portal.updateScrollableContainer(element, "auto"); + } + + static updateScrollableContainer(parentElement, pointerEvent = "") { + // Check if the current element is scrollable + function isScrollable(element) { + const style = window.getComputedStyle(element); + const overflowY = style.overflowY; + const overflowX = style.overflowX; + + const isScrollableY = element.scrollHeight > element.clientHeight; + const isScrollableX = element.scrollWidth > element.clientWidth; + + return ( + ((overflowY === "auto" || + overflowY === "scroll" || + overflowY === "overlay") && + isScrollableY) || + ((overflowX === "auto" || + overflowX === "scroll" || + overflowX === "overlay") && + isScrollableX) + ); + } + + // Recursive function to traverse DOM tree + function traverse(element) { + // Check if current element is scrollable + if (isScrollable(element)) { + // Set pointer-events to auto + element.style.pointerEvents = pointerEvent; + // Stop traversing this branch + return; + } + + // Process child elements if current element isn't scrollable + for (let i = 0; i < element.children.length; i++) { + traverse(element.children[i]); + } + } + + // Start traversal from parent + traverse(parentElement); + + // No return value as requested + } + + /** + * Clean up scroll passthrough setup + * + * @param {HTMLElement} element - Element to clean up + */ + static cleanupScrollPassthrough(element) { + if (!element) return; + + // Get the original data from registry if available + const originalData = this.portalRegistry.get(element); + const originalPointerEvents = originalData?.styles?.pointerEvents || ""; + + // Restore pointer-events on the element + element.style.pointerEvents = originalPointerEvents; + + // Reset pointer-events on all children that might have been modified + Portal.updateScrollableContainer(element, ""); + } +} + +export default Portal; diff --git a/assets/js/ui/core/positioned-element.js b/assets/js/ui/core/positioned-element.js new file mode 100644 index 0000000..07703f8 --- /dev/null +++ b/assets/js/ui/core/positioned-element.js @@ -0,0 +1,353 @@ +// saladui/core/positioned-element.js - Updated for fixed positioning +/** + * PositionedElement - Main positioning class that integrates all positioning utilities + */ +import Positioner from "./positioner"; +import FocusTrap from "./focus-trap"; +import ClickOutsideMonitor from "./click-outside"; +import Portal from "./portal"; +import ScrollManager from "./scroll-manager"; + +class PositionedElement { + /** + * Create a positioned element with full functionality + * + * @param {HTMLElement} element - Element to position + * @param {HTMLElement} reference - Reference element to position against + * @param {Object} options - Positioning options + */ + constructor(element, reference, options = {}) { + this.element = element; + this.reference = reference; + this.options = { + // Positioning options + placement: "bottom", + alignment: "center", + sideOffset: 8, + alignOffset: 0, + flip: true, + + // Portal options + usePortal: false, + portalContainer: document.body, + + // Focus management + trapFocus: false, + focusableSelector: + 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])', + + // Event handlers + onOutsideClick: null, + scrollPassThrough: false, + + ...options, + }; + + // State + this.active = false; + + // Initialize sub-modules + this.initializeModules(); + } + + /** + * Initialize all required modules + */ + initializeModules() { + // Focus trap for keyboard navigation + this.focusTrap = new FocusTrap(this.element, { + focusableSelector: this.options.focusableSelector, + }); + + // Click outside detection + this.clickOutsideMonitor = this.options.onOutsideClick + ? new ClickOutsideMonitor( + [this.element, this.reference], + this.options.onOutsideClick, + ) + : null; + + // Scroll and resize handling + this.scrollManager = new ScrollManager(() => { + this.update(); + }); + + // Bind methods for event handlers + this.handleWheel = this.handleWheel.bind(this); + this.handleTouchStart = this.handleTouchStart.bind(this); + this.handleTouchMove = this.handleTouchMove.bind(this); + } + + /** + * Activate the positioned element + */ + activate() { + if (this.active) return this; + + // Move to portal if enabled + if (this.options.usePortal) { + this.moveToPortal(); + } + + // Calculate and apply initial position + this.calculateAndApplyPosition(); + + // Activate sub-modules + if (this.options.trapFocus) { + this.focusTrap.activate(); + } + + if (this.clickOutsideMonitor) { + this.clickOutsideMonitor.start(); + } + + this.scrollManager.start(this.reference, this.element); + + // Add wheel and touch event handlers if in portal + if (Portal.isInPortal(this.element) && this.options.scrollPassThrough) { + this.setupScrollPassthrough(); + } + + // set reference width and height ass css variable + this.element.style.setProperty( + "--salad-reference-width", + this.reference.offsetWidth + "px", + ); + this.element.style.setProperty( + "--salad-reference-height", + this.reference.offsetHeight + "px", + ); + + this.active = true; + return this; + } + + /** + * Deactivate the positioned element + */ + deactivate() { + if (!this.active) return this; + + // Deactivate sub-modules + if (this.options.trapFocus) { + this.focusTrap.deactivate(); + } + + if (this.clickOutsideMonitor) { + this.clickOutsideMonitor.stop(); + } + + this.scrollManager.stop(); + + // Clean up scroll passthrough if in portal + if (Portal.isInPortal(this.element) && this.options.scrollPassThrough) { + this.cleanupScrollPassthrough(); + } + + // Restore from portal if needed + if (this.inPortal) { + this.restoreFromPortal(); + } + + this.active = false; + return this; + } + + /** + * Update position + */ + update() { + if (this.active) { + this.calculateAndApplyPosition(); + } + return this; + } + + /** + * Move element to portal container + */ + moveToPortal() { + if (Portal.isInPortal(this.element)) return; + + const container = this.options.portalContainer || document.body; + Portal.move(this.element, container); + } + + /** + * Restore element from portal + */ + restoreFromPortal() { + if (!Portal.isInPortal(this.element)) return; + + Portal.restore(this.element); + } + + /** + * Calculate and apply position to the element + */ + calculateAndApplyPosition() { + const position = Positioner.calculate( + this.element, + this.reference, + this.options, + ); + + // Apply positioning - with fixed positioning, we no longer need to + // adjust for scroll position since fixed is relative to the viewport + Positioner.applyPosition(this.element, position.x, position.y); + + // Update placement attribute + this.element.setAttribute("data-placement", position.placement); + + return position; + } + + /** + * Set up scroll event passthrough + */ + setupScrollPassthrough() { + Portal.setupScrollPassthrough(this.element, this.options.focusableSelector); + + // Add wheel and touch event handlers + this.element.addEventListener("wheel", this.handleWheel, { + passive: false, + }); + this.element.addEventListener("touchstart", this.handleTouchStart, { + passive: false, + }); + this.element.addEventListener("touchmove", this.handleTouchMove, { + passive: false, + }); + } + + /** + * Clean up scroll passthrough + */ + cleanupScrollPassthrough() { + if (!this.element) return; + + // Remove wheel and touch event handlers + this.element.removeEventListener("wheel", this.handleWheel); + this.element.removeEventListener("touchstart", this.handleTouchStart); + this.element.removeEventListener("touchmove", this.handleTouchMove); + + // Clean up styles + Portal.cleanupScrollPassthrough(this.element); + } + + /** + * Handle wheel events for scroll passthrough + */ + handleWheel(event) { + // Let the wheel event pass through + event.stopPropagation(); + } + + /** + * Handle touch start for scroll passthrough + */ + handleTouchStart(event) { + // Store initial touch position + if (event.touches.length === 1) { + this.touchStartY = event.touches[0].clientY; + } + } + + /** + * Handle touch move for scroll passthrough + */ + handleTouchMove(event) { + if (!this.touchStartY) return; + + // Determine scroll direction + const touchY = event.touches[0].clientY; + const deltaY = this.touchStartY - touchY; + this.touchStartY = touchY; + + // Find element that should receive scroll + const elementsFromPoint = document.elementsFromPoint( + event.touches[0].clientX, + event.touches[0].clientY, + ); + + // Find first scrollable element that is not our portal + const scrollableElement = elementsFromPoint.find((el) => { + if (el === this.element || this.element.contains(el)) return false; + + const style = window.getComputedStyle(el); + return ( + style.overflowY === "auto" || + style.overflowY === "scroll" || + el === document.documentElement + ); + }); + + if (scrollableElement) { + // Pass scroll to found element + scrollableElement.scrollTop += deltaY; + event.preventDefault(); + } + } + + /** + * Update the reference element + */ + updateReference(reference) { + this.reference = reference; + + // Update click outside monitor + if (this.clickOutsideMonitor) { + this.clickOutsideMonitor.updateElements([this.element, this.reference]); + } + + this.update(); + return this; + } + + /** + * Update options + */ + updateOptions(options = {}) { + this.options = { ...this.options, ...options }; + + // Update focus trap options if needed + if (this.focusTrap && options.focusableSelector) { + this.focusTrap.options = { + ...this.focusTrap.options, + focusableSelector: options.focusableSelector, + }; + } + + this.update(); + return this; + } + + /** + * Clean up and destroy the positioned element + */ + destroy() { + this.deactivate(); + + // Destroy sub-modules + this.focusTrap.destroy(); + if (this.clickOutsideMonitor) { + this.clickOutsideMonitor.destroy(); + } + this.scrollManager.destroy(); + + if (Portal.isInPortal(this.element)) { + this.element.remove(); + } + + // Clear references + this.element = null; + this.reference = null; + this.options = null; + this.focusTrap = null; + this.clickOutsideMonitor = null; + this.scrollManager = null; + this.touchStartY = null; + } +} + +export default PositionedElement; diff --git a/assets/js/ui/core/positioner.js b/assets/js/ui/core/positioner.js new file mode 100644 index 0000000..8e3ec0a --- /dev/null +++ b/assets/js/ui/core/positioner.js @@ -0,0 +1,238 @@ +// saladui/core/positioner.js - Updated to use fixed positioning +/** + * Core Positioning utility for SaladUI components + * Handles pure positioning calculations without side effects + */ +class Positioner { + /** + * Calculate position for an element relative to a reference element + * + * @param {HTMLElement} element - The element to position + * @param {HTMLElement} reference - The reference element to position against + * @param {Object} options - Positioning options + * @returns {Object} The computed position data + */ + static calculate(element, reference, options = {}) { + const { + placement = "bottom", + alignment = "center", + container = document.body, + flip = true, + alignOffset = 0, + sideOffset = 8, + } = options; + + // Get element and reference rects for positioning + const referenceRect = reference.getBoundingClientRect(); + const elementRect = { + width: element.offsetWidth, + height: element.offsetHeight, + }; + + // Find container bounds + let containerRect; + if (container === document.body) { + containerRect = { + top: 0, + right: window.innerWidth, + bottom: window.innerHeight, + left: 0, + width: window.innerWidth, + height: window.innerHeight, + }; + } else { + containerRect = container.getBoundingClientRect(); + } + + // Calculate initial position + let { x, y } = this.getBasePosition( + placement, + alignment, + elementRect, + referenceRect, + alignOffset, + sideOffset, + ); + + // Apply flipping if needed + let actualPlacement = placement; + if (flip) { + const flippedPlacement = this.getFlippedPlacement( + placement, + { x, y, width: elementRect.width, height: elementRect.height }, + containerRect, + ); + + if (flippedPlacement !== placement) { + actualPlacement = flippedPlacement; + const flippedPosition = this.getBasePosition( + flippedPlacement, + alignment, + elementRect, + referenceRect, + alignOffset, + sideOffset, + ); + x = flippedPosition.x; + y = flippedPosition.y; + } + } + + return { + x, + y, + placement: actualPlacement, + }; + } + + /** + * Apply position to an element + * @param {HTMLElement} element - Element to position + * @param {number} x - X coordinate + * @param {number} y - Y coordinate + */ + static applyPosition(element, x, y) { + element.style.position = "fixed"; + // element.style.transform = `translate(${x}px, ${y}px)`; + element.style.top = y + "px"; + element.style.left = x + "px"; + element.style.margin = "0"; // Reset margins to avoid positioning issues + } + + /** + * Calculate base position based on placement and alignment + */ + static getBasePosition( + placement, + alignment, + elementRect, + referenceRect, + alignOffset = 0, + sideOffset = 8, + ) { + let x = 0; + let y = 0; + + // Position based on placement + switch (placement) { + case "top": + y = referenceRect.top - elementRect.height - sideOffset; + break; + case "right": + x = referenceRect.right + sideOffset; + y = referenceRect.top; + break; + case "bottom": + y = referenceRect.bottom + sideOffset; + break; + case "left": + x = referenceRect.left - elementRect.width - sideOffset; + y = referenceRect.top; + break; + } + + // Adjust based on alignment + switch (alignment) { + case "start": + if (placement === "top" || placement === "bottom") { + x = referenceRect.left + alignOffset; + } else { + y = referenceRect.top + alignOffset; + } + break; + case "center": + if (placement === "top" || placement === "bottom") { + x = + referenceRect.left + + referenceRect.width / 2 - + elementRect.width / 2 + + alignOffset; + } else { + y = + referenceRect.top + + referenceRect.height / 2 - + elementRect.height / 2 + + alignOffset; + } + break; + case "end": + if (placement === "top" || placement === "bottom") { + x = referenceRect.right - elementRect.width + alignOffset; + } else { + y = referenceRect.bottom - elementRect.height + alignOffset; + } + break; + } + + return { x, y }; + } + + /** + * Determine if placement should be flipped due to lack of space + */ + static getFlippedPlacement(placement, elementCoords, containerRect) { + const { x, y, width, height } = elementCoords; + + // Check if element would overflow container + const overflowTop = y < containerRect.top; + const overflowRight = x + width > containerRect.right; + const overflowBottom = y + height > containerRect.bottom; + const overflowLeft = x < containerRect.left; + + // Determine if we need to flip + switch (placement) { + case "top": + if (overflowTop && !overflowBottom) { + return "bottom"; + } + break; + case "right": + if (overflowRight && !overflowLeft) { + return "left"; + } + break; + case "bottom": + if (overflowBottom && !overflowTop) { + return "top"; + } + break; + case "left": + if (overflowLeft && !overflowRight) { + return "right"; + } + break; + } + + return placement; + } + + /** + * Utility method to find all scrollable parent elements + */ + static findScrollableParents(element) { + const scrollableParents = []; + let currentElement = element; + + while (currentElement && currentElement !== document.body) { + const style = window.getComputedStyle(currentElement); + if ( + style.overflow === "auto" || + style.overflow === "scroll" || + style.overflowX === "auto" || + style.overflowX === "scroll" || + style.overflowY === "auto" || + style.overflowY === "scroll" + ) { + scrollableParents.push(currentElement); + } + currentElement = currentElement.parentElement; + } + + // Always include window for global scrolling + scrollableParents.push(window); + + return scrollableParents; + } +} + +export default Positioner; diff --git a/assets/js/ui/core/scroll-manager.js b/assets/js/ui/core/scroll-manager.js new file mode 100644 index 0000000..6f9069d --- /dev/null +++ b/assets/js/ui/core/scroll-manager.js @@ -0,0 +1,178 @@ +// saladui/core/scroll-manager.js +/** + * ScrollManager utility for SaladUI components + * Manages scroll and resize event handlers with optimized performance + */ +class ScrollManager { + /** + * Create a scroll manager to handle scroll and resize events + * + * @param {Function} updateCallback - Function to call when scroll/resize events occur + * @param {Object} options - Additional options + */ + constructor(updateCallback, options = {}) { + this.updateCallback = updateCallback; + this.options = { + // Use requestAnimationFrame for throttling + useRAF: true, + ...options, + }; + + this.scrollableParents = []; + this.active = false; + this.resizeObserver = null; + this.animationFrameId = null; + + // Bind methods to maintain correct context + this.handleScroll = this.handleScroll.bind(this); + this.handleResize = this.handleResize.bind(this); + this.updatePosition = this.updatePosition.bind(this); + } + + /** + * Start tracking scroll and resize events + * + * @param {HTMLElement} referenceElement - Element to track scrollable parents for + * @param {HTMLElement} targetElement - Optional element to observe with ResizeObserver + */ + start(referenceElement, targetElement = null) { + if (this.active) return; + + // Find scrollable parent elements + if (referenceElement) { + this.scrollableParents = this.findScrollableParents(referenceElement); + + // Add scroll listeners to all scrollable parents + this.scrollableParents.forEach((parent) => { + parent.addEventListener("scroll", this.handleScroll, { passive: true }); + }); + } + + // Add resize listener + window.addEventListener("resize", this.handleResize, { passive: true }); + + // Set up ResizeObserver for element size changes if available + if (targetElement && typeof ResizeObserver !== "undefined") { + this.resizeObserver = new ResizeObserver(this.updatePosition); + this.resizeObserver.observe(targetElement); + + if (referenceElement && referenceElement !== targetElement) { + this.resizeObserver.observe(referenceElement); + } + } + + this.active = true; + } + + /** + * Stop tracking scroll and resize events + */ + stop() { + if (!this.active) return; + + // Remove scroll listeners + this.scrollableParents.forEach((parent) => { + parent.removeEventListener("scroll", this.handleScroll); + }); + + // Remove resize listener + window.removeEventListener("resize", this.handleResize); + + // Disconnect ResizeObserver if present + if (this.resizeObserver) { + this.resizeObserver.disconnect(); + this.resizeObserver = null; + } + + // Cancel any pending animation frame + if (this.animationFrameId !== null) { + cancelAnimationFrame(this.animationFrameId); + this.animationFrameId = null; + } + + this.active = false; + this.scrollableParents = []; + } + + /** + * Handle scroll events with throttling + */ + handleScroll() { + if (this.options.useRAF) { + this.throttledUpdate(); + } else { + this.updatePosition(); + } + } + + /** + * Handle resize events with throttling + */ + handleResize() { + if (this.options.useRAF) { + this.throttledUpdate(); + } else { + this.updatePosition(); + } + } + + /** + * Throttle updates using requestAnimationFrame + */ + throttledUpdate() { + if (this.animationFrameId === null) { + this.animationFrameId = requestAnimationFrame(() => { + this.updatePosition(); + this.animationFrameId = null; + }); + } + } + + /** + * Call the update callback + */ + updatePosition() { + if (this.updateCallback) { + this.updateCallback(); + } + } + + /** + * Find all scrollable parent elements + */ + findScrollableParents(element) { + const scrollableParents = []; + let currentElement = element; + + while (currentElement && currentElement !== document.body) { + const style = window.getComputedStyle(currentElement); + if ( + style.overflow === "auto" || + style.overflow === "scroll" || + style.overflowX === "auto" || + style.overflowX === "scroll" || + style.overflowY === "auto" || + style.overflowY === "scroll" + ) { + scrollableParents.push(currentElement); + } + currentElement = currentElement.parentElement; + } + + // Always include window for global scrolling + scrollableParents.push(window); + + return scrollableParents; + } + + /** + * Clean up all references + */ + destroy() { + this.stop(); + this.updateCallback = null; + this.options = null; + } +} + +export default ScrollManager; diff --git a/assets/js/ui/core/state-machine.js b/assets/js/ui/core/state-machine.js new file mode 100644 index 0000000..05eeb90 --- /dev/null +++ b/assets/js/ui/core/state-machine.js @@ -0,0 +1,132 @@ +// saladui/core/state-machine.js +/** + * StateMachine class for SaladUI framework + * Handles state transitions, event processing, and state-specific behavior + */ +class StateMachine { + /** + * Create a state machine + * + * @param {Object} stateConfig - Configuration object defining states and transitions + * @param {string} initialState - The initial state to start in + * @param {Object} options - Optional configuration options. Currently supports: + * - onStateChanged: A callback function to be called when the state changes + */ + constructor(stateConfig, initialState, options) { + this.stateConfig = stateConfig; + this.state = initialState || "idle"; + this.previousState = null; + this.options = options || {}; + } + + /** + * Trigger a transition based on an event + * + * @param {string} event - The event triggering the transition + * @param {Object} params - Parameters to pass to the handlers + * @returns {boolean} Whether the transition was successful + */ + transition(event, params = {}) { + const currentStateConfig = this.stateConfig[this.state]; + if (!currentStateConfig) return false; + + const transition = currentStateConfig.transitions?.[event]; + if (!transition) return false; + + const nextState = this.determineNextState(transition, params); + if (!nextState) return false; + + const prevState = this.state; + + this.executeTransition(prevState, nextState, params); + + return true; + } + + /** + * Determine the next state based on the transition definition + * + * @param {string|Function|Object} transition - Transition definition + * @param {Object} params - Parameters to help determine the next state + * @returns {string|null} The next state or null if not determinable + */ + determineNextState(transition, params) { + if (typeof transition === "string") { + return transition; + } else if (typeof transition === "function") { + return transition(params); + } + return null; + } + + /** + * Execute a transition between states, with optional animation + * + * @param {string} prevState - The state we're coming from + * @param {string} nextState - The state we're going to + * @param {Object} params - Parameters to pass to handlers + */ + executeTransition(prevState, nextState, params = {}) { + // Execute exit handlers + this.executeStateHandler(prevState, "exit", params); + + // Update state + this.previousState = prevState; + this.state = nextState; + + let callbackResult; + // Execute state change hook + if (typeof this.options.onStateChanged === "function") { + callbackResult = this.options.onStateChanged( + prevState, + nextState, + params, + ); + } + + if (callbackResult && typeof callbackResult.then === "function") { + // If it returns a promise, wait for completion before executing enter handler + callbackResult + .then(() => { + this.executeStateHandler(nextState, "enter", params); + }) + .catch((error) => { + console.error("Animation promise rejected:", error); + // Still execute enter handler even if animation fails + this.executeStateHandler(nextState, "enter", params); + }); + } else { + // If it doesn't return a promise, execute enter handler immediately + this.executeStateHandler(nextState, "enter", params); + } + } + + /** + * Execute a state handler (enter or exit) + * + * @param {string} stateName - The state whose handler to execute + * @param {string} handlerType - 'enter' or 'exit' + * @param {Object} params - Parameters to pass to the handler + */ + executeStateHandler(stateName, handlerType, params) { + const stateConfig = this.stateConfig[stateName]; + if (!stateConfig) return; + + const handler = stateConfig[handlerType]; + + if (typeof handler === "function") { + handler(params); + } + } + + /** + * Check if the state has changed since last transition + * + * @returns {boolean} Whether the state has changed + */ + hasStateChanged() { + return this.state !== this.previousState; + } +} + +export default StateMachine; diff --git a/assets/js/ui/core/utils.js b/assets/js/ui/core/utils.js new file mode 100644 index 0000000..847e643 --- /dev/null +++ b/assets/js/ui/core/utils.js @@ -0,0 +1,164 @@ +// saladui/core/animation-utils.js +/** + * Animation utilities for SaladUI framework components + * Provides functions to handle animations and transitions + */ + +/** + * Animation handler for state transitions + * + * @param {Object} animConfig - Animation configuration object + * @param {HTMLElement} targetElement - Element to animate + * @returns {Promise} A Promise that resolves when animation completes + */ +export function animateTransition(animConfig, targetElement) { + if (!animConfig || !targetElement) { + return Promise.resolve(); // Return resolved promise if no animation or target + } + + const { animation, duration = 200 } = animConfig; + + // Process animation classes + const animationClasses = (animation || ["", "", ""]).map((item) => + typeof item === "string" ? item.split(/\s+/) : [], + ); + + // Execute animation with promise + return executeAnimation(targetElement, { + animation: animationClasses, + duration, + }); +} + +/** + * Execute animation sequence on target element + * + * @param {HTMLElement} targetElement - Element to animate + * @param {Object} animOptions - Animation options + * @returns {Promise} Promise that resolves when animation completes + */ +export function executeAnimation(targetElement, animOptions) { + console.log("Animating", targetElement, animOptions); + return new Promise((resolve) => { + const { animation, duration } = animOptions; + let [transitionRun, transitionStart, transitionEnd] = animation || [ + [], + [], + [], + ]; + + // First animation frame: apply start classes + addOrRemoveClasses( + targetElement, + transitionStart, + [].concat(transitionRun).concat(transitionEnd), + ); + + // Next frame: apply running classes + window.requestAnimationFrame(() => { + addOrRemoveClasses(targetElement, transitionRun, []); + + // Next frame: apply end classes + window.requestAnimationFrame(() => + addOrRemoveClasses(targetElement, transitionEnd, transitionStart), + ); + }); + + // After duration, clean up classes and resolve promise + setTimeout(() => { + addOrRemoveClasses( + targetElement, + [], + [].concat(transitionRun).concat(transitionStart).concat(transitionEnd), + ); + + resolve(); + }, duration); + }); +} + +/** + * Add and remove CSS classes from a target element + * + * @param {HTMLElement} targetElement - Element to modify classes on + * @param {Array} addClasses - Classes to add + * @param {Array} removeClasses - Classes to remove + */ +export function addOrRemoveClasses( + targetElement, + addClasses = [], + removeClasses = [], +) { + if (!targetElement) return; + + if (addClasses.length > 0) { + targetElement.classList.add(...addClasses.filter(Boolean)); + } + if (removeClasses.length > 0) { + targetElement.classList.remove(...removeClasses.filter(Boolean)); + } +} + +/** + * Filter result constants + * @enum {number} + */ +const FilterResult = { + IGNORE_AND_CONTINUE: 0, // Ignore node but traverse children + SELECT_AND_CONTINUE: 1, // Select node and traverse children + IGNORE_AND_SKIP: -1, // Ignore node and skip children +}; + +/** + * Custom DOM query selector with advanced filtering capabilities + * + * @param {Node} root - Starting DOM node + * @param {Function} filterFunction - Function returning a FilterResult value + * @param {Object} [options] - Optional settings + * @param {boolean} [options.breadthFirst=true] - Use breadth-first (true) or depth-first (false) + * @returns {Element[]} Matching elements + */ +export function queryDOM( + root, + filterFunction, + options = { breadthFirst: true }, +) { + // Validate inputs + if (!(root instanceof Node)) throw new TypeError("Root must be a DOM node"); + if (typeof filterFunction !== "function") + throw new TypeError("Filter must be a function"); + + const result = []; + const nodes = [...root.children]; + const getNext = + options.breadthFirst !== false ? () => nodes.shift() : () => nodes.pop(); + + while (nodes.length > 0) { + const current = getNext(); + + // Skip non-element nodes + if (!(current instanceof Element)) continue; + + // Process based on filter result + const filterResult = filterFunction(current); + + if (filterResult === FilterResult.SELECT_AND_CONTINUE) { + result.push(current); + addChildren(current, nodes); + } else if (filterResult === FilterResult.IGNORE_AND_CONTINUE) { + addChildren(current, nodes); + } + // IGNORE_AND_SKIP: do nothing + } + + return result; +} + +/** + * Add element's children to the traversal collection + */ +function addChildren(element, collection) { + for (let i = 0; i < element.children.length; i++) { + collection.push(element.children[i]); + } +} diff --git a/assets/js/ui/index.js b/assets/js/ui/index.js new file mode 100644 index 0000000..b57ffd8 --- /dev/null +++ b/assets/js/ui/index.js @@ -0,0 +1,16 @@ +// saladui/index.js +import Component from "./core/component"; +import { registry } from "./core/factory"; +import { SaladUIHook } from "./core/hook"; + +function register(type, ComponentClass) { + registry.register(type, ComponentClass); +} + +const SaladUI = { + Component, + register, + SaladUIHook, +}; + +export default SaladUI; diff --git a/assets/js/user_socket.js b/assets/js/user_socket.js new file mode 100644 index 0000000..8849e83 --- /dev/null +++ b/assets/js/user_socket.js @@ -0,0 +1,46 @@ +// NOTE: The contents of this file will only be executed if +// you uncomment its entry in "assets/js/app.js". + +// Bring in the socket library +import {Socket} from "phoenix" + +// And connect to the path in "lib/river_connect_web/endpoint.ex". We pass the +// token for authentication. Read below how it should be used. +let params = {user_id: window.userId || "guest-" + Math.floor(Math.random() * 1000)} +let socket = new Socket("/socket", {params: params}) + +// When you connect, you'll often need to authenticate the client. +// For example, imagine you have an authentication plug, `MyAuth`, +// which authenticates the session and assigns a `:current_user`. +// If the current user exists you can set a own_id in the window +// object in root.html.heex: +// +// +// +// Now you need to pass this token to JavaScript. You can do so +// inside the socket implementation in "lib/river_connect_web/channels/user_socket.ex": +// +// def connect(%{"token" => token}, socket, _connect_info) do +// # max_age: 1209600 is equivalent to two weeks in seconds +// case Phoenix.Token.verify(socket, "user socket", token, max_age: 1_209_600) do +// {:ok, user_id} -> +// {:ok, assign(socket, :user, user_id)} +// {:error, reason} -> +// :error +// end +// end +// +// Finally, connect to the socket: +socket.connect() + +// Now that you are connected, you can join channels with a topic. +// Let's assume you have a channel with a topic named `room` and the +// subtopic is its id - in this case 42: +// +// let channel = socket.channel("room:42", {}) +// channel.join() +// .receive("ok", resp => { console.log("Joined successfully", resp) }) +// .receive("error", resp => { console.log("Unable to join", resp) }) +// +// export default socket to import it in other files +export default socket diff --git a/assets/tailwind.colors.json b/assets/tailwind.colors.json new file mode 100644 index 0000000..2020bf9 --- /dev/null +++ b/assets/tailwind.colors.json @@ -0,0 +1,45 @@ +{ + "accent": { + "DEFAULT": "hsl(var(--accent))", + "foreground": "hsl(var(--accent-foreground))" + }, + "background": "hsl(var(--background))", + "border": "hsl(var(--border))", + "card": { + "DEFAULT": "hsl(var(--card))", + "foreground": "hsl(var(--card-foreground))" + }, + "destructive": { + "DEFAULT": "hsl(var(--destructive))", + "foreground": "hsl(var(--destructive-foreground))" + }, + "foreground": "hsl(var(--foreground))", + "input": "hsl(var(--input))", + "muted": { + "DEFAULT": "hsl(var(--muted))", + "foreground": "hsl(var(--muted-foreground))" + }, + "popover": { + "DEFAULT": "hsl(var(--popover))", + "foreground": "hsl(var(--popover-foreground))" + }, + "primary": { + "DEFAULT": "hsl(var(--primary))", + "foreground": "hsl(var(--primary-foreground))" + }, + "ring": "hsl(var(--ring))", + "secondary": { + "DEFAULT": "hsl(var(--secondary))", + "foreground": "hsl(var(--secondary-foreground))" + }, + "sidebar": { + "DEFAULT": "hsl(var(--sidebar-background))", + "foreground": "hsl(var(--sidebar-foreground))", + "primary": "hsl(var(--sidebar-primary))", + "primary-foreground": "hsl(var(--sidebar-primary-foreground))", + "accent": "hsl(var(--sidebar-accent))", + "accent-foreground": "hsl(var(--sidebar-accent-foreground))", + "border": "hsl(var(--sidebar-border))", + "ring": "hsl(var(--sidebar-ring))" + } +} diff --git a/assets/tailwind.config.js b/assets/tailwind.config.js new file mode 100644 index 0000000..afe1c41 --- /dev/null +++ b/assets/tailwind.config.js @@ -0,0 +1,19 @@ +import Config from 'tailwindcss' + +/** @type {import('tailwindcss').Config} */ +module.exports = { + darkMode: ["class"], + content: [ +"../deps/salad_ui/lib/**/*.ex", + './js/**/*.js', + '../lib/river_connect_web.ex', + '../lib/river_connect_web/**/*.*ex', + ], + theme: { + extend: { + colors: require("./tailwind.colors.json"),}, + }, + plugins: [ + require("@tailwindcss/typography"), + require("./vendor/tailwindcss-animate"),], +} diff --git a/assets/tsconfig.json b/assets/tsconfig.json new file mode 100644 index 0000000..a9401b6 --- /dev/null +++ b/assets/tsconfig.json @@ -0,0 +1,32 @@ +// This file is needed on most editors to enable the intelligent autocompletion +// of LiveView's JavaScript API methods. You can safely delete it if you don't need it. +// +// Note: This file assumes a basic esbuild setup without node_modules. +// We include a generic paths alias to deps to mimic how esbuild resolves +// the Phoenix and LiveView JavaScript assets. +// If you have a package.json in your project, you should remove the +// paths configuration and instead add the phoenix dependencies to the +// dependencies section of your package.json: +// +// { +// ... +// "dependencies": { +// ..., +// "phoenix": "../deps/phoenix", +// "phoenix_html": "../deps/phoenix_html", +// "phoenix_live_view": "../deps/phoenix_live_view" +// } +// } +// +// Feel free to adjust this configuration however you need. +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "*": ["../deps/*"] + }, + "allowJs": true, + "noEmit": true + }, + "include": ["js/**/*"] +} diff --git a/assets/vendor/daisyui-theme.js b/assets/vendor/daisyui-theme.js new file mode 100644 index 0000000..169c806 --- /dev/null +++ b/assets/vendor/daisyui-theme.js @@ -0,0 +1,124 @@ +/** 🌼 + * @license MIT + * daisyUI bundle + * https://daisyui.com/ + */ + +var __defProp = Object.defineProperty; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __moduleCache = /* @__PURE__ */ new WeakMap; +var __toCommonJS = (from) => { + var entry = __moduleCache.get(from), desc; + if (entry) + return entry; + entry = __defProp({}, "__esModule", { value: true }); + if (from && typeof from === "object" || typeof from === "function") + __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, { + get: () => from[key], + enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable + })); + __moduleCache.set(from, entry); + return entry; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { + get: all[name], + enumerable: true, + configurable: true, + set: (newValue) => all[name] = () => newValue + }); +}; + +// packages/daisyui/theme/index.js +var exports_theme = {}; +__export(exports_theme, { + default: () => theme_default +}); +module.exports = __toCommonJS(exports_theme); + +// packages/daisyui/functions/plugin.js +var plugin = { + withOptions: (pluginFunction, configFunction = () => ({})) => { + const optionsFunction = (options) => { + const handler = pluginFunction(options); + const config = configFunction(options); + return { handler, config }; + }; + optionsFunction.__isOptionsFunction = true; + return optionsFunction; + } +}; + +// packages/daisyui/theme/object.js +var object_default = { cyberpunk: { "color-scheme": "light", "--color-base-100": "oklch(94.51% 0.179 104.32)", "--color-base-200": "oklch(91.51% 0.179 104.32)", "--color-base-300": "oklch(85.51% 0.179 104.32)", "--color-base-content": "oklch(0% 0 0)", "--color-primary": "oklch(74.22% 0.209 6.35)", "--color-primary-content": "oklch(14.844% 0.041 6.35)", "--color-secondary": "oklch(83.33% 0.184 204.72)", "--color-secondary-content": "oklch(16.666% 0.036 204.72)", "--color-accent": "oklch(71.86% 0.217 310.43)", "--color-accent-content": "oklch(14.372% 0.043 310.43)", "--color-neutral": "oklch(23.04% 0.065 269.31)", "--color-neutral-content": "oklch(94.51% 0.179 104.32)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "0rem", "--radius-field": "0rem", "--radius-box": "0rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, acid: { "color-scheme": "light", "--color-base-100": "oklch(98% 0 0)", "--color-base-200": "oklch(95% 0 0)", "--color-base-300": "oklch(91% 0 0)", "--color-base-content": "oklch(0% 0 0)", "--color-primary": "oklch(71.9% 0.357 330.759)", "--color-primary-content": "oklch(14.38% 0.071 330.759)", "--color-secondary": "oklch(73.37% 0.224 48.25)", "--color-secondary-content": "oklch(14.674% 0.044 48.25)", "--color-accent": "oklch(92.78% 0.264 122.962)", "--color-accent-content": "oklch(18.556% 0.052 122.962)", "--color-neutral": "oklch(21.31% 0.128 278.68)", "--color-neutral-content": "oklch(84.262% 0.025 278.68)", "--color-info": "oklch(60.72% 0.227 252.05)", "--color-info-content": "oklch(12.144% 0.045 252.05)", "--color-success": "oklch(85.72% 0.266 158.53)", "--color-success-content": "oklch(17.144% 0.053 158.53)", "--color-warning": "oklch(91.01% 0.212 100.5)", "--color-warning-content": "oklch(18.202% 0.042 100.5)", "--color-error": "oklch(64.84% 0.293 29.349)", "--color-error-content": "oklch(12.968% 0.058 29.349)", "--radius-selector": "1rem", "--radius-field": "1rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, black: { "color-scheme": "dark", "--color-base-100": "oklch(0% 0 0)", "--color-base-200": "oklch(19% 0 0)", "--color-base-300": "oklch(22% 0 0)", "--color-base-content": "oklch(87.609% 0 0)", "--color-primary": "oklch(35% 0 0)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(35% 0 0)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(35% 0 0)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(35% 0 0)", "--color-neutral-content": "oklch(100% 0 0)", "--color-info": "oklch(45.201% 0.313 264.052)", "--color-info-content": "oklch(89.04% 0.062 264.052)", "--color-success": "oklch(51.975% 0.176 142.495)", "--color-success-content": "oklch(90.395% 0.035 142.495)", "--color-warning": "oklch(96.798% 0.211 109.769)", "--color-warning-content": "oklch(19.359% 0.042 109.769)", "--color-error": "oklch(62.795% 0.257 29.233)", "--color-error-content": "oklch(12.559% 0.051 29.233)", "--radius-selector": "0rem", "--radius-field": "0rem", "--radius-box": "0rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, dark: { "color-scheme": "dark", "--color-base-100": "oklch(25.33% 0.016 252.42)", "--color-base-200": "oklch(23.26% 0.014 253.1)", "--color-base-300": "oklch(21.15% 0.012 254.09)", "--color-base-content": "oklch(97.807% 0.029 256.847)", "--color-primary": "oklch(58% 0.233 277.117)", "--color-primary-content": "oklch(96% 0.018 272.314)", "--color-secondary": "oklch(65% 0.241 354.308)", "--color-secondary-content": "oklch(94% 0.028 342.258)", "--color-accent": "oklch(77% 0.152 181.912)", "--color-accent-content": "oklch(38% 0.063 188.416)", "--color-neutral": "oklch(14% 0.005 285.823)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(71% 0.194 13.428)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "0.5rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, light: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(98% 0 0)", "--color-base-300": "oklch(95% 0 0)", "--color-base-content": "oklch(21% 0.006 285.885)", "--color-primary": "oklch(45% 0.24 277.023)", "--color-primary-content": "oklch(93% 0.034 272.788)", "--color-secondary": "oklch(65% 0.241 354.308)", "--color-secondary-content": "oklch(94% 0.028 342.258)", "--color-accent": "oklch(77% 0.152 181.912)", "--color-accent-content": "oklch(38% 0.063 188.416)", "--color-neutral": "oklch(14% 0.005 285.823)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(71% 0.194 13.428)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "0.5rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, luxury: { "color-scheme": "dark", "--color-base-100": "oklch(14.076% 0.004 285.822)", "--color-base-200": "oklch(20.219% 0.004 308.229)", "--color-base-300": "oklch(23.219% 0.004 308.229)", "--color-base-content": "oklch(75.687% 0.123 76.89)", "--color-primary": "oklch(100% 0 0)", "--color-primary-content": "oklch(20% 0 0)", "--color-secondary": "oklch(27.581% 0.064 261.069)", "--color-secondary-content": "oklch(85.516% 0.012 261.069)", "--color-accent": "oklch(36.674% 0.051 338.825)", "--color-accent-content": "oklch(87.334% 0.01 338.825)", "--color-neutral": "oklch(24.27% 0.057 59.825)", "--color-neutral-content": "oklch(93.203% 0.089 90.861)", "--color-info": "oklch(79.061% 0.121 237.133)", "--color-info-content": "oklch(15.812% 0.024 237.133)", "--color-success": "oklch(78.119% 0.192 132.154)", "--color-success-content": "oklch(15.623% 0.038 132.154)", "--color-warning": "oklch(86.127% 0.136 102.891)", "--color-warning-content": "oklch(17.225% 0.027 102.891)", "--color-error": "oklch(71.753% 0.176 22.568)", "--color-error-content": "oklch(14.35% 0.035 22.568)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, dracula: { "color-scheme": "dark", "--color-base-100": "oklch(28.822% 0.022 277.508)", "--color-base-200": "oklch(26.805% 0.02 277.508)", "--color-base-300": "oklch(24.787% 0.019 277.508)", "--color-base-content": "oklch(97.747% 0.007 106.545)", "--color-primary": "oklch(75.461% 0.183 346.812)", "--color-primary-content": "oklch(15.092% 0.036 346.812)", "--color-secondary": "oklch(74.202% 0.148 301.883)", "--color-secondary-content": "oklch(14.84% 0.029 301.883)", "--color-accent": "oklch(83.392% 0.124 66.558)", "--color-accent-content": "oklch(16.678% 0.024 66.558)", "--color-neutral": "oklch(39.445% 0.032 275.524)", "--color-neutral-content": "oklch(87.889% 0.006 275.524)", "--color-info": "oklch(88.263% 0.093 212.846)", "--color-info-content": "oklch(17.652% 0.018 212.846)", "--color-success": "oklch(87.099% 0.219 148.024)", "--color-success-content": "oklch(17.419% 0.043 148.024)", "--color-warning": "oklch(95.533% 0.134 112.757)", "--color-warning-content": "oklch(19.106% 0.026 112.757)", "--color-error": "oklch(68.22% 0.206 24.43)", "--color-error-content": "oklch(13.644% 0.041 24.43)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, retro: { "color-scheme": "light", "--color-base-100": "oklch(91.637% 0.034 90.515)", "--color-base-200": "oklch(88.272% 0.049 91.774)", "--color-base-300": "oklch(84.133% 0.065 90.856)", "--color-base-content": "oklch(41% 0.112 45.904)", "--color-primary": "oklch(80% 0.114 19.571)", "--color-primary-content": "oklch(39% 0.141 25.723)", "--color-secondary": "oklch(92% 0.084 155.995)", "--color-secondary-content": "oklch(44% 0.119 151.328)", "--color-accent": "oklch(68% 0.162 75.834)", "--color-accent-content": "oklch(41% 0.112 45.904)", "--color-neutral": "oklch(44% 0.011 73.639)", "--color-neutral-content": "oklch(86% 0.005 56.366)", "--color-info": "oklch(58% 0.158 241.966)", "--color-info-content": "oklch(96% 0.059 95.617)", "--color-success": "oklch(51% 0.096 186.391)", "--color-success-content": "oklch(96% 0.059 95.617)", "--color-warning": "oklch(64% 0.222 41.116)", "--color-warning-content": "oklch(96% 0.059 95.617)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(40% 0.123 38.172)", "--radius-selector": "0.25rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, lofi: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(97% 0 0)", "--color-base-300": "oklch(94% 0 0)", "--color-base-content": "oklch(0% 0 0)", "--color-primary": "oklch(15.906% 0 0)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(21.455% 0.001 17.278)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(26.861% 0 0)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(0% 0 0)", "--color-neutral-content": "oklch(100% 0 0)", "--color-info": "oklch(79.54% 0.103 205.9)", "--color-info-content": "oklch(15.908% 0.02 205.9)", "--color-success": "oklch(90.13% 0.153 164.14)", "--color-success-content": "oklch(18.026% 0.03 164.14)", "--color-warning": "oklch(88.37% 0.135 79.94)", "--color-warning-content": "oklch(17.674% 0.027 79.94)", "--color-error": "oklch(78.66% 0.15 28.47)", "--color-error-content": "oklch(15.732% 0.03 28.47)", "--radius-selector": "2rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, valentine: { "color-scheme": "light", "--color-base-100": "oklch(97% 0.014 343.198)", "--color-base-200": "oklch(94% 0.028 342.258)", "--color-base-300": "oklch(89% 0.061 343.231)", "--color-base-content": "oklch(52% 0.223 3.958)", "--color-primary": "oklch(65% 0.241 354.308)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(62% 0.265 303.9)", "--color-secondary-content": "oklch(97% 0.014 308.299)", "--color-accent": "oklch(82% 0.111 230.318)", "--color-accent-content": "oklch(39% 0.09 240.876)", "--color-neutral": "oklch(40% 0.153 2.432)", "--color-neutral-content": "oklch(89% 0.061 343.231)", "--color-info": "oklch(86% 0.127 207.078)", "--color-info-content": "oklch(44% 0.11 240.79)", "--color-success": "oklch(84% 0.143 164.978)", "--color-success-content": "oklch(43% 0.095 166.913)", "--color-warning": "oklch(75% 0.183 55.934)", "--color-warning-content": "oklch(26% 0.079 36.259)", "--color-error": "oklch(63% 0.237 25.331)", "--color-error-content": "oklch(97% 0.013 17.38)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, nord: { "color-scheme": "light", "--color-base-100": "oklch(95.127% 0.007 260.731)", "--color-base-200": "oklch(93.299% 0.01 261.788)", "--color-base-300": "oklch(89.925% 0.016 262.749)", "--color-base-content": "oklch(32.437% 0.022 264.182)", "--color-primary": "oklch(59.435% 0.077 254.027)", "--color-primary-content": "oklch(11.887% 0.015 254.027)", "--color-secondary": "oklch(69.651% 0.059 248.687)", "--color-secondary-content": "oklch(13.93% 0.011 248.687)", "--color-accent": "oklch(77.464% 0.062 217.469)", "--color-accent-content": "oklch(15.492% 0.012 217.469)", "--color-neutral": "oklch(45.229% 0.035 264.131)", "--color-neutral-content": "oklch(89.925% 0.016 262.749)", "--color-info": "oklch(69.207% 0.062 332.664)", "--color-info-content": "oklch(13.841% 0.012 332.664)", "--color-success": "oklch(76.827% 0.074 131.063)", "--color-success-content": "oklch(15.365% 0.014 131.063)", "--color-warning": "oklch(85.486% 0.089 84.093)", "--color-warning-content": "oklch(17.097% 0.017 84.093)", "--color-error": "oklch(60.61% 0.12 15.341)", "--color-error-content": "oklch(12.122% 0.024 15.341)", "--radius-selector": "1rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, lemonade: { "color-scheme": "light", "--color-base-100": "oklch(98.71% 0.02 123.72)", "--color-base-200": "oklch(91.8% 0.018 123.72)", "--color-base-300": "oklch(84.89% 0.017 123.72)", "--color-base-content": "oklch(19.742% 0.004 123.72)", "--color-primary": "oklch(58.92% 0.199 134.6)", "--color-primary-content": "oklch(11.784% 0.039 134.6)", "--color-secondary": "oklch(77.75% 0.196 111.09)", "--color-secondary-content": "oklch(15.55% 0.039 111.09)", "--color-accent": "oklch(85.39% 0.201 100.73)", "--color-accent-content": "oklch(17.078% 0.04 100.73)", "--color-neutral": "oklch(30.98% 0.075 108.6)", "--color-neutral-content": "oklch(86.196% 0.015 108.6)", "--color-info": "oklch(86.19% 0.047 224.14)", "--color-info-content": "oklch(17.238% 0.009 224.14)", "--color-success": "oklch(86.19% 0.047 157.85)", "--color-success-content": "oklch(17.238% 0.009 157.85)", "--color-warning": "oklch(86.19% 0.047 102.15)", "--color-warning-content": "oklch(17.238% 0.009 102.15)", "--color-error": "oklch(86.19% 0.047 25.85)", "--color-error-content": "oklch(17.238% 0.009 25.85)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, garden: { "color-scheme": "light", "--color-base-100": "oklch(92.951% 0.002 17.197)", "--color-base-200": "oklch(86.445% 0.002 17.197)", "--color-base-300": "oklch(79.938% 0.001 17.197)", "--color-base-content": "oklch(16.961% 0.001 17.32)", "--color-primary": "oklch(62.45% 0.278 3.836)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(48.495% 0.11 355.095)", "--color-secondary-content": "oklch(89.699% 0.022 355.095)", "--color-accent": "oklch(56.273% 0.054 154.39)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(24.155% 0.049 89.07)", "--color-neutral-content": "oklch(92.951% 0.002 17.197)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, aqua: { "color-scheme": "dark", "--color-base-100": "oklch(37% 0.146 265.522)", "--color-base-200": "oklch(28% 0.091 267.935)", "--color-base-300": "oklch(22% 0.091 267.935)", "--color-base-content": "oklch(90% 0.058 230.902)", "--color-primary": "oklch(85.661% 0.144 198.645)", "--color-primary-content": "oklch(40.124% 0.068 197.603)", "--color-secondary": "oklch(60.682% 0.108 309.782)", "--color-secondary-content": "oklch(96% 0.016 293.756)", "--color-accent": "oklch(93.426% 0.102 94.555)", "--color-accent-content": "oklch(18.685% 0.02 94.555)", "--color-neutral": "oklch(27% 0.146 265.522)", "--color-neutral-content": "oklch(80% 0.146 265.522)", "--color-info": "oklch(54.615% 0.215 262.88)", "--color-info-content": "oklch(90.923% 0.043 262.88)", "--color-success": "oklch(62.705% 0.169 149.213)", "--color-success-content": "oklch(12.541% 0.033 149.213)", "--color-warning": "oklch(66.584% 0.157 58.318)", "--color-warning-content": "oklch(27% 0.077 45.635)", "--color-error": "oklch(73.95% 0.19 27.33)", "--color-error-content": "oklch(14.79% 0.038 27.33)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, corporate: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(93% 0 0)", "--color-base-300": "oklch(86% 0 0)", "--color-base-content": "oklch(22.389% 0.031 278.072)", "--color-primary": "oklch(58% 0.158 241.966)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(55% 0.046 257.417)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(60% 0.118 184.704)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(0% 0 0)", "--color-neutral-content": "oklch(100% 0 0)", "--color-info": "oklch(60% 0.126 221.723)", "--color-info-content": "oklch(100% 0 0)", "--color-success": "oklch(62% 0.194 149.214)", "--color-success-content": "oklch(100% 0 0)", "--color-warning": "oklch(85% 0.199 91.936)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "0.25rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, pastel: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(98.462% 0.001 247.838)", "--color-base-300": "oklch(92.462% 0.001 247.838)", "--color-base-content": "oklch(20% 0 0)", "--color-primary": "oklch(90% 0.063 306.703)", "--color-primary-content": "oklch(49% 0.265 301.924)", "--color-secondary": "oklch(89% 0.058 10.001)", "--color-secondary-content": "oklch(51% 0.222 16.935)", "--color-accent": "oklch(90% 0.093 164.15)", "--color-accent-content": "oklch(50% 0.118 165.612)", "--color-neutral": "oklch(55% 0.046 257.417)", "--color-neutral-content": "oklch(92% 0.013 255.508)", "--color-info": "oklch(86% 0.127 207.078)", "--color-info-content": "oklch(52% 0.105 223.128)", "--color-success": "oklch(87% 0.15 154.449)", "--color-success-content": "oklch(52% 0.154 150.069)", "--color-warning": "oklch(83% 0.128 66.29)", "--color-warning-content": "oklch(55% 0.195 38.402)", "--color-error": "oklch(80% 0.114 19.571)", "--color-error-content": "oklch(50% 0.213 27.518)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "0", "--noise": "0" }, bumblebee: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(97% 0 0)", "--color-base-300": "oklch(92% 0 0)", "--color-base-content": "oklch(20% 0 0)", "--color-primary": "oklch(85% 0.199 91.936)", "--color-primary-content": "oklch(42% 0.095 57.708)", "--color-secondary": "oklch(75% 0.183 55.934)", "--color-secondary-content": "oklch(40% 0.123 38.172)", "--color-accent": "oklch(0% 0 0)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(37% 0.01 67.558)", "--color-neutral-content": "oklch(92% 0.003 48.717)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(39% 0.09 240.876)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(39% 0.141 25.723)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, coffee: { "color-scheme": "dark", "--color-base-100": "oklch(24% 0.023 329.708)", "--color-base-200": "oklch(21% 0.021 329.708)", "--color-base-300": "oklch(16% 0.019 329.708)", "--color-base-content": "oklch(72.354% 0.092 79.129)", "--color-primary": "oklch(71.996% 0.123 62.756)", "--color-primary-content": "oklch(14.399% 0.024 62.756)", "--color-secondary": "oklch(34.465% 0.029 199.194)", "--color-secondary-content": "oklch(86.893% 0.005 199.194)", "--color-accent": "oklch(42.621% 0.074 224.389)", "--color-accent-content": "oklch(88.524% 0.014 224.389)", "--color-neutral": "oklch(16.51% 0.015 326.261)", "--color-neutral-content": "oklch(83.302% 0.003 326.261)", "--color-info": "oklch(79.49% 0.063 184.558)", "--color-info-content": "oklch(15.898% 0.012 184.558)", "--color-success": "oklch(74.722% 0.072 131.116)", "--color-success-content": "oklch(14.944% 0.014 131.116)", "--color-warning": "oklch(88.15% 0.14 87.722)", "--color-warning-content": "oklch(17.63% 0.028 87.722)", "--color-error": "oklch(77.318% 0.128 31.871)", "--color-error-content": "oklch(15.463% 0.025 31.871)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, silk: { "color-scheme": "light", "--color-base-100": "oklch(97% 0.0035 67.78)", "--color-base-200": "oklch(95% 0.0081 61.42)", "--color-base-300": "oklch(90% 0.0081 61.42)", "--color-base-content": "oklch(40% 0.0081 61.42)", "--color-primary": "oklch(23.27% 0.0249 284.3)", "--color-primary-content": "oklch(94.22% 0.2505 117.44)", "--color-secondary": "oklch(23.27% 0.0249 284.3)", "--color-secondary-content": "oklch(73.92% 0.2135 50.94)", "--color-accent": "oklch(23.27% 0.0249 284.3)", "--color-accent-content": "oklch(88.92% 0.2061 189.9)", "--color-neutral": "oklch(20% 0 0)", "--color-neutral-content": "oklch(80% 0.0081 61.42)", "--color-info": "oklch(80.39% 0.1148 241.68)", "--color-info-content": "oklch(30.39% 0.1148 241.68)", "--color-success": "oklch(83.92% 0.0901 136.87)", "--color-success-content": "oklch(23.92% 0.0901 136.87)", "--color-warning": "oklch(83.92% 0.1085 80)", "--color-warning-content": "oklch(43.92% 0.1085 80)", "--color-error": "oklch(75.1% 0.1814 22.37)", "--color-error-content": "oklch(35.1% 0.1814 22.37)", "--radius-selector": "2rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "0" }, sunset: { "color-scheme": "dark", "--color-base-100": "oklch(22% 0.019 237.69)", "--color-base-200": "oklch(20% 0.019 237.69)", "--color-base-300": "oklch(18% 0.019 237.69)", "--color-base-content": "oklch(77.383% 0.043 245.096)", "--color-primary": "oklch(74.703% 0.158 39.947)", "--color-primary-content": "oklch(14.94% 0.031 39.947)", "--color-secondary": "oklch(72.537% 0.177 2.72)", "--color-secondary-content": "oklch(14.507% 0.035 2.72)", "--color-accent": "oklch(71.294% 0.166 299.844)", "--color-accent-content": "oklch(14.258% 0.033 299.844)", "--color-neutral": "oklch(26% 0.019 237.69)", "--color-neutral-content": "oklch(70% 0.019 237.69)", "--color-info": "oklch(85.559% 0.085 206.015)", "--color-info-content": "oklch(17.111% 0.017 206.015)", "--color-success": "oklch(85.56% 0.085 144.778)", "--color-success-content": "oklch(17.112% 0.017 144.778)", "--color-warning": "oklch(85.569% 0.084 74.427)", "--color-warning-content": "oklch(17.113% 0.016 74.427)", "--color-error": "oklch(85.511% 0.078 16.886)", "--color-error-content": "oklch(17.102% 0.015 16.886)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, synthwave: { "color-scheme": "dark", "--color-base-100": "oklch(15% 0.09 281.288)", "--color-base-200": "oklch(20% 0.09 281.288)", "--color-base-300": "oklch(25% 0.09 281.288)", "--color-base-content": "oklch(78% 0.115 274.713)", "--color-primary": "oklch(71% 0.202 349.761)", "--color-primary-content": "oklch(28% 0.109 3.907)", "--color-secondary": "oklch(82% 0.111 230.318)", "--color-secondary-content": "oklch(29% 0.066 243.157)", "--color-accent": "oklch(75% 0.183 55.934)", "--color-accent-content": "oklch(26% 0.079 36.259)", "--color-neutral": "oklch(45% 0.24 277.023)", "--color-neutral-content": "oklch(87% 0.065 274.039)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(77% 0.152 181.912)", "--color-success-content": "oklch(27% 0.046 192.524)", "--color-warning": "oklch(90% 0.182 98.111)", "--color-warning-content": "oklch(42% 0.095 57.708)", "--color-error": "oklch(73.7% 0.121 32.639)", "--color-error-content": "oklch(23.501% 0.096 290.329)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, dim: { "color-scheme": "dark", "--color-base-100": "oklch(30.857% 0.023 264.149)", "--color-base-200": "oklch(28.036% 0.019 264.182)", "--color-base-300": "oklch(26.346% 0.018 262.177)", "--color-base-content": "oklch(82.901% 0.031 222.959)", "--color-primary": "oklch(86.133% 0.141 139.549)", "--color-primary-content": "oklch(17.226% 0.028 139.549)", "--color-secondary": "oklch(73.375% 0.165 35.353)", "--color-secondary-content": "oklch(14.675% 0.033 35.353)", "--color-accent": "oklch(74.229% 0.133 311.379)", "--color-accent-content": "oklch(14.845% 0.026 311.379)", "--color-neutral": "oklch(24.731% 0.02 264.094)", "--color-neutral-content": "oklch(82.901% 0.031 222.959)", "--color-info": "oklch(86.078% 0.142 206.182)", "--color-info-content": "oklch(17.215% 0.028 206.182)", "--color-success": "oklch(86.171% 0.142 166.534)", "--color-success-content": "oklch(17.234% 0.028 166.534)", "--color-warning": "oklch(86.163% 0.142 94.818)", "--color-warning-content": "oklch(17.232% 0.028 94.818)", "--color-error": "oklch(82.418% 0.099 33.756)", "--color-error-content": "oklch(16.483% 0.019 33.756)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, abyss: { "color-scheme": "dark", "--color-base-100": "oklch(20% 0.08 209)", "--color-base-200": "oklch(15% 0.08 209)", "--color-base-300": "oklch(10% 0.08 209)", "--color-base-content": "oklch(90% 0.076 70.697)", "--color-primary": "oklch(92% 0.2653 125)", "--color-primary-content": "oklch(50% 0.2653 125)", "--color-secondary": "oklch(83.27% 0.0764 298.3)", "--color-secondary-content": "oklch(43.27% 0.0764 298.3)", "--color-accent": "oklch(43% 0 0)", "--color-accent-content": "oklch(98% 0 0)", "--color-neutral": "oklch(30% 0.08 209)", "--color-neutral-content": "oklch(90% 0.076 70.697)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(79% 0.209 151.711)", "--color-success-content": "oklch(26% 0.065 152.934)", "--color-warning": "oklch(84.8% 0.1962 84.62)", "--color-warning-content": "oklch(44.8% 0.1962 84.62)", "--color-error": "oklch(65% 0.1985 24.22)", "--color-error-content": "oklch(27% 0.1985 24.22)", "--radius-selector": "2rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, forest: { "color-scheme": "dark", "--color-base-100": "oklch(20.84% 0.008 17.911)", "--color-base-200": "oklch(18.522% 0.007 17.911)", "--color-base-300": "oklch(16.203% 0.007 17.911)", "--color-base-content": "oklch(83.768% 0.001 17.911)", "--color-primary": "oklch(68.628% 0.185 148.958)", "--color-primary-content": "oklch(0% 0 0)", "--color-secondary": "oklch(69.776% 0.135 168.327)", "--color-secondary-content": "oklch(13.955% 0.027 168.327)", "--color-accent": "oklch(70.628% 0.119 185.713)", "--color-accent-content": "oklch(14.125% 0.023 185.713)", "--color-neutral": "oklch(30.698% 0.039 171.364)", "--color-neutral-content": "oklch(86.139% 0.007 171.364)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, night: { "color-scheme": "dark", "--color-base-100": "oklch(20.768% 0.039 265.754)", "--color-base-200": "oklch(19.314% 0.037 265.754)", "--color-base-300": "oklch(17.86% 0.034 265.754)", "--color-base-content": "oklch(84.153% 0.007 265.754)", "--color-primary": "oklch(75.351% 0.138 232.661)", "--color-primary-content": "oklch(15.07% 0.027 232.661)", "--color-secondary": "oklch(68.011% 0.158 276.934)", "--color-secondary-content": "oklch(13.602% 0.031 276.934)", "--color-accent": "oklch(72.36% 0.176 350.048)", "--color-accent-content": "oklch(14.472% 0.035 350.048)", "--color-neutral": "oklch(27.949% 0.036 260.03)", "--color-neutral-content": "oklch(85.589% 0.007 260.03)", "--color-info": "oklch(68.455% 0.148 237.251)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(78.452% 0.132 181.911)", "--color-success-content": "oklch(15.69% 0.026 181.911)", "--color-warning": "oklch(83.242% 0.139 82.95)", "--color-warning-content": "oklch(16.648% 0.027 82.95)", "--color-error": "oklch(71.785% 0.17 13.118)", "--color-error-content": "oklch(14.357% 0.034 13.118)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, caramellatte: { "color-scheme": "light", "--color-base-100": "oklch(98% 0.016 73.684)", "--color-base-200": "oklch(95% 0.038 75.164)", "--color-base-300": "oklch(90% 0.076 70.697)", "--color-base-content": "oklch(40% 0.123 38.172)", "--color-primary": "oklch(0% 0 0)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(22.45% 0.075 37.85)", "--color-secondary-content": "oklch(90% 0.076 70.697)", "--color-accent": "oklch(46.44% 0.111 37.85)", "--color-accent-content": "oklch(90% 0.076 70.697)", "--color-neutral": "oklch(55% 0.195 38.402)", "--color-neutral-content": "oklch(98% 0.016 73.684)", "--color-info": "oklch(42% 0.199 265.638)", "--color-info-content": "oklch(90% 0.076 70.697)", "--color-success": "oklch(43% 0.095 166.913)", "--color-success-content": "oklch(90% 0.076 70.697)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(39% 0.141 25.723)", "--radius-selector": "2rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "1" }, autumn: { "color-scheme": "light", "--color-base-100": "oklch(95.814% 0 0)", "--color-base-200": "oklch(89.107% 0 0)", "--color-base-300": "oklch(82.4% 0 0)", "--color-base-content": "oklch(19.162% 0 0)", "--color-primary": "oklch(40.723% 0.161 17.53)", "--color-primary-content": "oklch(88.144% 0.032 17.53)", "--color-secondary": "oklch(61.676% 0.169 23.865)", "--color-secondary-content": "oklch(12.335% 0.033 23.865)", "--color-accent": "oklch(73.425% 0.094 60.729)", "--color-accent-content": "oklch(14.685% 0.018 60.729)", "--color-neutral": "oklch(54.367% 0.037 51.902)", "--color-neutral-content": "oklch(90.873% 0.007 51.902)", "--color-info": "oklch(69.224% 0.097 207.284)", "--color-info-content": "oklch(13.844% 0.019 207.284)", "--color-success": "oklch(60.995% 0.08 174.616)", "--color-success-content": "oklch(12.199% 0.016 174.616)", "--color-warning": "oklch(70.081% 0.164 56.844)", "--color-warning-content": "oklch(14.016% 0.032 56.844)", "--color-error": "oklch(53.07% 0.241 24.16)", "--color-error-content": "oklch(90.614% 0.048 24.16)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, emerald: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(93% 0 0)", "--color-base-300": "oklch(86% 0 0)", "--color-base-content": "oklch(35.519% 0.032 262.988)", "--color-primary": "oklch(76.662% 0.135 153.45)", "--color-primary-content": "oklch(33.387% 0.04 162.24)", "--color-secondary": "oklch(61.302% 0.202 261.294)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(72.772% 0.149 33.2)", "--color-accent-content": "oklch(0% 0 0)", "--color-neutral": "oklch(35.519% 0.032 262.988)", "--color-neutral-content": "oklch(98.462% 0.001 247.838)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, cupcake: { "color-scheme": "light", "--color-base-100": "oklch(97.788% 0.004 56.375)", "--color-base-200": "oklch(93.982% 0.007 61.449)", "--color-base-300": "oklch(91.586% 0.006 53.44)", "--color-base-content": "oklch(23.574% 0.066 313.189)", "--color-primary": "oklch(85% 0.138 181.071)", "--color-primary-content": "oklch(43% 0.078 188.216)", "--color-secondary": "oklch(89% 0.061 343.231)", "--color-secondary-content": "oklch(45% 0.187 3.815)", "--color-accent": "oklch(90% 0.076 70.697)", "--color-accent-content": "oklch(47% 0.157 37.304)", "--color-neutral": "oklch(27% 0.006 286.033)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(68% 0.169 237.323)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(69% 0.17 162.48)", "--color-success-content": "oklch(26% 0.051 172.552)", "--color-warning": "oklch(79% 0.184 86.047)", "--color-warning-content": "oklch(28% 0.066 53.813)", "--color-error": "oklch(64% 0.246 16.439)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "0" }, cmyk: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(95% 0 0)", "--color-base-300": "oklch(90% 0 0)", "--color-base-content": "oklch(20% 0 0)", "--color-primary": "oklch(71.772% 0.133 239.443)", "--color-primary-content": "oklch(14.354% 0.026 239.443)", "--color-secondary": "oklch(64.476% 0.202 359.339)", "--color-secondary-content": "oklch(12.895% 0.04 359.339)", "--color-accent": "oklch(94.228% 0.189 105.306)", "--color-accent-content": "oklch(18.845% 0.037 105.306)", "--color-neutral": "oklch(21.778% 0 0)", "--color-neutral-content": "oklch(84.355% 0 0)", "--color-info": "oklch(68.475% 0.094 217.284)", "--color-info-content": "oklch(13.695% 0.018 217.284)", "--color-success": "oklch(46.949% 0.162 321.406)", "--color-success-content": "oklch(89.389% 0.032 321.406)", "--color-warning": "oklch(71.236% 0.159 52.023)", "--color-warning-content": "oklch(14.247% 0.031 52.023)", "--color-error": "oklch(62.013% 0.208 28.717)", "--color-error-content": "oklch(12.402% 0.041 28.717)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, business: { "color-scheme": "dark", "--color-base-100": "oklch(24.353% 0 0)", "--color-base-200": "oklch(22.648% 0 0)", "--color-base-300": "oklch(20.944% 0 0)", "--color-base-content": "oklch(84.87% 0 0)", "--color-primary": "oklch(41.703% 0.099 251.473)", "--color-primary-content": "oklch(88.34% 0.019 251.473)", "--color-secondary": "oklch(64.092% 0.027 229.389)", "--color-secondary-content": "oklch(12.818% 0.005 229.389)", "--color-accent": "oklch(67.271% 0.167 35.791)", "--color-accent-content": "oklch(13.454% 0.033 35.791)", "--color-neutral": "oklch(27.441% 0.013 253.041)", "--color-neutral-content": "oklch(85.488% 0.002 253.041)", "--color-info": "oklch(62.616% 0.143 240.033)", "--color-info-content": "oklch(12.523% 0.028 240.033)", "--color-success": "oklch(70.226% 0.094 156.596)", "--color-success-content": "oklch(14.045% 0.018 156.596)", "--color-warning": "oklch(77.482% 0.115 81.519)", "--color-warning-content": "oklch(15.496% 0.023 81.519)", "--color-error": "oklch(51.61% 0.146 29.674)", "--color-error-content": "oklch(90.322% 0.029 29.674)", "--radius-selector": "0rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, winter: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(97.466% 0.011 259.822)", "--color-base-300": "oklch(93.268% 0.016 262.751)", "--color-base-content": "oklch(41.886% 0.053 255.824)", "--color-primary": "oklch(56.86% 0.255 257.57)", "--color-primary-content": "oklch(91.372% 0.051 257.57)", "--color-secondary": "oklch(42.551% 0.161 282.339)", "--color-secondary-content": "oklch(88.51% 0.032 282.339)", "--color-accent": "oklch(59.939% 0.191 335.171)", "--color-accent-content": "oklch(11.988% 0.038 335.171)", "--color-neutral": "oklch(19.616% 0.063 257.651)", "--color-neutral-content": "oklch(83.923% 0.012 257.651)", "--color-info": "oklch(88.127% 0.085 214.515)", "--color-info-content": "oklch(17.625% 0.017 214.515)", "--color-success": "oklch(80.494% 0.077 197.823)", "--color-success-content": "oklch(16.098% 0.015 197.823)", "--color-warning": "oklch(89.172% 0.045 71.47)", "--color-warning-content": "oklch(17.834% 0.009 71.47)", "--color-error": "oklch(73.092% 0.11 20.076)", "--color-error-content": "oklch(14.618% 0.022 20.076)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, halloween: { "color-scheme": "dark", "--color-base-100": "oklch(21% 0.006 56.043)", "--color-base-200": "oklch(14% 0.004 49.25)", "--color-base-300": "oklch(0% 0 0)", "--color-base-content": "oklch(84.955% 0 0)", "--color-primary": "oklch(77.48% 0.204 60.62)", "--color-primary-content": "oklch(19.693% 0.004 196.779)", "--color-secondary": "oklch(45.98% 0.248 305.03)", "--color-secondary-content": "oklch(89.196% 0.049 305.03)", "--color-accent": "oklch(64.8% 0.223 136.073)", "--color-accent-content": "oklch(0% 0 0)", "--color-neutral": "oklch(24.371% 0.046 65.681)", "--color-neutral-content": "oklch(84.874% 0.009 65.681)", "--color-info": "oklch(54.615% 0.215 262.88)", "--color-info-content": "oklch(90.923% 0.043 262.88)", "--color-success": "oklch(62.705% 0.169 149.213)", "--color-success-content": "oklch(12.541% 0.033 149.213)", "--color-warning": "oklch(66.584% 0.157 58.318)", "--color-warning-content": "oklch(13.316% 0.031 58.318)", "--color-error": "oklch(65.72% 0.199 27.33)", "--color-error-content": "oklch(13.144% 0.039 27.33)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, fantasy: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(93% 0 0)", "--color-base-300": "oklch(86% 0 0)", "--color-base-content": "oklch(27.807% 0.029 256.847)", "--color-primary": "oklch(37.45% 0.189 325.02)", "--color-primary-content": "oklch(87.49% 0.037 325.02)", "--color-secondary": "oklch(53.92% 0.162 241.36)", "--color-secondary-content": "oklch(90.784% 0.032 241.36)", "--color-accent": "oklch(75.98% 0.204 56.72)", "--color-accent-content": "oklch(15.196% 0.04 56.72)", "--color-neutral": "oklch(27.807% 0.029 256.847)", "--color-neutral-content": "oklch(85.561% 0.005 256.847)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, wireframe: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(97% 0 0)", "--color-base-300": "oklch(94% 0 0)", "--color-base-content": "oklch(20% 0 0)", "--color-primary": "oklch(87% 0 0)", "--color-primary-content": "oklch(26% 0 0)", "--color-secondary": "oklch(87% 0 0)", "--color-secondary-content": "oklch(26% 0 0)", "--color-accent": "oklch(87% 0 0)", "--color-accent-content": "oklch(26% 0 0)", "--color-neutral": "oklch(87% 0 0)", "--color-neutral-content": "oklch(26% 0 0)", "--color-info": "oklch(44% 0.11 240.79)", "--color-info-content": "oklch(90% 0.058 230.902)", "--color-success": "oklch(43% 0.095 166.913)", "--color-success-content": "oklch(90% 0.093 164.15)", "--color-warning": "oklch(47% 0.137 46.201)", "--color-warning-content": "oklch(92% 0.12 95.746)", "--color-error": "oklch(44% 0.177 26.899)", "--color-error-content": "oklch(88% 0.062 18.334)", "--radius-selector": "0rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" } }; + +// packages/daisyui/theme/index.js +var theme_default = plugin.withOptions((options = {}) => { + return ({ addBase }) => { + const { + name = "custom-theme", + default: isDefault = false, + prefersdark = false, + "color-scheme": colorScheme = "normal", + root = ":root", + ...customThemeTokens + } = options; + let selector = `${root}:has(input.theme-controller[value=${name}]:checked),[data-theme="${name}"]`; + if (isDefault) { + selector = `:where(${root}),${selector}`; + } + let themeTokens = { ...customThemeTokens }; + if (object_default[name]) { + const builtinTheme = object_default[name]; + themeTokens = { + ...builtinTheme, + ...customThemeTokens, + "color-scheme": colorScheme || builtinTheme.colorScheme + }; + } + const baseStyles = { + [selector]: { + "color-scheme": themeTokens["color-scheme"] || colorScheme, + ...themeTokens + } + }; + if (prefersdark) { + addBase({ + "@media (prefers-color-scheme: dark)": { + [root]: baseStyles[selector] + } + }); + } + addBase(baseStyles); + }; +}); + + +/* + + MIT License + + Copyright (c) 2020 Pouya Saadeghi – https://daisyui.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +*/ diff --git a/assets/vendor/daisyui.js b/assets/vendor/daisyui.js new file mode 100644 index 0000000..46bf6bf --- /dev/null +++ b/assets/vendor/daisyui.js @@ -0,0 +1,1031 @@ +/** 🌼 + * @license MIT + * daisyUI bundle + * https://daisyui.com/ + */ + +var __defProp = Object.defineProperty; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __moduleCache = /* @__PURE__ */ new WeakMap; +var __toCommonJS = (from) => { + var entry = __moduleCache.get(from), desc; + if (entry) + return entry; + entry = __defProp({}, "__esModule", { value: true }); + if (from && typeof from === "object" || typeof from === "function") + __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, { + get: () => from[key], + enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable + })); + __moduleCache.set(from, entry); + return entry; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { + get: all[name], + enumerable: true, + configurable: true, + set: (newValue) => all[name] = () => newValue + }); +}; + +// packages/daisyui/index.js +var exports_daisyui = {}; +__export(exports_daisyui, { + default: () => daisyui_default +}); +module.exports = __toCommonJS(exports_daisyui); + +// packages/daisyui/functions/themeOrder.js +var themeOrder_default = [ + "light", + "dark", + "cupcake", + "bumblebee", + "emerald", + "corporate", + "synthwave", + "retro", + "cyberpunk", + "valentine", + "halloween", + "garden", + "forest", + "aqua", + "lofi", + "pastel", + "fantasy", + "wireframe", + "black", + "luxury", + "dracula", + "cmyk", + "autumn", + "business", + "acid", + "lemonade", + "night", + "coffee", + "winter", + "dim", + "nord", + "sunset", + "caramellatte", + "abyss", + "silk" +]; + +// packages/daisyui/functions/pluginOptionsHandler.js +var pluginOptionsHandler = (() => { + let firstRun = true; + return (options, addBase, themesObject, packageVersion) => { + const { + logs = true, + root = ":root", + themes = ["light --default", "dark --prefersdark"], + include, + exclude, + prefix = "" + } = options || {}; + if (logs !== false && firstRun) { + console.log(`${atob("Lyoh")} ${decodeURIComponent("%F0%9F%8C%BC")} ${atob("ZGFpc3lVSQ==")} ${packageVersion} ${atob("Ki8=")}`); + firstRun = false; + } + const applyTheme = (themeName, flags) => { + const theme = themesObject[themeName]; + if (theme) { + let selector = `${root}:has(input.theme-controller[value=${themeName}]:checked),[data-theme=${themeName}]`; + if (flags.includes("--default")) { + selector = `:where(${root}),${selector}`; + } + addBase({ [selector]: theme }); + if (flags.includes("--prefersdark")) { + addBase({ "@media (prefers-color-scheme: dark)": { [root]: theme } }); + } + } + }; + if (themes === "all") { + if (themesObject["light"]) { + applyTheme("light", ["--default"]); + } + if (themesObject["dark"]) { + addBase({ "@media (prefers-color-scheme: dark)": { [root]: themesObject["dark"] } }); + } + themeOrder_default.forEach((themeName) => { + if (themesObject[themeName]) { + applyTheme(themeName, []); + } + }); + } else if (themes) { + const themeArray = Array.isArray(themes) ? themes : [themes]; + if (themeArray.length === 1 && themeArray[0].includes("--default")) { + const [themeName, ...flags] = themeArray[0].split(" "); + applyTheme(themeName, flags); + return { include, exclude, prefix }; + } + themeArray.forEach((themeOption) => { + const [themeName, ...flags] = themeOption.split(" "); + if (flags.includes("--default")) { + applyTheme(themeName, ["--default"]); + } + }); + themeArray.forEach((themeOption) => { + const [themeName, ...flags] = themeOption.split(" "); + if (flags.includes("--prefersdark")) { + addBase({ "@media (prefers-color-scheme: dark)": { [root]: themesObject[themeName] } }); + } + }); + themeArray.forEach((themeOption) => { + const [themeName] = themeOption.split(" "); + applyTheme(themeName, []); + }); + } + return { include, exclude, prefix }; + }; +})(); + +// packages/daisyui/functions/plugin.js +var plugin = { + withOptions: (pluginFunction, configFunction = () => ({})) => { + const optionsFunction = (options) => { + const handler = pluginFunction(options); + const config = configFunction(options); + return { handler, config }; + }; + optionsFunction.__isOptionsFunction = true; + return optionsFunction; + } +}; + +// packages/daisyui/functions/variables.js +var variables_default = { + colors: { + "base-100": "var(--color-base-100)", + "base-200": "var(--color-base-200)", + "base-300": "var(--color-base-300)", + "base-content": "var(--color-base-content)", + primary: "var(--color-primary)", + "primary-content": "var(--color-primary-content)", + secondary: "var(--color-secondary)", + "secondary-content": "var(--color-secondary-content)", + accent: "var(--color-accent)", + "accent-content": "var(--color-accent-content)", + neutral: "var(--color-neutral)", + "neutral-content": "var(--color-neutral-content)", + info: "var(--color-info)", + "info-content": "var(--color-info-content)", + success: "var(--color-success)", + "success-content": "var(--color-success-content)", + warning: "var(--color-warning)", + "warning-content": "var(--color-warning-content)", + error: "var(--color-error)", + "error-content": "var(--color-error-content)" + }, + borderRadius: { + selector: "var(--radius-selector)", + field: "var(--radius-field)", + box: "var(--radius-box)" + } +}; + +// packages/daisyui/theme/object.js +var object_default = { cyberpunk: { "color-scheme": "light", "--color-base-100": "oklch(94.51% 0.179 104.32)", "--color-base-200": "oklch(91.51% 0.179 104.32)", "--color-base-300": "oklch(85.51% 0.179 104.32)", "--color-base-content": "oklch(0% 0 0)", "--color-primary": "oklch(74.22% 0.209 6.35)", "--color-primary-content": "oklch(14.844% 0.041 6.35)", "--color-secondary": "oklch(83.33% 0.184 204.72)", "--color-secondary-content": "oklch(16.666% 0.036 204.72)", "--color-accent": "oklch(71.86% 0.217 310.43)", "--color-accent-content": "oklch(14.372% 0.043 310.43)", "--color-neutral": "oklch(23.04% 0.065 269.31)", "--color-neutral-content": "oklch(94.51% 0.179 104.32)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "0rem", "--radius-field": "0rem", "--radius-box": "0rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, acid: { "color-scheme": "light", "--color-base-100": "oklch(98% 0 0)", "--color-base-200": "oklch(95% 0 0)", "--color-base-300": "oklch(91% 0 0)", "--color-base-content": "oklch(0% 0 0)", "--color-primary": "oklch(71.9% 0.357 330.759)", "--color-primary-content": "oklch(14.38% 0.071 330.759)", "--color-secondary": "oklch(73.37% 0.224 48.25)", "--color-secondary-content": "oklch(14.674% 0.044 48.25)", "--color-accent": "oklch(92.78% 0.264 122.962)", "--color-accent-content": "oklch(18.556% 0.052 122.962)", "--color-neutral": "oklch(21.31% 0.128 278.68)", "--color-neutral-content": "oklch(84.262% 0.025 278.68)", "--color-info": "oklch(60.72% 0.227 252.05)", "--color-info-content": "oklch(12.144% 0.045 252.05)", "--color-success": "oklch(85.72% 0.266 158.53)", "--color-success-content": "oklch(17.144% 0.053 158.53)", "--color-warning": "oklch(91.01% 0.212 100.5)", "--color-warning-content": "oklch(18.202% 0.042 100.5)", "--color-error": "oklch(64.84% 0.293 29.349)", "--color-error-content": "oklch(12.968% 0.058 29.349)", "--radius-selector": "1rem", "--radius-field": "1rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, black: { "color-scheme": "dark", "--color-base-100": "oklch(0% 0 0)", "--color-base-200": "oklch(19% 0 0)", "--color-base-300": "oklch(22% 0 0)", "--color-base-content": "oklch(87.609% 0 0)", "--color-primary": "oklch(35% 0 0)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(35% 0 0)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(35% 0 0)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(35% 0 0)", "--color-neutral-content": "oklch(100% 0 0)", "--color-info": "oklch(45.201% 0.313 264.052)", "--color-info-content": "oklch(89.04% 0.062 264.052)", "--color-success": "oklch(51.975% 0.176 142.495)", "--color-success-content": "oklch(90.395% 0.035 142.495)", "--color-warning": "oklch(96.798% 0.211 109.769)", "--color-warning-content": "oklch(19.359% 0.042 109.769)", "--color-error": "oklch(62.795% 0.257 29.233)", "--color-error-content": "oklch(12.559% 0.051 29.233)", "--radius-selector": "0rem", "--radius-field": "0rem", "--radius-box": "0rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, dark: { "color-scheme": "dark", "--color-base-100": "oklch(25.33% 0.016 252.42)", "--color-base-200": "oklch(23.26% 0.014 253.1)", "--color-base-300": "oklch(21.15% 0.012 254.09)", "--color-base-content": "oklch(97.807% 0.029 256.847)", "--color-primary": "oklch(58% 0.233 277.117)", "--color-primary-content": "oklch(96% 0.018 272.314)", "--color-secondary": "oklch(65% 0.241 354.308)", "--color-secondary-content": "oklch(94% 0.028 342.258)", "--color-accent": "oklch(77% 0.152 181.912)", "--color-accent-content": "oklch(38% 0.063 188.416)", "--color-neutral": "oklch(14% 0.005 285.823)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(71% 0.194 13.428)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "0.5rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, light: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(98% 0 0)", "--color-base-300": "oklch(95% 0 0)", "--color-base-content": "oklch(21% 0.006 285.885)", "--color-primary": "oklch(45% 0.24 277.023)", "--color-primary-content": "oklch(93% 0.034 272.788)", "--color-secondary": "oklch(65% 0.241 354.308)", "--color-secondary-content": "oklch(94% 0.028 342.258)", "--color-accent": "oklch(77% 0.152 181.912)", "--color-accent-content": "oklch(38% 0.063 188.416)", "--color-neutral": "oklch(14% 0.005 285.823)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(71% 0.194 13.428)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "0.5rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, luxury: { "color-scheme": "dark", "--color-base-100": "oklch(14.076% 0.004 285.822)", "--color-base-200": "oklch(20.219% 0.004 308.229)", "--color-base-300": "oklch(23.219% 0.004 308.229)", "--color-base-content": "oklch(75.687% 0.123 76.89)", "--color-primary": "oklch(100% 0 0)", "--color-primary-content": "oklch(20% 0 0)", "--color-secondary": "oklch(27.581% 0.064 261.069)", "--color-secondary-content": "oklch(85.516% 0.012 261.069)", "--color-accent": "oklch(36.674% 0.051 338.825)", "--color-accent-content": "oklch(87.334% 0.01 338.825)", "--color-neutral": "oklch(24.27% 0.057 59.825)", "--color-neutral-content": "oklch(93.203% 0.089 90.861)", "--color-info": "oklch(79.061% 0.121 237.133)", "--color-info-content": "oklch(15.812% 0.024 237.133)", "--color-success": "oklch(78.119% 0.192 132.154)", "--color-success-content": "oklch(15.623% 0.038 132.154)", "--color-warning": "oklch(86.127% 0.136 102.891)", "--color-warning-content": "oklch(17.225% 0.027 102.891)", "--color-error": "oklch(71.753% 0.176 22.568)", "--color-error-content": "oklch(14.35% 0.035 22.568)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, dracula: { "color-scheme": "dark", "--color-base-100": "oklch(28.822% 0.022 277.508)", "--color-base-200": "oklch(26.805% 0.02 277.508)", "--color-base-300": "oklch(24.787% 0.019 277.508)", "--color-base-content": "oklch(97.747% 0.007 106.545)", "--color-primary": "oklch(75.461% 0.183 346.812)", "--color-primary-content": "oklch(15.092% 0.036 346.812)", "--color-secondary": "oklch(74.202% 0.148 301.883)", "--color-secondary-content": "oklch(14.84% 0.029 301.883)", "--color-accent": "oklch(83.392% 0.124 66.558)", "--color-accent-content": "oklch(16.678% 0.024 66.558)", "--color-neutral": "oklch(39.445% 0.032 275.524)", "--color-neutral-content": "oklch(87.889% 0.006 275.524)", "--color-info": "oklch(88.263% 0.093 212.846)", "--color-info-content": "oklch(17.652% 0.018 212.846)", "--color-success": "oklch(87.099% 0.219 148.024)", "--color-success-content": "oklch(17.419% 0.043 148.024)", "--color-warning": "oklch(95.533% 0.134 112.757)", "--color-warning-content": "oklch(19.106% 0.026 112.757)", "--color-error": "oklch(68.22% 0.206 24.43)", "--color-error-content": "oklch(13.644% 0.041 24.43)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, retro: { "color-scheme": "light", "--color-base-100": "oklch(91.637% 0.034 90.515)", "--color-base-200": "oklch(88.272% 0.049 91.774)", "--color-base-300": "oklch(84.133% 0.065 90.856)", "--color-base-content": "oklch(41% 0.112 45.904)", "--color-primary": "oklch(80% 0.114 19.571)", "--color-primary-content": "oklch(39% 0.141 25.723)", "--color-secondary": "oklch(92% 0.084 155.995)", "--color-secondary-content": "oklch(44% 0.119 151.328)", "--color-accent": "oklch(68% 0.162 75.834)", "--color-accent-content": "oklch(41% 0.112 45.904)", "--color-neutral": "oklch(44% 0.011 73.639)", "--color-neutral-content": "oklch(86% 0.005 56.366)", "--color-info": "oklch(58% 0.158 241.966)", "--color-info-content": "oklch(96% 0.059 95.617)", "--color-success": "oklch(51% 0.096 186.391)", "--color-success-content": "oklch(96% 0.059 95.617)", "--color-warning": "oklch(64% 0.222 41.116)", "--color-warning-content": "oklch(96% 0.059 95.617)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(40% 0.123 38.172)", "--radius-selector": "0.25rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, lofi: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(97% 0 0)", "--color-base-300": "oklch(94% 0 0)", "--color-base-content": "oklch(0% 0 0)", "--color-primary": "oklch(15.906% 0 0)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(21.455% 0.001 17.278)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(26.861% 0 0)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(0% 0 0)", "--color-neutral-content": "oklch(100% 0 0)", "--color-info": "oklch(79.54% 0.103 205.9)", "--color-info-content": "oklch(15.908% 0.02 205.9)", "--color-success": "oklch(90.13% 0.153 164.14)", "--color-success-content": "oklch(18.026% 0.03 164.14)", "--color-warning": "oklch(88.37% 0.135 79.94)", "--color-warning-content": "oklch(17.674% 0.027 79.94)", "--color-error": "oklch(78.66% 0.15 28.47)", "--color-error-content": "oklch(15.732% 0.03 28.47)", "--radius-selector": "2rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, valentine: { "color-scheme": "light", "--color-base-100": "oklch(97% 0.014 343.198)", "--color-base-200": "oklch(94% 0.028 342.258)", "--color-base-300": "oklch(89% 0.061 343.231)", "--color-base-content": "oklch(52% 0.223 3.958)", "--color-primary": "oklch(65% 0.241 354.308)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(62% 0.265 303.9)", "--color-secondary-content": "oklch(97% 0.014 308.299)", "--color-accent": "oklch(82% 0.111 230.318)", "--color-accent-content": "oklch(39% 0.09 240.876)", "--color-neutral": "oklch(40% 0.153 2.432)", "--color-neutral-content": "oklch(89% 0.061 343.231)", "--color-info": "oklch(86% 0.127 207.078)", "--color-info-content": "oklch(44% 0.11 240.79)", "--color-success": "oklch(84% 0.143 164.978)", "--color-success-content": "oklch(43% 0.095 166.913)", "--color-warning": "oklch(75% 0.183 55.934)", "--color-warning-content": "oklch(26% 0.079 36.259)", "--color-error": "oklch(63% 0.237 25.331)", "--color-error-content": "oklch(97% 0.013 17.38)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, nord: { "color-scheme": "light", "--color-base-100": "oklch(95.127% 0.007 260.731)", "--color-base-200": "oklch(93.299% 0.01 261.788)", "--color-base-300": "oklch(89.925% 0.016 262.749)", "--color-base-content": "oklch(32.437% 0.022 264.182)", "--color-primary": "oklch(59.435% 0.077 254.027)", "--color-primary-content": "oklch(11.887% 0.015 254.027)", "--color-secondary": "oklch(69.651% 0.059 248.687)", "--color-secondary-content": "oklch(13.93% 0.011 248.687)", "--color-accent": "oklch(77.464% 0.062 217.469)", "--color-accent-content": "oklch(15.492% 0.012 217.469)", "--color-neutral": "oklch(45.229% 0.035 264.131)", "--color-neutral-content": "oklch(89.925% 0.016 262.749)", "--color-info": "oklch(69.207% 0.062 332.664)", "--color-info-content": "oklch(13.841% 0.012 332.664)", "--color-success": "oklch(76.827% 0.074 131.063)", "--color-success-content": "oklch(15.365% 0.014 131.063)", "--color-warning": "oklch(85.486% 0.089 84.093)", "--color-warning-content": "oklch(17.097% 0.017 84.093)", "--color-error": "oklch(60.61% 0.12 15.341)", "--color-error-content": "oklch(12.122% 0.024 15.341)", "--radius-selector": "1rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, lemonade: { "color-scheme": "light", "--color-base-100": "oklch(98.71% 0.02 123.72)", "--color-base-200": "oklch(91.8% 0.018 123.72)", "--color-base-300": "oklch(84.89% 0.017 123.72)", "--color-base-content": "oklch(19.742% 0.004 123.72)", "--color-primary": "oklch(58.92% 0.199 134.6)", "--color-primary-content": "oklch(11.784% 0.039 134.6)", "--color-secondary": "oklch(77.75% 0.196 111.09)", "--color-secondary-content": "oklch(15.55% 0.039 111.09)", "--color-accent": "oklch(85.39% 0.201 100.73)", "--color-accent-content": "oklch(17.078% 0.04 100.73)", "--color-neutral": "oklch(30.98% 0.075 108.6)", "--color-neutral-content": "oklch(86.196% 0.015 108.6)", "--color-info": "oklch(86.19% 0.047 224.14)", "--color-info-content": "oklch(17.238% 0.009 224.14)", "--color-success": "oklch(86.19% 0.047 157.85)", "--color-success-content": "oklch(17.238% 0.009 157.85)", "--color-warning": "oklch(86.19% 0.047 102.15)", "--color-warning-content": "oklch(17.238% 0.009 102.15)", "--color-error": "oklch(86.19% 0.047 25.85)", "--color-error-content": "oklch(17.238% 0.009 25.85)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, garden: { "color-scheme": "light", "--color-base-100": "oklch(92.951% 0.002 17.197)", "--color-base-200": "oklch(86.445% 0.002 17.197)", "--color-base-300": "oklch(79.938% 0.001 17.197)", "--color-base-content": "oklch(16.961% 0.001 17.32)", "--color-primary": "oklch(62.45% 0.278 3.836)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(48.495% 0.11 355.095)", "--color-secondary-content": "oklch(89.699% 0.022 355.095)", "--color-accent": "oklch(56.273% 0.054 154.39)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(24.155% 0.049 89.07)", "--color-neutral-content": "oklch(92.951% 0.002 17.197)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, aqua: { "color-scheme": "dark", "--color-base-100": "oklch(37% 0.146 265.522)", "--color-base-200": "oklch(28% 0.091 267.935)", "--color-base-300": "oklch(22% 0.091 267.935)", "--color-base-content": "oklch(90% 0.058 230.902)", "--color-primary": "oklch(85.661% 0.144 198.645)", "--color-primary-content": "oklch(40.124% 0.068 197.603)", "--color-secondary": "oklch(60.682% 0.108 309.782)", "--color-secondary-content": "oklch(96% 0.016 293.756)", "--color-accent": "oklch(93.426% 0.102 94.555)", "--color-accent-content": "oklch(18.685% 0.02 94.555)", "--color-neutral": "oklch(27% 0.146 265.522)", "--color-neutral-content": "oklch(80% 0.146 265.522)", "--color-info": "oklch(54.615% 0.215 262.88)", "--color-info-content": "oklch(90.923% 0.043 262.88)", "--color-success": "oklch(62.705% 0.169 149.213)", "--color-success-content": "oklch(12.541% 0.033 149.213)", "--color-warning": "oklch(66.584% 0.157 58.318)", "--color-warning-content": "oklch(27% 0.077 45.635)", "--color-error": "oklch(73.95% 0.19 27.33)", "--color-error-content": "oklch(14.79% 0.038 27.33)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, corporate: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(93% 0 0)", "--color-base-300": "oklch(86% 0 0)", "--color-base-content": "oklch(22.389% 0.031 278.072)", "--color-primary": "oklch(58% 0.158 241.966)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(55% 0.046 257.417)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(60% 0.118 184.704)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(0% 0 0)", "--color-neutral-content": "oklch(100% 0 0)", "--color-info": "oklch(60% 0.126 221.723)", "--color-info-content": "oklch(100% 0 0)", "--color-success": "oklch(62% 0.194 149.214)", "--color-success-content": "oklch(100% 0 0)", "--color-warning": "oklch(85% 0.199 91.936)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "0.25rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, pastel: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(98.462% 0.001 247.838)", "--color-base-300": "oklch(92.462% 0.001 247.838)", "--color-base-content": "oklch(20% 0 0)", "--color-primary": "oklch(90% 0.063 306.703)", "--color-primary-content": "oklch(49% 0.265 301.924)", "--color-secondary": "oklch(89% 0.058 10.001)", "--color-secondary-content": "oklch(51% 0.222 16.935)", "--color-accent": "oklch(90% 0.093 164.15)", "--color-accent-content": "oklch(50% 0.118 165.612)", "--color-neutral": "oklch(55% 0.046 257.417)", "--color-neutral-content": "oklch(92% 0.013 255.508)", "--color-info": "oklch(86% 0.127 207.078)", "--color-info-content": "oklch(52% 0.105 223.128)", "--color-success": "oklch(87% 0.15 154.449)", "--color-success-content": "oklch(52% 0.154 150.069)", "--color-warning": "oklch(83% 0.128 66.29)", "--color-warning-content": "oklch(55% 0.195 38.402)", "--color-error": "oklch(80% 0.114 19.571)", "--color-error-content": "oklch(50% 0.213 27.518)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "0", "--noise": "0" }, bumblebee: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(97% 0 0)", "--color-base-300": "oklch(92% 0 0)", "--color-base-content": "oklch(20% 0 0)", "--color-primary": "oklch(85% 0.199 91.936)", "--color-primary-content": "oklch(42% 0.095 57.708)", "--color-secondary": "oklch(75% 0.183 55.934)", "--color-secondary-content": "oklch(40% 0.123 38.172)", "--color-accent": "oklch(0% 0 0)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(37% 0.01 67.558)", "--color-neutral-content": "oklch(92% 0.003 48.717)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(39% 0.09 240.876)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(39% 0.141 25.723)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, coffee: { "color-scheme": "dark", "--color-base-100": "oklch(24% 0.023 329.708)", "--color-base-200": "oklch(21% 0.021 329.708)", "--color-base-300": "oklch(16% 0.019 329.708)", "--color-base-content": "oklch(72.354% 0.092 79.129)", "--color-primary": "oklch(71.996% 0.123 62.756)", "--color-primary-content": "oklch(14.399% 0.024 62.756)", "--color-secondary": "oklch(34.465% 0.029 199.194)", "--color-secondary-content": "oklch(86.893% 0.005 199.194)", "--color-accent": "oklch(42.621% 0.074 224.389)", "--color-accent-content": "oklch(88.524% 0.014 224.389)", "--color-neutral": "oklch(16.51% 0.015 326.261)", "--color-neutral-content": "oklch(83.302% 0.003 326.261)", "--color-info": "oklch(79.49% 0.063 184.558)", "--color-info-content": "oklch(15.898% 0.012 184.558)", "--color-success": "oklch(74.722% 0.072 131.116)", "--color-success-content": "oklch(14.944% 0.014 131.116)", "--color-warning": "oklch(88.15% 0.14 87.722)", "--color-warning-content": "oklch(17.63% 0.028 87.722)", "--color-error": "oklch(77.318% 0.128 31.871)", "--color-error-content": "oklch(15.463% 0.025 31.871)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, silk: { "color-scheme": "light", "--color-base-100": "oklch(97% 0.0035 67.78)", "--color-base-200": "oklch(95% 0.0081 61.42)", "--color-base-300": "oklch(90% 0.0081 61.42)", "--color-base-content": "oklch(40% 0.0081 61.42)", "--color-primary": "oklch(23.27% 0.0249 284.3)", "--color-primary-content": "oklch(94.22% 0.2505 117.44)", "--color-secondary": "oklch(23.27% 0.0249 284.3)", "--color-secondary-content": "oklch(73.92% 0.2135 50.94)", "--color-accent": "oklch(23.27% 0.0249 284.3)", "--color-accent-content": "oklch(88.92% 0.2061 189.9)", "--color-neutral": "oklch(20% 0 0)", "--color-neutral-content": "oklch(80% 0.0081 61.42)", "--color-info": "oklch(80.39% 0.1148 241.68)", "--color-info-content": "oklch(30.39% 0.1148 241.68)", "--color-success": "oklch(83.92% 0.0901 136.87)", "--color-success-content": "oklch(23.92% 0.0901 136.87)", "--color-warning": "oklch(83.92% 0.1085 80)", "--color-warning-content": "oklch(43.92% 0.1085 80)", "--color-error": "oklch(75.1% 0.1814 22.37)", "--color-error-content": "oklch(35.1% 0.1814 22.37)", "--radius-selector": "2rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "0" }, sunset: { "color-scheme": "dark", "--color-base-100": "oklch(22% 0.019 237.69)", "--color-base-200": "oklch(20% 0.019 237.69)", "--color-base-300": "oklch(18% 0.019 237.69)", "--color-base-content": "oklch(77.383% 0.043 245.096)", "--color-primary": "oklch(74.703% 0.158 39.947)", "--color-primary-content": "oklch(14.94% 0.031 39.947)", "--color-secondary": "oklch(72.537% 0.177 2.72)", "--color-secondary-content": "oklch(14.507% 0.035 2.72)", "--color-accent": "oklch(71.294% 0.166 299.844)", "--color-accent-content": "oklch(14.258% 0.033 299.844)", "--color-neutral": "oklch(26% 0.019 237.69)", "--color-neutral-content": "oklch(70% 0.019 237.69)", "--color-info": "oklch(85.559% 0.085 206.015)", "--color-info-content": "oklch(17.111% 0.017 206.015)", "--color-success": "oklch(85.56% 0.085 144.778)", "--color-success-content": "oklch(17.112% 0.017 144.778)", "--color-warning": "oklch(85.569% 0.084 74.427)", "--color-warning-content": "oklch(17.113% 0.016 74.427)", "--color-error": "oklch(85.511% 0.078 16.886)", "--color-error-content": "oklch(17.102% 0.015 16.886)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, synthwave: { "color-scheme": "dark", "--color-base-100": "oklch(15% 0.09 281.288)", "--color-base-200": "oklch(20% 0.09 281.288)", "--color-base-300": "oklch(25% 0.09 281.288)", "--color-base-content": "oklch(78% 0.115 274.713)", "--color-primary": "oklch(71% 0.202 349.761)", "--color-primary-content": "oklch(28% 0.109 3.907)", "--color-secondary": "oklch(82% 0.111 230.318)", "--color-secondary-content": "oklch(29% 0.066 243.157)", "--color-accent": "oklch(75% 0.183 55.934)", "--color-accent-content": "oklch(26% 0.079 36.259)", "--color-neutral": "oklch(45% 0.24 277.023)", "--color-neutral-content": "oklch(87% 0.065 274.039)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(77% 0.152 181.912)", "--color-success-content": "oklch(27% 0.046 192.524)", "--color-warning": "oklch(90% 0.182 98.111)", "--color-warning-content": "oklch(42% 0.095 57.708)", "--color-error": "oklch(73.7% 0.121 32.639)", "--color-error-content": "oklch(23.501% 0.096 290.329)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, dim: { "color-scheme": "dark", "--color-base-100": "oklch(30.857% 0.023 264.149)", "--color-base-200": "oklch(28.036% 0.019 264.182)", "--color-base-300": "oklch(26.346% 0.018 262.177)", "--color-base-content": "oklch(82.901% 0.031 222.959)", "--color-primary": "oklch(86.133% 0.141 139.549)", "--color-primary-content": "oklch(17.226% 0.028 139.549)", "--color-secondary": "oklch(73.375% 0.165 35.353)", "--color-secondary-content": "oklch(14.675% 0.033 35.353)", "--color-accent": "oklch(74.229% 0.133 311.379)", "--color-accent-content": "oklch(14.845% 0.026 311.379)", "--color-neutral": "oklch(24.731% 0.02 264.094)", "--color-neutral-content": "oklch(82.901% 0.031 222.959)", "--color-info": "oklch(86.078% 0.142 206.182)", "--color-info-content": "oklch(17.215% 0.028 206.182)", "--color-success": "oklch(86.171% 0.142 166.534)", "--color-success-content": "oklch(17.234% 0.028 166.534)", "--color-warning": "oklch(86.163% 0.142 94.818)", "--color-warning-content": "oklch(17.232% 0.028 94.818)", "--color-error": "oklch(82.418% 0.099 33.756)", "--color-error-content": "oklch(16.483% 0.019 33.756)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, abyss: { "color-scheme": "dark", "--color-base-100": "oklch(20% 0.08 209)", "--color-base-200": "oklch(15% 0.08 209)", "--color-base-300": "oklch(10% 0.08 209)", "--color-base-content": "oklch(90% 0.076 70.697)", "--color-primary": "oklch(92% 0.2653 125)", "--color-primary-content": "oklch(50% 0.2653 125)", "--color-secondary": "oklch(83.27% 0.0764 298.3)", "--color-secondary-content": "oklch(43.27% 0.0764 298.3)", "--color-accent": "oklch(43% 0 0)", "--color-accent-content": "oklch(98% 0 0)", "--color-neutral": "oklch(30% 0.08 209)", "--color-neutral-content": "oklch(90% 0.076 70.697)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(79% 0.209 151.711)", "--color-success-content": "oklch(26% 0.065 152.934)", "--color-warning": "oklch(84.8% 0.1962 84.62)", "--color-warning-content": "oklch(44.8% 0.1962 84.62)", "--color-error": "oklch(65% 0.1985 24.22)", "--color-error-content": "oklch(27% 0.1985 24.22)", "--radius-selector": "2rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, forest: { "color-scheme": "dark", "--color-base-100": "oklch(20.84% 0.008 17.911)", "--color-base-200": "oklch(18.522% 0.007 17.911)", "--color-base-300": "oklch(16.203% 0.007 17.911)", "--color-base-content": "oklch(83.768% 0.001 17.911)", "--color-primary": "oklch(68.628% 0.185 148.958)", "--color-primary-content": "oklch(0% 0 0)", "--color-secondary": "oklch(69.776% 0.135 168.327)", "--color-secondary-content": "oklch(13.955% 0.027 168.327)", "--color-accent": "oklch(70.628% 0.119 185.713)", "--color-accent-content": "oklch(14.125% 0.023 185.713)", "--color-neutral": "oklch(30.698% 0.039 171.364)", "--color-neutral-content": "oklch(86.139% 0.007 171.364)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, night: { "color-scheme": "dark", "--color-base-100": "oklch(20.768% 0.039 265.754)", "--color-base-200": "oklch(19.314% 0.037 265.754)", "--color-base-300": "oklch(17.86% 0.034 265.754)", "--color-base-content": "oklch(84.153% 0.007 265.754)", "--color-primary": "oklch(75.351% 0.138 232.661)", "--color-primary-content": "oklch(15.07% 0.027 232.661)", "--color-secondary": "oklch(68.011% 0.158 276.934)", "--color-secondary-content": "oklch(13.602% 0.031 276.934)", "--color-accent": "oklch(72.36% 0.176 350.048)", "--color-accent-content": "oklch(14.472% 0.035 350.048)", "--color-neutral": "oklch(27.949% 0.036 260.03)", "--color-neutral-content": "oklch(85.589% 0.007 260.03)", "--color-info": "oklch(68.455% 0.148 237.251)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(78.452% 0.132 181.911)", "--color-success-content": "oklch(15.69% 0.026 181.911)", "--color-warning": "oklch(83.242% 0.139 82.95)", "--color-warning-content": "oklch(16.648% 0.027 82.95)", "--color-error": "oklch(71.785% 0.17 13.118)", "--color-error-content": "oklch(14.357% 0.034 13.118)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, caramellatte: { "color-scheme": "light", "--color-base-100": "oklch(98% 0.016 73.684)", "--color-base-200": "oklch(95% 0.038 75.164)", "--color-base-300": "oklch(90% 0.076 70.697)", "--color-base-content": "oklch(40% 0.123 38.172)", "--color-primary": "oklch(0% 0 0)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(22.45% 0.075 37.85)", "--color-secondary-content": "oklch(90% 0.076 70.697)", "--color-accent": "oklch(46.44% 0.111 37.85)", "--color-accent-content": "oklch(90% 0.076 70.697)", "--color-neutral": "oklch(55% 0.195 38.402)", "--color-neutral-content": "oklch(98% 0.016 73.684)", "--color-info": "oklch(42% 0.199 265.638)", "--color-info-content": "oklch(90% 0.076 70.697)", "--color-success": "oklch(43% 0.095 166.913)", "--color-success-content": "oklch(90% 0.076 70.697)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(39% 0.141 25.723)", "--radius-selector": "2rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "1" }, autumn: { "color-scheme": "light", "--color-base-100": "oklch(95.814% 0 0)", "--color-base-200": "oklch(89.107% 0 0)", "--color-base-300": "oklch(82.4% 0 0)", "--color-base-content": "oklch(19.162% 0 0)", "--color-primary": "oklch(40.723% 0.161 17.53)", "--color-primary-content": "oklch(88.144% 0.032 17.53)", "--color-secondary": "oklch(61.676% 0.169 23.865)", "--color-secondary-content": "oklch(12.335% 0.033 23.865)", "--color-accent": "oklch(73.425% 0.094 60.729)", "--color-accent-content": "oklch(14.685% 0.018 60.729)", "--color-neutral": "oklch(54.367% 0.037 51.902)", "--color-neutral-content": "oklch(90.873% 0.007 51.902)", "--color-info": "oklch(69.224% 0.097 207.284)", "--color-info-content": "oklch(13.844% 0.019 207.284)", "--color-success": "oklch(60.995% 0.08 174.616)", "--color-success-content": "oklch(12.199% 0.016 174.616)", "--color-warning": "oklch(70.081% 0.164 56.844)", "--color-warning-content": "oklch(14.016% 0.032 56.844)", "--color-error": "oklch(53.07% 0.241 24.16)", "--color-error-content": "oklch(90.614% 0.048 24.16)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, emerald: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(93% 0 0)", "--color-base-300": "oklch(86% 0 0)", "--color-base-content": "oklch(35.519% 0.032 262.988)", "--color-primary": "oklch(76.662% 0.135 153.45)", "--color-primary-content": "oklch(33.387% 0.04 162.24)", "--color-secondary": "oklch(61.302% 0.202 261.294)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(72.772% 0.149 33.2)", "--color-accent-content": "oklch(0% 0 0)", "--color-neutral": "oklch(35.519% 0.032 262.988)", "--color-neutral-content": "oklch(98.462% 0.001 247.838)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, cupcake: { "color-scheme": "light", "--color-base-100": "oklch(97.788% 0.004 56.375)", "--color-base-200": "oklch(93.982% 0.007 61.449)", "--color-base-300": "oklch(91.586% 0.006 53.44)", "--color-base-content": "oklch(23.574% 0.066 313.189)", "--color-primary": "oklch(85% 0.138 181.071)", "--color-primary-content": "oklch(43% 0.078 188.216)", "--color-secondary": "oklch(89% 0.061 343.231)", "--color-secondary-content": "oklch(45% 0.187 3.815)", "--color-accent": "oklch(90% 0.076 70.697)", "--color-accent-content": "oklch(47% 0.157 37.304)", "--color-neutral": "oklch(27% 0.006 286.033)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(68% 0.169 237.323)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(69% 0.17 162.48)", "--color-success-content": "oklch(26% 0.051 172.552)", "--color-warning": "oklch(79% 0.184 86.047)", "--color-warning-content": "oklch(28% 0.066 53.813)", "--color-error": "oklch(64% 0.246 16.439)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "0" }, cmyk: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(95% 0 0)", "--color-base-300": "oklch(90% 0 0)", "--color-base-content": "oklch(20% 0 0)", "--color-primary": "oklch(71.772% 0.133 239.443)", "--color-primary-content": "oklch(14.354% 0.026 239.443)", "--color-secondary": "oklch(64.476% 0.202 359.339)", "--color-secondary-content": "oklch(12.895% 0.04 359.339)", "--color-accent": "oklch(94.228% 0.189 105.306)", "--color-accent-content": "oklch(18.845% 0.037 105.306)", "--color-neutral": "oklch(21.778% 0 0)", "--color-neutral-content": "oklch(84.355% 0 0)", "--color-info": "oklch(68.475% 0.094 217.284)", "--color-info-content": "oklch(13.695% 0.018 217.284)", "--color-success": "oklch(46.949% 0.162 321.406)", "--color-success-content": "oklch(89.389% 0.032 321.406)", "--color-warning": "oklch(71.236% 0.159 52.023)", "--color-warning-content": "oklch(14.247% 0.031 52.023)", "--color-error": "oklch(62.013% 0.208 28.717)", "--color-error-content": "oklch(12.402% 0.041 28.717)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, business: { "color-scheme": "dark", "--color-base-100": "oklch(24.353% 0 0)", "--color-base-200": "oklch(22.648% 0 0)", "--color-base-300": "oklch(20.944% 0 0)", "--color-base-content": "oklch(84.87% 0 0)", "--color-primary": "oklch(41.703% 0.099 251.473)", "--color-primary-content": "oklch(88.34% 0.019 251.473)", "--color-secondary": "oklch(64.092% 0.027 229.389)", "--color-secondary-content": "oklch(12.818% 0.005 229.389)", "--color-accent": "oklch(67.271% 0.167 35.791)", "--color-accent-content": "oklch(13.454% 0.033 35.791)", "--color-neutral": "oklch(27.441% 0.013 253.041)", "--color-neutral-content": "oklch(85.488% 0.002 253.041)", "--color-info": "oklch(62.616% 0.143 240.033)", "--color-info-content": "oklch(12.523% 0.028 240.033)", "--color-success": "oklch(70.226% 0.094 156.596)", "--color-success-content": "oklch(14.045% 0.018 156.596)", "--color-warning": "oklch(77.482% 0.115 81.519)", "--color-warning-content": "oklch(15.496% 0.023 81.519)", "--color-error": "oklch(51.61% 0.146 29.674)", "--color-error-content": "oklch(90.322% 0.029 29.674)", "--radius-selector": "0rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, winter: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(97.466% 0.011 259.822)", "--color-base-300": "oklch(93.268% 0.016 262.751)", "--color-base-content": "oklch(41.886% 0.053 255.824)", "--color-primary": "oklch(56.86% 0.255 257.57)", "--color-primary-content": "oklch(91.372% 0.051 257.57)", "--color-secondary": "oklch(42.551% 0.161 282.339)", "--color-secondary-content": "oklch(88.51% 0.032 282.339)", "--color-accent": "oklch(59.939% 0.191 335.171)", "--color-accent-content": "oklch(11.988% 0.038 335.171)", "--color-neutral": "oklch(19.616% 0.063 257.651)", "--color-neutral-content": "oklch(83.923% 0.012 257.651)", "--color-info": "oklch(88.127% 0.085 214.515)", "--color-info-content": "oklch(17.625% 0.017 214.515)", "--color-success": "oklch(80.494% 0.077 197.823)", "--color-success-content": "oklch(16.098% 0.015 197.823)", "--color-warning": "oklch(89.172% 0.045 71.47)", "--color-warning-content": "oklch(17.834% 0.009 71.47)", "--color-error": "oklch(73.092% 0.11 20.076)", "--color-error-content": "oklch(14.618% 0.022 20.076)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, halloween: { "color-scheme": "dark", "--color-base-100": "oklch(21% 0.006 56.043)", "--color-base-200": "oklch(14% 0.004 49.25)", "--color-base-300": "oklch(0% 0 0)", "--color-base-content": "oklch(84.955% 0 0)", "--color-primary": "oklch(77.48% 0.204 60.62)", "--color-primary-content": "oklch(19.693% 0.004 196.779)", "--color-secondary": "oklch(45.98% 0.248 305.03)", "--color-secondary-content": "oklch(89.196% 0.049 305.03)", "--color-accent": "oklch(64.8% 0.223 136.073)", "--color-accent-content": "oklch(0% 0 0)", "--color-neutral": "oklch(24.371% 0.046 65.681)", "--color-neutral-content": "oklch(84.874% 0.009 65.681)", "--color-info": "oklch(54.615% 0.215 262.88)", "--color-info-content": "oklch(90.923% 0.043 262.88)", "--color-success": "oklch(62.705% 0.169 149.213)", "--color-success-content": "oklch(12.541% 0.033 149.213)", "--color-warning": "oklch(66.584% 0.157 58.318)", "--color-warning-content": "oklch(13.316% 0.031 58.318)", "--color-error": "oklch(65.72% 0.199 27.33)", "--color-error-content": "oklch(13.144% 0.039 27.33)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, fantasy: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(93% 0 0)", "--color-base-300": "oklch(86% 0 0)", "--color-base-content": "oklch(27.807% 0.029 256.847)", "--color-primary": "oklch(37.45% 0.189 325.02)", "--color-primary-content": "oklch(87.49% 0.037 325.02)", "--color-secondary": "oklch(53.92% 0.162 241.36)", "--color-secondary-content": "oklch(90.784% 0.032 241.36)", "--color-accent": "oklch(75.98% 0.204 56.72)", "--color-accent-content": "oklch(15.196% 0.04 56.72)", "--color-neutral": "oklch(27.807% 0.029 256.847)", "--color-neutral-content": "oklch(85.561% 0.005 256.847)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, wireframe: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(97% 0 0)", "--color-base-300": "oklch(94% 0 0)", "--color-base-content": "oklch(20% 0 0)", "--color-primary": "oklch(87% 0 0)", "--color-primary-content": "oklch(26% 0 0)", "--color-secondary": "oklch(87% 0 0)", "--color-secondary-content": "oklch(26% 0 0)", "--color-accent": "oklch(87% 0 0)", "--color-accent-content": "oklch(26% 0 0)", "--color-neutral": "oklch(87% 0 0)", "--color-neutral-content": "oklch(26% 0 0)", "--color-info": "oklch(44% 0.11 240.79)", "--color-info-content": "oklch(90% 0.058 230.902)", "--color-success": "oklch(43% 0.095 166.913)", "--color-success-content": "oklch(90% 0.093 164.15)", "--color-warning": "oklch(47% 0.137 46.201)", "--color-warning-content": "oklch(92% 0.12 95.746)", "--color-error": "oklch(44% 0.177 26.899)", "--color-error-content": "oklch(88% 0.062 18.334)", "--radius-selector": "0rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" } }; + +// packages/daisyui/base/rootscrolllock/object.js +var object_default2 = { ':root:has( .modal-open, .modal[open], .modal:target, .modal-toggle:checked, .drawer:not([class*="drawer-open"]) > .drawer-toggle:checked )': { overflow: "hidden" } }; + +// packages/daisyui/functions/addPrefix.js +var defaultExcludedPrefixes = ["color-", "size-", "radius-", "border", "depth", "noise"]; +var shouldExcludeVariable = (variableName, excludedPrefixes) => { + if (variableName.startsWith("tw")) { + return true; + } + return excludedPrefixes.some((excludedPrefix) => variableName.startsWith(excludedPrefix)); +}; +var prefixVariable = (variableName, prefix, excludedPrefixes) => { + if (shouldExcludeVariable(variableName, excludedPrefixes)) { + return variableName; + } + return `${prefix}${variableName}`; +}; +var getPrefixedSelector = (selector, prefix) => { + if (!selector.startsWith(".")) + return selector; + return `.${prefix}${selector.slice(1)}`; +}; +var getPrefixedKey = (key, prefix, excludedPrefixes) => { + const prefixAmpDot = prefix ? `&.${prefix}` : ""; + if (!prefix) + return key; + if (key.startsWith("--")) { + const variableName = key.slice(2); + return `--${prefixVariable(variableName, prefix, excludedPrefixes)}`; + } + if (key.startsWith("@") || key.startsWith("[")) { + return key; + } + if (key.startsWith("&")) { + if (key.match(/:[a-z-]+\(/)) { + return key.replace(/\.([\w-]+)/g, `.${prefix}$1`); + } + if (key.startsWith("&.")) { + return `${prefixAmpDot}${key.slice(2)}`; + } + return key.replace(/\.([\w-]+)/g, `.${prefix}$1`); + } + if (key.startsWith(":")) { + return key.replace(/\.([\w-]+)/g, `.${prefix}$1`); + } + if (key.includes(".") && !key.includes(" ") && !key.includes(">") && !key.includes("+") && !key.includes("~")) { + return key.split(".").filter(Boolean).map((part) => prefix + part).join(".").replace(/^/, "."); + } + if (key.includes(">") || key.includes("+") || key.includes("~")) { + if (key.includes(",")) { + return key.split(/\s*,\s*/).map((part) => { + return part.replace(/\.([\w-]+)/g, `.${prefix}$1`); + }).join(", "); + } + let processedKey = key.replace(/\.([\w-]+)/g, `.${prefix}$1`); + if (processedKey.startsWith(">") || processedKey.startsWith("+") || processedKey.startsWith("~")) { + processedKey = ` ${processedKey}`; + } + return processedKey; + } + if (key.includes(" ")) { + return key.split(/\s+/).map((part) => { + if (part.startsWith(".")) { + return getPrefixedSelector(part, prefix); + } + return part; + }).join(" "); + } + if (key.includes(":")) { + const [selector, ...pseudo] = key.split(":"); + if (selector.startsWith(".")) { + return `${getPrefixedSelector(selector, prefix)}:${pseudo.join(":")}`; + } + return key.replace(/\.([\w-]+)/g, `.${prefix}$1`); + } + if (key.startsWith(".")) { + return getPrefixedSelector(key, prefix); + } + return key; +}; +var processArrayValue = (value, prefix, excludedPrefixes) => { + return value.map((item) => { + if (typeof item === "string") { + if (item.startsWith(".")) { + return prefix ? `.${prefix}${item.slice(1)}` : item; + } + return processStringValue(item, prefix, excludedPrefixes); + } + return item; + }); +}; +var processStringValue = (value, prefix, excludedPrefixes) => { + if (prefix === 0) + return value; + return value.replace(/var\(--([^)]+)\)/g, (match, variableName) => { + if (shouldExcludeVariable(variableName, excludedPrefixes)) { + return match; + } + return `var(--${prefix}${variableName})`; + }); +}; +var processValue = (value, prefix, excludedPrefixes) => { + if (Array.isArray(value)) { + return processArrayValue(value, prefix, excludedPrefixes); + } else if (typeof value === "object" && value !== null) { + return addPrefix(value, prefix, excludedPrefixes); + } else if (typeof value === "string") { + return processStringValue(value, prefix, excludedPrefixes); + } else { + return value; + } +}; +var addPrefix = (obj, prefix, excludedPrefixes = defaultExcludedPrefixes) => { + return Object.entries(obj).reduce((result, [key, value]) => { + const newKey = getPrefixedKey(key, prefix, excludedPrefixes); + result[newKey] = processValue(value, prefix, excludedPrefixes); + return result; + }, {}); +}; + +// packages/daisyui/base/rootscrolllock/index.js +var rootscrolllock_default = ({ addBase, prefix = "" }) => { + const prefixedrootscrolllock = addPrefix(object_default2, prefix); + addBase({ ...prefixedrootscrolllock }); +}; + +// packages/daisyui/base/rootcolor/object.js +var object_default3 = { ":root, [data-theme]": { "background-color": "var(--root-bg, var(--color-base-100))", color: "var(--color-base-content)" } }; + +// packages/daisyui/base/rootcolor/index.js +var rootcolor_default = ({ addBase, prefix = "" }) => { + const prefixedrootcolor = addPrefix(object_default3, prefix); + addBase({ ...prefixedrootcolor }); +}; + +// packages/daisyui/base/scrollbar/object.js +var object_default4 = { ":root": { "scrollbar-color": "color-mix(in oklch, currentColor 35%, #0000) #0000" } }; + +// packages/daisyui/base/scrollbar/index.js +var scrollbar_default = ({ addBase, prefix = "" }) => { + const prefixedscrollbar = addPrefix(object_default4, prefix); + addBase({ ...prefixedscrollbar }); +}; + +// packages/daisyui/base/properties/object.js +var object_default5 = { "@property --radialprogress": { syntax: '""', inherits: "true", "initial-value": "0%" } }; + +// packages/daisyui/base/properties/index.js +var properties_default = ({ addBase, prefix = "" }) => { + const prefixedproperties = addPrefix(object_default5, prefix); + addBase({ ...prefixedproperties }); +}; + +// packages/daisyui/base/rootscrollgutter/object.js +var object_default6 = { ":where( :root:has( .modal-open, .modal[open], .modal:target, .modal-toggle:checked, .drawer:not(.drawer-open) > .drawer-toggle:checked ) )": { "scrollbar-gutter": "stable", "background-image": "linear-gradient(var(--color-base-100), var(--color-base-100))", "--root-bg": "color-mix(in srgb, var(--color-base-100), oklch(0% 0 0) 40%)" }, ":where(.modal[open], .modal-open, .modal-toggle:checked + .modal):not(.modal-start, .modal-end)": { "scrollbar-gutter": "stable" } }; + +// packages/daisyui/base/rootscrollgutter/index.js +var rootscrollgutter_default = ({ addBase, prefix = "" }) => { + const prefixedrootscrollgutter = addPrefix(object_default6, prefix); + addBase({ ...prefixedrootscrollgutter }); +}; + +// packages/daisyui/base/svg/object.js +var object_default7 = { ":root": { "--fx-noise": `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='a'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.34' numOctaves='4' stitchTiles='stitch'%3E%3C/feTurbulence%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23a)' opacity='0.2'%3E%3C/rect%3E%3C/svg%3E")` }, ".chat": { "--mask-chat": `url("data:image/svg+xml,%3csvg width='13' height='13' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='M0 11.5004C0 13.0004 2 13.0004 2 13.0004H12H13V0.00036329L12.5 0C12.5 0 11.977 2.09572 11.8581 2.50033C11.6075 3.35237 10.9149 4.22374 9 5.50036C6 7.50036 0 10.0004 0 11.5004Z'/%3e%3c/svg%3e")` } }; + +// packages/daisyui/base/svg/index.js +var svg_default = ({ addBase, prefix = "" }) => { + const prefixedsvg = addPrefix(object_default7, prefix); + addBase({ ...prefixedsvg }); +}; + +// packages/daisyui/components/drawer/object.js +var object_default8 = { ".drawer": { position: "relative", display: "grid", width: "100%", "grid-auto-columns": "max-content auto" }, ".drawer-content": { "grid-column-start": "2", "grid-row-start": "1", "min-width": "calc(0.25rem * 0)" }, ".drawer-side": { "pointer-events": "none", visibility: "hidden", position: "fixed", "inset-inline-start": "calc(0.25rem * 0)", top: "calc(0.25rem * 0)", "z-index": 1, "grid-column-start": "1", "grid-row-start": "1", display: "grid", width: "100%", "grid-template-columns": "repeat(1, minmax(0, 1fr))", "grid-template-rows": "repeat(1, minmax(0, 1fr))", "align-items": "flex-start", "justify-items": "start", "overflow-x": "hidden", "overflow-y": "hidden", "overscroll-behavior": "contain", opacity: "0%", transition: "opacity 0.2s ease-out 0.1s allow-discrete, visibility 0.3s ease-out 0.1s allow-discrete", height: ["100vh", "100dvh"], "> .drawer-overlay": { position: "sticky", top: "calc(0.25rem * 0)", cursor: "pointer", "place-self": "stretch", "background-color": "oklch(0% 0 0 / 40%)" }, "> *": { "grid-column-start": "1", "grid-row-start": "1" }, "> *:not(.drawer-overlay)": { "will-change": "transform", transition: "translate 0.3s ease-out", translate: "-100%", '[dir="rtl"] &': { translate: "100%" } } }, ".drawer-toggle": { position: "fixed", height: "calc(0.25rem * 0)", width: "calc(0.25rem * 0)", appearance: "none", opacity: "0%", "&:checked": { "& ~ .drawer-side": { "pointer-events": "auto", visibility: "visible", "overflow-y": "auto", opacity: "100%", "& > *:not(.drawer-overlay)": { translate: "0%" } } }, "&:focus-visible ~ .drawer-content label.drawer-button": { outline: "2px solid", "outline-offset": "2px" } }, ".drawer-end": { "grid-auto-columns": "auto max-content", "> .drawer-toggle": { "& ~ .drawer-content": { "grid-column-start": "1" }, "& ~ .drawer-side": { "grid-column-start": "2", "justify-items": "end" }, "& ~ .drawer-side > *:not(.drawer-overlay)": { translate: "100%", '[dir="rtl"] &': { translate: "-100%" } }, "&:checked ~ .drawer-side > *:not(.drawer-overlay)": { translate: "0%" } } }, ".drawer-open": { "> .drawer-side": { "overflow-y": "auto" }, "> .drawer-toggle": { display: "none", "& ~ .drawer-side": { "pointer-events": "auto", visibility: "visible", position: "sticky", display: "block", width: "auto", "overscroll-behavior": "auto", opacity: "100%", "& > .drawer-overlay": { cursor: "default", "background-color": "transparent" }, "& > *:not(.drawer-overlay)": { translate: "0%", '[dir="rtl"] &': { translate: "0%" } } }, "&:checked ~ .drawer-side": { "pointer-events": "auto", visibility: "visible" } } } }; + +// packages/daisyui/components/drawer/index.js +var drawer_default = ({ addComponents, prefix = "" }) => { + const prefixeddrawer = addPrefix(object_default8, prefix); + addComponents({ ...prefixeddrawer }); +}; + +// packages/daisyui/components/link/object.js +var object_default9 = { ".link": { cursor: "pointer", "text-decoration-line": "underline", "&:focus": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, "&:focus-visible": { outline: "2px solid currentColor", "outline-offset": "2px" } }, ".link-hover": { "text-decoration-line": "none", "&:hover": { "@media (hover: hover)": { "text-decoration-line": "underline" } } }, ".link-primary": { color: "var(--color-primary)", "@media (hover: hover)": { "&:hover": { color: "color-mix(in oklab, var(--color-primary) 80%, #000)" } } }, ".link-secondary": { color: "var(--color-secondary)", "@media (hover: hover)": { "&:hover": { color: "color-mix(in oklab, var(--color-secondary) 80%, #000)" } } }, ".link-accent": { color: "var(--color-accent)", "@media (hover: hover)": { "&:hover": { color: "color-mix(in oklab, var(--color-accent) 80%, #000)" } } }, ".link-neutral": { color: "var(--color-neutral)", "@media (hover: hover)": { "&:hover": { color: "color-mix(in oklab, var(--color-neutral) 80%, #000)" } } }, ".link-success": { color: "var(--color-success)", "@media (hover: hover)": { "&:hover": { color: "color-mix(in oklab, var(--color-success) 80%, #000)" } } }, ".link-info": { color: "var(--color-info)", "@media (hover: hover)": { "&:hover": { color: "color-mix(in oklab, var(--color-info) 80%, #000)" } } }, ".link-warning": { color: "var(--color-warning)", "@media (hover: hover)": { "&:hover": { color: "color-mix(in oklab, var(--color-warning) 80%, #000)" } } }, ".link-error": { color: "var(--color-error)", "@media (hover: hover)": { "&:hover": { color: "color-mix(in oklab, var(--color-error) 80%, #000)" } } } }; + +// packages/daisyui/components/link/index.js +var link_default = ({ addComponents, prefix = "" }) => { + const prefixedlink = addPrefix(object_default9, prefix); + addComponents({ ...prefixedlink }); +}; + +// packages/daisyui/components/stat/object.js +var object_default10 = { ".stats": { position: "relative", display: "inline-grid", "grid-auto-flow": "column", "overflow-x": "auto", "border-radius": "var(--radius-box)" }, ".stat": { display: "inline-grid", width: "100%", "column-gap": "calc(0.25rem * 4)", "padding-inline": "calc(0.25rem * 6)", "padding-block": "calc(0.25rem * 4)", "grid-template-columns": "repeat(1, 1fr)", "&:not(:last-child)": { "border-inline-end": "var(--border) dashed color-mix(in oklab, currentColor 10%, #0000)", "border-block-end": "none" } }, ".stat-figure": { "grid-column-start": "2", "grid-row": "span 3 / span 3", "grid-row-start": "1", "place-self": "center", "justify-self": "flex-end" }, ".stat-title": { "grid-column-start": "1", "white-space": "nowrap", color: "color-mix(in oklab, var(--color-base-content) 60%, transparent)", "font-size": "0.75rem" }, ".stat-value": { "grid-column-start": "1", "white-space": "nowrap", "font-size": "2rem", "font-weight": 800 }, ".stat-desc": { "grid-column-start": "1", "white-space": "nowrap", color: "color-mix(in oklab, var(--color-base-content) 60%, transparent)", "font-size": "0.75rem" }, ".stat-actions": { "grid-column-start": "1", "white-space": "nowrap" }, ".stats-horizontal": { "grid-auto-flow": "column", "overflow-x": "auto", ".stat:not(:last-child)": { "border-inline-end": "var(--border) dashed color-mix(in oklab, currentColor 10%, #0000)", "border-block-end": "none" } }, ".stats-vertical": { "grid-auto-flow": "row", "overflow-y": "auto", ".stat:not(:last-child)": { "border-inline-end": "none", "border-block-end": "var(--border) dashed color-mix(in oklab, currentColor 10%, #0000)" } } }; + +// packages/daisyui/components/stat/index.js +var stat_default = ({ addComponents, prefix = "" }) => { + const prefixedstat = addPrefix(object_default10, prefix); + addComponents({ ...prefixedstat }); +}; + +// packages/daisyui/components/carousel/object.js +var object_default11 = { ".carousel": { display: "inline-flex", "overflow-x": "scroll", "scroll-snap-type": "x mandatory", "scroll-behavior": "smooth", "scrollbar-width": "none", "&::-webkit-scrollbar": { display: "none" } }, ".carousel-vertical": { "flex-direction": "column", "overflow-y": "scroll", "scroll-snap-type": "y mandatory" }, ".carousel-horizontal": { "flex-direction": "row", "overflow-x": "scroll", "scroll-snap-type": "x mandatory" }, ".carousel-item": { "box-sizing": "content-box", display: "flex", flex: "none", "scroll-snap-align": "start" }, ".carousel-start": { ".carousel-item": { "scroll-snap-align": "start" } }, ".carousel-center": { ".carousel-item": { "scroll-snap-align": "center" } }, ".carousel-end": { ".carousel-item": { "scroll-snap-align": "end" } } }; + +// packages/daisyui/components/carousel/index.js +var carousel_default = ({ addComponents, prefix = "" }) => { + const prefixedcarousel = addPrefix(object_default11, prefix); + addComponents({ ...prefixedcarousel }); +}; + +// packages/daisyui/components/divider/object.js +var object_default12 = { ".divider": { display: "flex", height: "calc(0.25rem * 4)", "flex-direction": "row", "align-items": "center", "align-self": "stretch", "white-space": "nowrap", margin: "var(--divider-m, 1rem 0)", "--divider-color": "color-mix(in oklab, var(--color-base-content) 10%, transparent)", "&:before, &:after": { content: '""', height: "calc(0.25rem * 0.5)", width: "100%", "flex-grow": 1, "background-color": "var(--divider-color)" }, "@media print": { "&:before, &:after": { border: "0.5px solid" } }, "&:not(:empty)": { gap: "calc(0.25rem * 4)" } }, ".divider-horizontal": { "--divider-m": "0 1rem", "&.divider": { height: "auto", width: "calc(0.25rem * 4)", "flex-direction": "column", "&:before": { height: "100%", width: "calc(0.25rem * 0.5)" }, "&:after": { height: "100%", width: "calc(0.25rem * 0.5)" } } }, ".divider-vertical": { "--divider-m": "1rem 0", "&.divider": { height: "calc(0.25rem * 4)", width: "auto", "flex-direction": "row", "&:before": { height: "calc(0.25rem * 0.5)", width: "100%" }, "&:after": { height: "calc(0.25rem * 0.5)", width: "100%" } } }, ".divider-neutral": { "&:before, &:after": { "background-color": "var(--color-neutral)" } }, ".divider-primary": { "&:before, &:after": { "background-color": "var(--color-primary)" } }, ".divider-secondary": { "&:before, &:after": { "background-color": "var(--color-secondary)" } }, ".divider-accent": { "&:before, &:after": { "background-color": "var(--color-accent)" } }, ".divider-success": { "&:before, &:after": { "background-color": "var(--color-success)" } }, ".divider-warning": { "&:before, &:after": { "background-color": "var(--color-warning)" } }, ".divider-info": { "&:before, &:after": { "background-color": "var(--color-info)" } }, ".divider-error": { "&:before, &:after": { "background-color": "var(--color-error)" } }, ".divider-start:before": { display: "none" }, ".divider-end:after": { display: "none" } }; + +// packages/daisyui/components/divider/index.js +var divider_default = ({ addComponents, prefix = "" }) => { + const prefixeddivider = addPrefix(object_default12, prefix); + addComponents({ ...prefixeddivider }); +}; + +// packages/daisyui/components/mask/object.js +var object_default13 = { ".mask": { display: "inline-block", "vertical-align": "middle", "mask-size": "contain", "mask-repeat": "no-repeat", "mask-position": "center" }, ".mask-half-1": { "mask-size": "200%", "mask-position": ["left", "left"], '&:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *)': { "mask-position": "right" } }, ".mask-half-2": { "mask-size": "200%", "mask-position": ["right", "right"], '&:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *)': { "mask-position": "left" } }, ".mask-squircle": { "mask-image": `url("data:image/svg+xml,%3csvg width='200' height='200' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M100 0C20 0 0 20 0 100s20 100 100 100 100-20 100-100S180 0 100 0Z'/%3e%3c/svg%3e")` }, ".mask-decagon": { "mask-image": `url("data:image/svg+xml,%3csvg width='192' height='200' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m96 0 58.779 19.098 36.327 50v61.804l-36.327 50L96 200l-58.779-19.098-36.327-50V69.098l36.327-50z' fill-rule='evenodd'/%3e%3c/svg%3e")` }, ".mask-diamond": { "mask-image": `url("data:image/svg+xml,%3csvg width='200' height='200' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m100 0 100 100-100 100L0 100z' fill-rule='evenodd'/%3e%3c/svg%3e")` }, ".mask-heart": { "mask-image": `url("data:image/svg+xml,%3csvg width='200' height='185' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M100 184.606a15.384 15.384 0 0 1-8.653-2.678C53.565 156.28 37.205 138.695 28.182 127.7 8.952 104.264-.254 80.202.005 54.146.308 24.287 24.264 0 53.406 0c21.192 0 35.869 11.937 44.416 21.879a2.884 2.884 0 0 0 4.356 0C110.725 11.927 125.402 0 146.594 0c29.142 0 53.098 24.287 53.4 54.151.26 26.061-8.956 50.122-28.176 73.554-9.023 10.994-25.383 28.58-63.165 54.228a15.384 15.384 0 0 1-8.653 2.673Z' fill='black' fill-rule='nonzero'/%3e%3c/svg%3e")` }, ".mask-hexagon": { "mask-image": `url("data:image/svg+xml,%3csvg width='182' height='201' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M.3 65.486c0-9.196 6.687-20.063 14.211-25.078l61.86-35.946c8.36-5.016 20.899-5.016 29.258 0l61.86 35.946c8.36 5.015 14.211 15.882 14.211 25.078v71.055c0 9.196-6.687 20.063-14.211 25.079l-61.86 35.945c-8.36 4.18-20.899 4.18-29.258 0L14.51 161.62C6.151 157.44.3 145.737.3 136.54V65.486Z' fill='black' fill-rule='nonzero'/%3e%3c/svg%3e")` }, ".mask-hexagon-2": { "mask-image": `url("data:image/svg+xml,%3csvg width='200' height='182' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M64.786 181.4c-9.196 0-20.063-6.687-25.079-14.21L3.762 105.33c-5.016-8.36-5.016-20.9 0-29.259l35.945-61.86C44.723 5.851 55.59 0 64.786 0h71.055c9.196 0 20.063 6.688 25.079 14.211l35.945 61.86c4.18 8.36 4.18 20.899 0 29.258l-35.945 61.86c-4.18 8.36-15.883 14.211-25.079 14.211H64.786Z' fill='black' fill-rule='nonzero'/%3e%3c/svg%3e")` }, ".mask-circle": { "mask-image": `url("data:image/svg+xml,%3csvg width='200' height='200' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle fill='black' cx='100' cy='100' r='100' fill-rule='evenodd'/%3e%3c/svg%3e")` }, ".mask-pentagon": { "mask-image": `url("data:image/svg+xml,%3csvg width='192' height='181' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m96 0 95.106 69.098-36.327 111.804H37.22L.894 69.098z' fill-rule='evenodd'/%3e%3c/svg%3e")` }, ".mask-star": { "mask-image": `url("data:image/svg+xml,%3csvg width='192' height='180' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m96 137.263-58.779 42.024 22.163-68.389L.894 68.481l72.476-.243L96 0l22.63 68.238 72.476.243-58.49 42.417 22.163 68.389z' fill-rule='evenodd'/%3e%3c/svg%3e")` }, ".mask-star-2": { "mask-image": `url("data:image/svg+xml,%3csvg width='192' height='180' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m96 153.044-58.779 26.243 7.02-63.513L.894 68.481l63.117-13.01L96 0l31.989 55.472 63.117 13.01-43.347 47.292 7.02 63.513z' fill-rule='evenodd'/%3e%3c/svg%3e")` }, ".mask-triangle": { "mask-image": `url("data:image/svg+xml,%3csvg width='174' height='149' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m87 148.476-86.603.185L43.86 74.423 87 0l43.14 74.423 43.463 74.238z' fill-rule='evenodd'/%3e%3c/svg%3e")` }, ".mask-triangle-2": { "mask-image": `url("data:image/svg+xml,%3csvg width='174' height='150' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m87 .738 86.603-.184-43.463 74.238L87 149.214 43.86 74.792.397.554z' fill-rule='evenodd'/%3e%3c/svg%3e")` }, ".mask-triangle-3": { "mask-image": `url("data:image/svg+xml,%3csvg width='150' height='174' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m149.369 87.107.185 86.603-74.239-43.463L.893 87.107l74.422-43.14L149.554.505z' fill-rule='evenodd'/%3e%3c/svg%3e")` }, ".mask-triangle-4": { "mask-image": `url("data:image/svg+xml,%3csvg width='150' height='174' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='M.631 87.107.446.505l74.239 43.462 74.422 43.14-74.422 43.14L.446 173.71z' fill-rule='evenodd'/%3e%3c/svg%3e")` } }; + +// packages/daisyui/components/mask/index.js +var mask_default = ({ addComponents, prefix = "" }) => { + const prefixedmask = addPrefix(object_default13, prefix); + addComponents({ ...prefixedmask }); +}; + +// packages/daisyui/components/fieldset/object.js +var object_default14 = { ".fieldset": { display: "grid", gap: "calc(0.25rem * 1.5)", "padding-block": "calc(0.25rem * 1)", "font-size": "0.75rem", "grid-template-columns": "1fr", "grid-auto-rows": "max-content" }, ".fieldset-legend": { "margin-bottom": "calc(0.25rem * -1)", display: "flex", "align-items": "center", "justify-content": "space-between", gap: "calc(0.25rem * 2)", "padding-block": "calc(0.25rem * 2)", color: "var(--color-base-content)", "font-weight": 600 }, ".fieldset-label": { display: "flex", "align-items": "center", gap: "calc(0.25rem * 1.5)", color: "color-mix(in oklab, var(--color-base-content) 60%, transparent)", "&:has(input)": { cursor: "pointer" } } }; + +// packages/daisyui/components/fieldset/index.js +var fieldset_default = ({ addComponents, prefix = "" }) => { + const prefixedfieldset = addPrefix(object_default14, prefix); + addComponents({ ...prefixedfieldset }); +}; + +// packages/daisyui/components/dropdown/object.js +var object_default15 = { ".dropdown": { position: "relative", display: "inline-block", "position-area": "var(--anchor-v, bottom) var(--anchor-h, span-right)", "& > *:not(summary):focus": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, ".dropdown-content": { position: "absolute" }, "&:not(details, .dropdown-open, .dropdown-hover:hover, :focus-within)": { ".dropdown-content": { display: "none", "transform-origin": "top", opacity: "0%", scale: "95%" } }, "&[popover], .dropdown-content": { "z-index": 999, animation: "dropdown 0.2s", "transition-property": "opacity, scale, display", "transition-behavior": "allow-discrete", "transition-duration": "0.2s", "transition-timing-function": "cubic-bezier(0.4, 0, 0.2, 1)" }, "@starting-style": { "&[popover], .dropdown-content": { scale: "95%", opacity: 0 } }, "&.dropdown-open, &:not(.dropdown-hover):focus, &:focus-within": { "> [tabindex]:first-child": { "pointer-events": "none" }, ".dropdown-content": { opacity: "100%" } }, "&.dropdown-hover:hover": { ".dropdown-content": { opacity: "100%", scale: "100%" } }, "&:is(details)": { summary: { "&::-webkit-details-marker": { display: "none" } } }, "&.dropdown-open, &:focus, &:focus-within": { ".dropdown-content": { scale: "100%" } }, "&:where([popover])": { background: "#0000" }, "&[popover]": { position: "fixed", color: "inherit", "@supports not (position-area: bottom)": { margin: "auto", "&.dropdown-open:not(:popover-open)": { display: "none", "transform-origin": "top", opacity: "0%", scale: "95%" }, "&::backdrop": { "background-color": "color-mix(in oklab, #000 30%, #0000)" } }, "&:not(.dropdown-open, :popover-open)": { display: "none", "transform-origin": "top", opacity: "0%", scale: "95%" } } }, ".dropdown-start": { "--anchor-h": "span-right", ":where(.dropdown-content)": { "inset-inline-end": "auto" }, "&.dropdown-left": { "--anchor-h": "left", "--anchor-v": "span-bottom", ".dropdown-content": { top: "calc(0.25rem * 0)", bottom: "auto" } }, "&.dropdown-right": { "--anchor-h": "right", "--anchor-v": "span-bottom", ".dropdown-content": { top: "calc(0.25rem * 0)", bottom: "auto" } } }, ".dropdown-center": { "--anchor-h": "center", ":where(.dropdown-content)": { "inset-inline-end": "calc(1/2 * 100%)", translate: "50% 0", '[dir="rtl"] &': { translate: "-50% 0" } }, "&.dropdown-left": { "--anchor-h": "left", "--anchor-v": "center", ".dropdown-content": { top: "auto", bottom: "calc(1/2 * 100%)", translate: "0 50%" } }, "&.dropdown-right": { "--anchor-h": "right", "--anchor-v": "center", ".dropdown-content": { top: "auto", bottom: "calc(1/2 * 100%)", translate: "0 50%" } } }, ".dropdown-end": { "--anchor-h": "span-left", ":where(.dropdown-content)": { "inset-inline-end": "calc(0.25rem * 0)", translate: "0 0" }, "&.dropdown-left": { "--anchor-h": "left", "--anchor-v": "span-top", ".dropdown-content": { top: "auto", bottom: "calc(0.25rem * 0)" } }, "&.dropdown-right": { "--anchor-h": "right", "--anchor-v": "span-top", ".dropdown-content": { top: "auto", bottom: "calc(0.25rem * 0)" } } }, ".dropdown-left": { "--anchor-h": "left", "--anchor-v": "span-bottom", ".dropdown-content": { "inset-inline-end": "100%", top: "calc(0.25rem * 0)", bottom: "auto", "transform-origin": "right" } }, ".dropdown-right": { "--anchor-h": "right", "--anchor-v": "span-bottom", ".dropdown-content": { "inset-inline-start": "100%", top: "calc(0.25rem * 0)", bottom: "auto", "transform-origin": "left" } }, ".dropdown-bottom": { "--anchor-v": "bottom", ".dropdown-content": { top: "100%", bottom: "auto", "transform-origin": "top" } }, ".dropdown-top": { "--anchor-v": "top", ".dropdown-content": { top: "auto", bottom: "100%", "transform-origin": "bottom" } }, "@keyframes dropdown": { "0%": { opacity: 0 } } }; + +// packages/daisyui/components/dropdown/index.js +var dropdown_default = ({ addComponents, prefix = "" }) => { + const prefixeddropdown = addPrefix(object_default15, prefix); + addComponents({ ...prefixeddropdown }); +}; + +// packages/daisyui/components/card/object.js +var object_default16 = { ".card": { position: "relative", display: "flex", "flex-direction": "column", "border-radius": "var(--radius-box)", "outline-width": "2px", transition: "outline 0.2s ease-in-out", outline: "0 solid #0000", "outline-offset": "2px", "&:focus": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, "&:focus-visible": { "outline-color": "currentColor" }, ":where(figure:first-child)": { overflow: "hidden", "border-start-start-radius": "inherit", "border-start-end-radius": "inherit", "border-end-start-radius": "unset", "border-end-end-radius": "unset" }, ":where(figure:last-child)": { overflow: "hidden", "border-start-start-radius": "unset", "border-start-end-radius": "unset", "border-end-start-radius": "inherit", "border-end-end-radius": "inherit" }, "&:where(.card-border)": { border: "var(--border) solid var(--color-base-200)" }, "&:where(.card-dash)": { border: "var(--border) dashed var(--color-base-200)" }, "&.image-full": { display: "grid", "> *": { "grid-column-start": "1", "grid-row-start": "1" }, "> .card-body": { position: "relative", color: "var(--color-neutral-content)" }, ":where(figure)": { overflow: "hidden", "border-radius": "inherit" }, "> figure img": { height: "100%", "object-fit": "cover", filter: "brightness(28%)" } }, figure: { display: "flex", "align-items": "center", "justify-content": "center" }, '&:has(> input:is(input[type="checkbox"], input[type="radio"]))': { cursor: "pointer", "user-select": "none" }, "&:has(> :checked)": { outline: "2px solid currentColor" } }, ".card-title": { display: "flex", "align-items": "center", gap: "calc(0.25rem * 2)", "font-size": "var(--cardtitle-fs, 1.125rem)", "font-weight": 600 }, ".card-body": { display: "flex", flex: "auto", "flex-direction": "column", gap: "calc(0.25rem * 2)", padding: "var(--card-p, 1.5rem)", "font-size": "var(--card-fs, 0.875rem)", ":where(p)": { "flex-grow": 1 } }, ".card-actions": { display: "flex", "flex-wrap": "wrap", "align-items": "flex-start", gap: "calc(0.25rem * 2)" }, ".card-xs": { ".card-body": { "--card-p": "0.5rem", "--card-fs": "0.6875rem" }, ".card-title": { "--cardtitle-fs": "0.875rem" } }, ".card-sm": { ".card-body": { "--card-p": "1rem", "--card-fs": "0.75rem" }, ".card-title": { "--cardtitle-fs": "1rem" } }, ".card-md": { ".card-body": { "--card-p": "1.5rem", "--card-fs": "0.875rem" }, ".card-title": { "--cardtitle-fs": "1.125rem" } }, ".card-lg": { ".card-body": { "--card-p": "2rem", "--card-fs": "1rem" }, ".card-title": { "--cardtitle-fs": "1.25rem" } }, ".card-xl": { ".card-body": { "--card-p": "2.5rem", "--card-fs": "1.125rem" }, ".card-title": { "--cardtitle-fs": "1.375rem" } }, ".card-side": { "align-items": "stretch", "flex-direction": "row", ":where(figure:first-child)": { overflow: "hidden", "border-start-start-radius": "inherit", "border-start-end-radius": "unset", "border-end-start-radius": "inherit", "border-end-end-radius": "unset" }, ":where(figure:last-child)": { overflow: "hidden", "border-start-start-radius": "unset", "border-start-end-radius": "inherit", "border-end-start-radius": "unset", "border-end-end-radius": "inherit" }, "figure > *": { "max-width": "unset" }, ":where(figure > *)": { width: "100%", height: "100%", "object-fit": "cover" } } }; + +// packages/daisyui/components/card/index.js +var card_default = ({ addComponents, prefix = "" }) => { + const prefixedcard = addPrefix(object_default16, prefix); + addComponents({ ...prefixedcard }); +}; + +// packages/daisyui/components/steps/object.js +var object_default17 = { ".steps": { display: "inline-grid", "grid-auto-flow": "column", overflow: "hidden", "overflow-x": "auto", "counter-reset": "step", "grid-auto-columns": "1fr", ".step": { display: "grid", "grid-template-columns": ["repeat(1, minmax(0, 1fr))", "auto"], "grid-template-rows": ["repeat(2, minmax(0, 1fr))", "40px 1fr"], "place-items": "center", "text-align": "center", "min-width": "4rem", "--step-bg": "var(--color-base-300)", "--step-fg": "var(--color-base-content)", "&:before": { top: "calc(0.25rem * 0)", "grid-column-start": "1", "grid-row-start": "1", height: "calc(0.25rem * 2)", width: "100%", border: "1px solid", color: "var(--step-bg)", "background-color": "var(--step-bg)", "--tw-content": '""', content: "var(--tw-content)", "margin-inline-start": "-100%" }, "> .step-icon, &:not(:has(.step-icon)):after": { content: "counter(step)", "counter-increment": "step", "z-index": 1, color: "var(--step-fg)", "background-color": "var(--step-bg)", border: "1px solid var(--step-bg)", position: "relative", "grid-column-start": "1", "grid-row-start": "1", display: "grid", height: "calc(0.25rem * 8)", width: "calc(0.25rem * 8)", "place-items": "center", "place-self": "center", "border-radius": "calc(infinity * 1px)" }, "&:first-child:before": { content: "none" }, "&[data-content]:after": { content: "attr(data-content)" } }, ".step-neutral": { "+ .step-neutral:before, &:after, > .step-icon": { "--step-bg": "var(--color-neutral)", "--step-fg": "var(--color-neutral-content)" } }, ".step-primary": { "+ .step-primary:before, &:after, > .step-icon": { "--step-bg": "var(--color-primary)", "--step-fg": "var(--color-primary-content)" } }, ".step-secondary": { "+ .step-secondary:before, &:after, > .step-icon": { "--step-bg": "var(--color-secondary)", "--step-fg": "var(--color-secondary-content)" } }, ".step-accent": { "+ .step-accent:before, &:after, > .step-icon": { "--step-bg": "var(--color-accent)", "--step-fg": "var(--color-accent-content)" } }, ".step-info": { "+ .step-info:before, &:after, > .step-icon": { "--step-bg": "var(--color-info)", "--step-fg": "var(--color-info-content)" } }, ".step-success": { "+ .step-success:before, &:after, > .step-icon": { "--step-bg": "var(--color-success)", "--step-fg": "var(--color-success-content)" } }, ".step-warning": { "+ .step-warning:before, &:after, > .step-icon": { "--step-bg": "var(--color-warning)", "--step-fg": "var(--color-warning-content)" } }, ".step-error": { "+ .step-error:before, &:after, > .step-icon": { "--step-bg": "var(--color-error)", "--step-fg": "var(--color-error-content)" } } }, ".steps-horizontal": { "grid-auto-columns": "1fr", display: "inline-grid", "grid-auto-flow": "column", overflow: "hidden", "overflow-x": "auto", ".step": { display: "grid", "grid-template-columns": ["repeat(1, minmax(0, 1fr))", "auto"], "grid-template-rows": ["repeat(2, minmax(0, 1fr))", "40px 1fr"], "place-items": "center", "text-align": "center", "min-width": "4rem", "&:before": { height: "calc(0.25rem * 2)", width: "100%", translate: "0", content: '""', "margin-inline-start": "-100%" }, '[dir="rtl"] &:before': { translate: "0" } } }, ".steps-vertical": { "grid-auto-rows": "1fr", "grid-auto-flow": "row", ".step": { display: "grid", "grid-template-columns": ["repeat(2, minmax(0, 1fr))", "40px 1fr"], "grid-template-rows": ["repeat(1, minmax(0, 1fr))", "auto"], gap: "0.5rem", "min-height": "4rem", "justify-items": "start", "&:before": { height: "100%", width: "calc(0.25rem * 2)", translate: "-50% -50%", "margin-inline-start": "50%" }, '[dir="rtl"] &:before': { translate: "50% -50%" } } } }; + +// packages/daisyui/components/steps/index.js +var steps_default = ({ addComponents, prefix = "" }) => { + const prefixedsteps = addPrefix(object_default17, prefix); + addComponents({ ...prefixedsteps }); +}; + +// packages/daisyui/components/alert/object.js +var object_default18 = { ".alert": { display: "grid", "align-items": "center", gap: "calc(0.25rem * 4)", "border-radius": "var(--radius-box)", "padding-inline": "calc(0.25rem * 4)", "padding-block": "calc(0.25rem * 3)", color: "var(--color-base-content)", "background-color": "var(--alert-color, var(--color-base-200))", "justify-content": "start", "justify-items": "start", "grid-auto-flow": "column", "grid-template-columns": "auto", "text-align": "start", border: "var(--border) solid var(--color-base-200)", "font-size": "0.875rem", "line-height": "1.25rem", "background-size": "auto, calc(var(--noise) * 100%)", "background-image": "none, var(--fx-noise)", "box-shadow": "0 3px 0 -2px oklch(100% 0 0 / calc(var(--depth) * 0.08)) inset, 0 1px color-mix( in oklab, color-mix(in oklab, #000 20%, var(--alert-color, var(--color-base-200))) calc(var(--depth) * 20%), #0000 ), 0 4px 3px -2px oklch(0% 0 0 / calc(var(--depth) * 0.08))", "&:has(:nth-child(2))": { "grid-template-columns": "auto minmax(auto, 1fr)" }, "&.alert-outline": { "background-color": "transparent", color: "var(--alert-color)", "box-shadow": "none", "background-image": "none" }, "&.alert-dash": { "background-color": "transparent", color: "var(--alert-color)", "border-style": "dashed", "box-shadow": "none", "background-image": "none" }, "&.alert-soft": { color: "var(--alert-color, var(--color-base-content))", background: "color-mix( in oklab, var(--alert-color, var(--color-base-content)) 8%, var(--color-base-100) )", "border-color": "color-mix( in oklab, var(--alert-color, var(--color-base-content)) 10%, var(--color-base-100) )", "box-shadow": "none", "background-image": "none" } }, ".alert-info": { "border-color": "var(--color-info)", color: "var(--color-info-content)", "--alert-color": "var(--color-info)" }, ".alert-success": { "border-color": "var(--color-success)", color: "var(--color-success-content)", "--alert-color": "var(--color-success)" }, ".alert-warning": { "border-color": "var(--color-warning)", color: "var(--color-warning-content)", "--alert-color": "var(--color-warning)" }, ".alert-error": { "border-color": "var(--color-error)", color: "var(--color-error-content)", "--alert-color": "var(--color-error)" }, ".alert-vertical": { "justify-content": "center", "justify-items": "center", "grid-auto-flow": "row", "grid-template-columns": "auto", "text-align": "center", "&:has(:nth-child(2))": { "grid-template-columns": "auto" } }, ".alert-horizontal": { "justify-content": "start", "justify-items": "start", "grid-auto-flow": "column", "grid-template-columns": "auto", "text-align": "start", "&:has(:nth-child(2))": { "grid-template-columns": "auto minmax(auto, 1fr)" } } }; + +// packages/daisyui/components/alert/index.js +var alert_default = ({ addComponents, prefix = "" }) => { + const prefixedalert = addPrefix(object_default18, prefix); + addComponents({ ...prefixedalert }); +}; + +// packages/daisyui/components/kbd/object.js +var object_default19 = { ".kbd": { display: "inline-flex", "align-items": "center", "justify-content": "center", "border-radius": "var(--radius-field)", "background-color": "var(--color-base-200)", "vertical-align": "middle", "padding-left": "0.5em", "padding-right": "0.5em", border: "var(--border) solid color-mix(in srgb, var(--color-base-content) 20%, #0000)", "border-bottom": "calc(var(--border) + 1px) solid color-mix(in srgb, var(--color-base-content) 20%, #0000)", "--size": "calc(var(--size-selector, 0.25rem) * 6)", "font-size": "0.875rem", height: "var(--size)", "min-width": "var(--size)" }, ".kbd-xs": { "--size": "calc(var(--size-selector, 0.25rem) * 4)", "font-size": "0.625rem" }, ".kbd-sm": { "--size": "calc(var(--size-selector, 0.25rem) * 5)", "font-size": "0.75rem" }, ".kbd-md": { "--size": "calc(var(--size-selector, 0.25rem) * 6)", "font-size": "0.875rem" }, ".kbd-lg": { "--size": "calc(var(--size-selector, 0.25rem) * 7)", "font-size": "1rem" }, ".kbd-xl": { "--size": "calc(var(--size-selector, 0.25rem) * 8)", "font-size": "1.125rem" } }; + +// packages/daisyui/components/kbd/index.js +var kbd_default = ({ addComponents, prefix = "" }) => { + const prefixedkbd = addPrefix(object_default19, prefix); + addComponents({ ...prefixedkbd }); +}; + +// packages/daisyui/components/select/object.js +var object_default20 = { ".select": { border: "var(--border) solid #0000", position: "relative", display: "inline-flex", "flex-shrink": 1, appearance: "none", "align-items": "center", gap: "calc(0.25rem * 1.5)", "background-color": "var(--color-base-100)", "padding-inline-start": "calc(0.25rem * 4)", "padding-inline-end": "calc(0.25rem * 7)", "vertical-align": "middle", width: "clamp(3rem, 20rem, 100%)", height: "var(--size)", "font-size": "0.875rem", "border-start-start-radius": "var(--join-ss, var(--radius-field))", "border-start-end-radius": "var(--join-se, var(--radius-field))", "border-end-start-radius": "var(--join-es, var(--radius-field))", "border-end-end-radius": "var(--join-ee, var(--radius-field))", "background-image": "linear-gradient(45deg, #0000 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, #0000 50%)", "background-position": "calc(100% - 20px) calc(1px + 50%), calc(100% - 16.1px) calc(1px + 50%)", "background-size": "4px 4px, 4px 4px", "background-repeat": "no-repeat", "text-overflow": "ellipsis", "box-shadow": "0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset", "border-color": "var(--input-color)", "--input-color": "color-mix(in oklab, var(--color-base-content) 20%, #0000)", "--size": "calc(var(--size-field, 0.25rem) * 10)", '[dir="rtl"] &': { "background-position": "calc(0% + 12px) calc(1px + 50%), calc(0% + 16px) calc(1px + 50%)" }, select: { "margin-inline-start": "calc(0.25rem * -4)", "margin-inline-end": "calc(0.25rem * -7)", width: "calc(100% + 2.75rem)", appearance: "none", "padding-inline-start": "calc(0.25rem * 4)", "padding-inline-end": "calc(0.25rem * 7)", height: "calc(100% - 2px)", background: "inherit", "border-radius": "inherit", "border-style": "none", "&:focus, &:focus-within": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, "&:not(:last-child)": { "margin-inline-end": "calc(0.25rem * -5.5)", "background-image": "none" } }, "&:focus, &:focus-within": { "--input-color": "var(--color-base-content)", "box-shadow": "0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000)", outline: "2px solid var(--input-color)", "outline-offset": "2px", isolation: "isolate", "z-index": 1 }, "&:has(> select[disabled]), &:is(:disabled, [disabled])": { cursor: "not-allowed", "border-color": "var(--color-base-200)", "background-color": "var(--color-base-200)", color: "color-mix(in oklab, var(--color-base-content) 40%, transparent)", "&::placeholder": { color: "color-mix(in oklab, var(--color-base-content) 20%, transparent)" } }, "&:has(> select[disabled]) > select[disabled]": { cursor: "not-allowed" } }, ".select-ghost": { "background-color": "transparent", transition: "background-color 0.2s", "box-shadow": "none", "border-color": "#0000", "&:focus, &:focus-within": { "background-color": "var(--color-base-100)", color: "var(--color-base-content)", "border-color": "#0000", "box-shadow": "none" } }, ".select-neutral": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-neutral)" } }, ".select-primary": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-primary)" } }, ".select-secondary": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-secondary)" } }, ".select-accent": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-accent)" } }, ".select-info": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-info)" } }, ".select-success": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-success)" } }, ".select-warning": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-warning)" } }, ".select-error": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-error)" } }, ".select-xs": { "--size": "calc(var(--size-field, 0.25rem) * 6)", "font-size": "0.6875rem" }, ".select-sm": { "--size": "calc(var(--size-field, 0.25rem) * 8)", "font-size": "0.75rem" }, ".select-md": { "--size": "calc(var(--size-field, 0.25rem) * 10)", "font-size": "0.875rem" }, ".select-lg": { "--size": "calc(var(--size-field, 0.25rem) * 12)", "font-size": "1.125rem" }, ".select-xl": { "--size": "calc(var(--size-field, 0.25rem) * 14)", "font-size": "1.375rem" } }; + +// packages/daisyui/components/select/index.js +var select_default = ({ addComponents, prefix = "" }) => { + const prefixedselect = addPrefix(object_default20, prefix); + addComponents({ ...prefixedselect }); +}; + +// packages/daisyui/components/progress/object.js +var object_default21 = { ".progress": { position: "relative", height: "calc(0.25rem * 2)", width: "100%", appearance: "none", overflow: "hidden", "border-radius": "var(--radius-box)", "background-color": "color-mix(in oklab, currentColor 20%, transparent)", color: "var(--color-base-content)", "&:indeterminate": { "background-image": "repeating-linear-gradient( 90deg, currentColor -1%, currentColor 10%, #0000 10%, #0000 90% )", "background-size": "200%", "background-position-x": "15%", animation: "progress 5s ease-in-out infinite", "@supports (-moz-appearance: none)": { "&::-moz-progress-bar": { "background-color": "transparent", "background-image": "repeating-linear-gradient( 90deg, currentColor -1%, currentColor 10%, #0000 10%, #0000 90% )", "background-size": "200%", "background-position-x": "15%", animation: "progress 5s ease-in-out infinite" } } }, "@supports (-moz-appearance: none)": { "&::-moz-progress-bar": { "border-radius": "var(--radius-box)", "background-color": "currentColor" } }, "@supports (-webkit-appearance: none)": { "&::-webkit-progress-bar": { "border-radius": "var(--radius-box)", "background-color": "transparent" }, "&::-webkit-progress-value": { "border-radius": "var(--radius-box)", "background-color": "currentColor" } } }, ".progress-primary": { color: "var(--color-primary)" }, ".progress-secondary": { color: "var(--color-secondary)" }, ".progress-accent": { color: "var(--color-accent)" }, ".progress-neutral": { color: "var(--color-neutral)" }, ".progress-info": { color: "var(--color-info)" }, ".progress-success": { color: "var(--color-success)" }, ".progress-warning": { color: "var(--color-warning)" }, ".progress-error": { color: "var(--color-error)" }, "@keyframes progress": { "50%": { "background-position-x": "-115%" } } }; + +// packages/daisyui/components/progress/index.js +var progress_default = ({ addComponents, prefix = "" }) => { + const prefixedprogress = addPrefix(object_default21, prefix); + addComponents({ ...prefixedprogress }); +}; + +// packages/daisyui/components/fileinput/object.js +var object_default22 = { ".file-input": { cursor: ["pointer", "pointer"], border: "var(--border) solid #0000", display: "inline-flex", appearance: "none", "align-items": "center", "background-color": "var(--color-base-100)", "vertical-align": "middle", "webkit-user-select": "none", "user-select": "none", width: "clamp(3rem, 20rem, 100%)", height: "var(--size)", "padding-inline-end": "0.75rem", "font-size": "0.875rem", "line-height": 2, "border-start-start-radius": "var(--join-ss, var(--radius-field))", "border-start-end-radius": "var(--join-se, var(--radius-field))", "border-end-start-radius": "var(--join-es, var(--radius-field))", "border-end-end-radius": "var(--join-ee, var(--radius-field))", "border-color": "var(--input-color)", "box-shadow": "0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset", "--size": "calc(var(--size-field, 0.25rem) * 10)", "--input-color": "color-mix(in oklab, var(--color-base-content) 20%, #0000)", "&::file-selector-button": { "margin-inline-end": "calc(0.25rem * 4)", cursor: "pointer", "padding-inline": "calc(0.25rem * 4)", "webkit-user-select": "none", "user-select": "none", height: "calc(100% + var(--border) * 2)", "margin-block": "calc(var(--border) * -1)", "margin-inline-start": "calc(var(--border) * -1)", "font-size": "0.875rem", color: "var(--btn-fg)", "border-width": "var(--border)", "border-style": "solid", "border-color": "var(--btn-border)", "border-start-start-radius": "calc(var(--join-ss, var(--radius-field) - var(--border)))", "border-end-start-radius": "calc(var(--join-es, var(--radius-field) - var(--border)))", "font-weight": 600, "background-color": "var(--btn-bg)", "background-size": "calc(var(--noise) * 100%)", "background-image": "var(--btn-noise)", "text-shadow": "0 0.5px oklch(1 0 0 / calc(var(--depth) * 0.15))", "box-shadow": "0 0.5px 0 0.5px color-mix( in oklab, color-mix(in oklab, white 30%, var(--btn-bg)) calc(var(--depth) * 20%), #0000 ) inset, var(--btn-shadow)", "--size": "calc(var(--size-field, 0.25rem) * 10)", "--btn-bg": "var(--btn-color, var(--color-base-200))", "--btn-fg": "var(--color-base-content)", "--btn-border": "color-mix(in oklab, var(--btn-bg), #000 5%)", "--btn-shadow": `0 3px 2px -2px color-mix(in oklab, var(--btn-bg) 30%, #0000), + 0 4px 3px -2px color-mix(in oklab, var(--btn-bg) 30%, #0000)`, "--btn-noise": "var(--fx-noise)" }, "&:focus": { "--input-color": "var(--color-base-content)", "box-shadow": "0 1px color-mix(in oklab, var(--input-color) 10%, #0000)", outline: "2px solid var(--input-color)", "outline-offset": "2px", isolation: "isolate" }, "&:has(> input[disabled]), &:is(:disabled, [disabled])": { cursor: "not-allowed", "border-color": "var(--color-base-200)", "background-color": "var(--color-base-200)", "&::placeholder": { color: "color-mix(in oklab, var(--color-base-content) 20%, transparent)" }, "box-shadow": "none", color: "color-mix(in oklch, var(--color-base-content) 20%, #0000)", "&::file-selector-button": { cursor: "not-allowed", "border-color": "var(--color-base-200)", "background-color": "var(--color-base-200)", "--btn-border": "#0000", "--btn-noise": "none", "--btn-fg": "color-mix(in oklch, var(--color-base-content) 20%, #0000)" } } }, ".file-input-ghost": { "background-color": "transparent", transition: "background-color 0.2s", "box-shadow": "none", "border-color": "#0000", "&::file-selector-button": { "margin-inline-start": "calc(0.25rem * 0)", "margin-inline-end": "calc(0.25rem * 4)", height: "100%", cursor: "pointer", "padding-inline": "calc(0.25rem * 4)", "webkit-user-select": "none", "user-select": "none", "margin-block": "0", "border-start-end-radius": "calc(var(--join-ss, var(--radius-field) - var(--border)))", "border-end-end-radius": "calc(var(--join-es, var(--radius-field) - var(--border)))" }, "&:focus, &:focus-within": { "background-color": "var(--color-base-100)", color: "var(--color-base-content)", "border-color": "#0000", "box-shadow": "none" } }, ".file-input-neutral": { "--btn-color": "var(--color-neutral)", "&::file-selector-button": { color: "var(--color-neutral-content)" }, "&, &:focus, &:focus-within": { "--input-color": "var(--color-neutral)" } }, ".file-input-primary": { "--btn-color": "var(--color-primary)", "&::file-selector-button": { color: "var(--color-primary-content)" }, "&, &:focus, &:focus-within": { "--input-color": "var(--color-primary)" } }, ".file-input-secondary": { "--btn-color": "var(--color-secondary)", "&::file-selector-button": { color: "var(--color-secondary-content)" }, "&, &:focus, &:focus-within": { "--input-color": "var(--color-secondary)" } }, ".file-input-accent": { "--btn-color": "var(--color-accent)", "&::file-selector-button": { color: "var(--color-accent-content)" }, "&, &:focus, &:focus-within": { "--input-color": "var(--color-accent)" } }, ".file-input-info": { "--btn-color": "var(--color-info)", "&::file-selector-button": { color: "var(--color-info-content)" }, "&, &:focus, &:focus-within": { "--input-color": "var(--color-info)" } }, ".file-input-success": { "--btn-color": "var(--color-success)", "&::file-selector-button": { color: "var(--color-success-content)" }, "&, &:focus, &:focus-within": { "--input-color": "var(--color-success)" } }, ".file-input-warning": { "--btn-color": "var(--color-warning)", "&::file-selector-button": { color: "var(--color-warning-content)" }, "&, &:focus, &:focus-within": { "--input-color": "var(--color-warning)" } }, ".file-input-error": { "--btn-color": "var(--color-error)", "&::file-selector-button": { color: "var(--color-error-content)" }, "&, &:focus, &:focus-within": { "--input-color": "var(--color-error)" } }, ".file-input-xs": { "--size": "calc(var(--size-field, 0.25rem) * 6)", "font-size": "0.6875rem", "line-height": "1rem", "&::file-selector-button": { "font-size": "0.6875rem" } }, ".file-input-sm": { "--size": "calc(var(--size-field, 0.25rem) * 8)", "font-size": "0.75rem", "line-height": "1.5rem", "&::file-selector-button": { "font-size": "0.75rem" } }, ".file-input-md": { "--size": "calc(var(--size-field, 0.25rem) * 10)", "font-size": "0.875rem", "line-height": 2, "&::file-selector-button": { "font-size": "0.875rem" } }, ".file-input-lg": { "--size": "calc(var(--size-field, 0.25rem) * 12)", "font-size": "1.125rem", "line-height": "2.5rem", "&::file-selector-button": { "font-size": "1.125rem" } }, ".file-input-xl": { "padding-inline-end": "calc(0.25rem * 6)", "--size": "calc(var(--size-field, 0.25rem) * 14)", "font-size": "1.125rem", "line-height": "3rem", "&::file-selector-button": { "font-size": "1.375rem" } } }; + +// packages/daisyui/components/fileinput/index.js +var fileinput_default = ({ addComponents, prefix = "" }) => { + const prefixedfileinput = addPrefix(object_default22, prefix); + addComponents({ ...prefixedfileinput }); +}; + +// packages/daisyui/components/modal/object.js +var object_default23 = { ".modal": { "pointer-events": "none", visibility: "hidden", position: "fixed", inset: "calc(0.25rem * 0)", margin: "calc(0.25rem * 0)", display: "grid", height: "100%", "max-height": "none", width: "100%", "max-width": "none", "align-items": "center", "justify-items": "center", "background-color": "transparent", padding: "calc(0.25rem * 0)", color: "inherit", "overflow-x": "hidden", transition: "translate 0.3s ease-out, visibility 0.3s allow-discrete, background-color 0.3s ease-out, opacity 0.1s ease-out", "overflow-y": "hidden", "overscroll-behavior": "contain", "z-index": 999, "&::backdrop": { display: "none" }, "&.modal-open, &[open], &:target": { "pointer-events": "auto", visibility: "visible", opacity: "100%", "background-color": "oklch(0% 0 0/ 0.4)", ".modal-box": { translate: "0 0", scale: "1", opacity: 1 } }, "@starting-style": { "&.modal-open, &[open], &:target": { visibility: "hidden", opacity: "0%" } } }, ".modal-action": { "margin-top": "calc(0.25rem * 6)", display: "flex", "justify-content": "flex-end", gap: "calc(0.25rem * 2)" }, ".modal-toggle": { position: "fixed", height: "calc(0.25rem * 0)", width: "calc(0.25rem * 0)", appearance: "none", opacity: "0%", "&:checked + .modal": { "pointer-events": "auto", visibility: "visible", opacity: "100%", "background-color": "oklch(0% 0 0/ 0.4)", ".modal-box": { translate: "0 0", scale: "1", opacity: 1 } }, "@starting-style": { "&:checked + .modal": { visibility: "hidden", opacity: "0%" } } }, ".modal-backdrop": { "grid-column-start": "1", "grid-row-start": "1", display: "grid", "align-self": "stretch", "justify-self": "stretch", color: "transparent", "z-index": -1, button: { cursor: "pointer" } }, ".modal-box": { "grid-column-start": "1", "grid-row-start": "1", "max-height": "100vh", width: "calc(11/12 * 100%)", "max-width": "32rem", "background-color": "var(--color-base-100)", padding: "calc(0.25rem * 6)", transition: "translate 0.3s ease-out, scale 0.3s ease-out, opacity 0.2s ease-out 0.05s, box-shadow 0.3s ease-out", "border-top-left-radius": "var(--modal-tl, var(--radius-box))", "border-top-right-radius": "var(--modal-tr, var(--radius-box))", "border-bottom-left-radius": "var(--modal-bl, var(--radius-box))", "border-bottom-right-radius": "var(--modal-br, var(--radius-box))", scale: "95%", opacity: 0, "box-shadow": "oklch(0% 0 0/ 0.25) 0px 25px 50px -12px", "overflow-y": "auto", "overscroll-behavior": "contain" }, ".modal-top": { "place-items": "start", ":where(.modal-box)": { height: "auto", width: "100%", "max-width": "none", "max-height": "calc(100vh - 5em)", translate: "0 -100%", scale: "1", "--modal-tl": "0", "--modal-tr": "0", "--modal-bl": "var(--radius-box)", "--modal-br": "var(--radius-box)" } }, ".modal-middle": { "place-items": "center", ":where(.modal-box)": { height: "auto", width: "calc(11/12 * 100%)", "max-width": "32rem", "max-height": "calc(100vh - 5em)", translate: "0 2%", scale: "98%", "--modal-tl": "var(--radius-box)", "--modal-tr": "var(--radius-box)", "--modal-bl": "var(--radius-box)", "--modal-br": "var(--radius-box)" } }, ".modal-bottom": { "place-items": "end", ":where(.modal-box)": { height: "auto", width: "100%", "max-width": "none", "max-height": "calc(100vh - 5em)", translate: "0 100%", scale: "1", "--modal-tl": "var(--radius-box)", "--modal-tr": "var(--radius-box)", "--modal-bl": "0", "--modal-br": "0" } }, ".modal-start": { "place-items": "start", ":where(.modal-box)": { height: "100vh", "max-height": "none", width: "auto", "max-width": "none", translate: "-100% 0", scale: "1", "--modal-tl": "0", "--modal-tr": "var(--radius-box)", "--modal-bl": "0", "--modal-br": "var(--radius-box)" } }, ".modal-end": { "place-items": "end", ":where(.modal-box)": { height: "100vh", "max-height": "none", width: "auto", "max-width": "none", translate: "100% 0", scale: "1", "--modal-tl": "var(--radius-box)", "--modal-tr": "0", "--modal-bl": "var(--radius-box)", "--modal-br": "0" } } }; + +// packages/daisyui/components/modal/index.js +var modal_default = ({ addComponents, prefix = "" }) => { + const prefixedmodal = addPrefix(object_default23, prefix); + addComponents({ ...prefixedmodal }); +}; + +// packages/daisyui/components/footer/object.js +var object_default24 = { ".footer": { display: "grid", width: "100%", "grid-auto-flow": "row", "place-items": "start", "column-gap": "calc(0.25rem * 4)", "row-gap": "calc(0.25rem * 10)", "font-size": "0.875rem", "line-height": "1.25rem", "& > *": { display: "grid", "place-items": "start", gap: "calc(0.25rem * 2)" }, "&.footer-center": { "grid-auto-flow": "column dense", "place-items": "center", "text-align": "center", "& > *": { "place-items": "center" } } }, ".footer-title": { "margin-bottom": "calc(0.25rem * 2)", "text-transform": "uppercase", opacity: "60%", "font-weight": 600 }, ".footer-horizontal": { "grid-auto-flow": "column", "&.footer-center": { "grid-auto-flow": "row dense" } }, ".footer-vertical": { "grid-auto-flow": "row", "&.footer-center": { "grid-auto-flow": "column dense" } } }; + +// packages/daisyui/components/footer/index.js +var footer_default = ({ addComponents, prefix = "" }) => { + const prefixedfooter = addPrefix(object_default24, prefix); + addComponents({ ...prefixedfooter }); +}; + +// packages/daisyui/components/table/object.js +var object_default25 = { ".table": { "font-size": "0.875rem", position: "relative", width: "100%", "border-radius": "var(--radius-box)", "text-align": "left", '&:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *)': { "text-align": "right" }, "tr.row-hover": { "&, &:nth-child(even)": { "&:hover": { "@media (hover: hover)": { "background-color": "var(--color-base-200)" } } } }, ":where(th, td)": { "padding-inline": "calc(0.25rem * 4)", "padding-block": "calc(0.25rem * 3)", "vertical-align": "middle" }, ":where(thead, tfoot)": { "white-space": "nowrap", color: "color-mix(in oklab, var(--color-base-content) 60%, transparent)", "font-size": "0.875rem", "font-weight": 600 }, ":where(tfoot)": { "border-top": "var(--border) solid color-mix(in oklch, var(--color-base-content) 5%, #0000)" }, ":where(.table-pin-rows thead tr)": { position: "sticky", top: "calc(0.25rem * 0)", "z-index": 1, "background-color": "var(--color-base-100)" }, ":where(.table-pin-rows tfoot tr)": { position: "sticky", bottom: "calc(0.25rem * 0)", "z-index": 1, "background-color": "var(--color-base-100)" }, ":where(.table-pin-cols tr th)": { position: "sticky", right: "calc(0.25rem * 0)", left: "calc(0.25rem * 0)", "background-color": "var(--color-base-100)" }, ":where(thead tr, tbody tr:not(:last-child))": { "border-bottom": "var(--border) solid color-mix(in oklch, var(--color-base-content) 5%, #0000)" } }, ".table-zebra": { tbody: { tr: { "&:where(:nth-child(even))": { "background-color": "var(--color-base-200)", ":where(.table-pin-cols tr th)": { "background-color": "var(--color-base-200)" } }, "&.row-hover": { "&, &:where(:nth-child(even))": { "&:hover": { "@media (hover: hover)": { "background-color": "var(--color-base-300)" } } } } } } }, ".table-xs": { ":not(thead, tfoot) tr": { "font-size": "0.6875rem" }, ":where(th, td)": { "padding-inline": "calc(0.25rem * 2)", "padding-block": "calc(0.25rem * 1)" } }, ".table-sm": { ":not(thead, tfoot) tr": { "font-size": "0.75rem" }, ":where(th, td)": { "padding-inline": "calc(0.25rem * 3)", "padding-block": "calc(0.25rem * 2)" } }, ".table-md": { ":not(thead, tfoot) tr": { "font-size": "0.875rem" }, ":where(th, td)": { "padding-inline": "calc(0.25rem * 4)", "padding-block": "calc(0.25rem * 3)" } }, ".table-lg": { ":not(thead, tfoot) tr": { "font-size": "1.125rem" }, ":where(th, td)": { "padding-inline": "calc(0.25rem * 5)", "padding-block": "calc(0.25rem * 4)" } }, ".table-xl": { ":not(thead, tfoot) tr": { "font-size": "1.375rem" }, ":where(th, td)": { "padding-inline": "calc(0.25rem * 6)", "padding-block": "calc(0.25rem * 5)" } } }; + +// packages/daisyui/components/table/index.js +var table_default = ({ addComponents, prefix = "" }) => { + const prefixedtable = addPrefix(object_default25, prefix); + addComponents({ ...prefixedtable }); +}; + +// packages/daisyui/components/avatar/object.js +var object_default26 = { ".avatar-group": { display: "flex", overflow: "hidden", ":where(.avatar)": { overflow: "hidden", "border-radius": "calc(infinity * 1px)", border: "4px solid var(--color-base-100)" } }, ".avatar": { position: "relative", display: "inline-flex", "vertical-align": "middle", "& > div": { display: "block", "aspect-ratio": "1 / 1", overflow: "hidden" }, img: { height: "100%", width: "100%", "object-fit": "cover" } }, ".avatar-placeholder": { "& > div": { display: "flex", "align-items": "center", "justify-content": "center" } }, ".avatar-online": { "&:before": { content: '""', position: "absolute", "z-index": 1, display: "block", "border-radius": "calc(infinity * 1px)", "background-color": "var(--color-success)", outline: "2px solid var(--color-base-100)", width: "15%", height: "15%", top: "7%", right: "7%" } }, ".avatar-offline": { "&:before": { content: '""', position: "absolute", "z-index": 1, display: "block", "border-radius": "calc(infinity * 1px)", "background-color": "var(--color-base-300)", outline: "2px solid var(--color-base-100)", width: "15%", height: "15%", top: "7%", right: "7%" } } }; + +// packages/daisyui/components/avatar/index.js +var avatar_default = ({ addComponents, prefix = "" }) => { + const prefixedavatar = addPrefix(object_default26, prefix); + addComponents({ ...prefixedavatar }); +}; + +// packages/daisyui/components/input/object.js +var object_default27 = { ".input": { cursor: "text", border: "var(--border) solid #0000", position: "relative", display: "inline-flex", "flex-shrink": 1, appearance: "none", "align-items": "center", gap: "calc(0.25rem * 2)", "background-color": "var(--color-base-100)", "padding-inline": "calc(0.25rem * 3)", "vertical-align": "middle", "white-space": "nowrap", width: "clamp(3rem, 20rem, 100%)", height: "var(--size)", "font-size": "0.875rem", "border-start-start-radius": "var(--join-ss, var(--radius-field))", "border-start-end-radius": "var(--join-se, var(--radius-field))", "border-end-start-radius": "var(--join-es, var(--radius-field))", "border-end-end-radius": "var(--join-ee, var(--radius-field))", "border-color": "var(--input-color)", "box-shadow": "0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset", "--size": "calc(var(--size-field, 0.25rem) * 10)", "--input-color": "color-mix(in oklab, var(--color-base-content) 20%, #0000)", "&:where(input)": { display: "inline-flex" }, ":where(input)": { display: "inline-flex", height: "100%", width: "100%", appearance: "none", "background-color": "transparent", border: "none", "&:focus, &:focus-within": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } } }, ':where(input[type="date"])': { display: "inline-block" }, "&:focus, &:focus-within": { "--input-color": "var(--color-base-content)", "box-shadow": "0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000)", outline: "2px solid var(--input-color)", "outline-offset": "2px", isolation: "isolate", "z-index": 1 }, "&:has(> input[disabled]), &:is(:disabled, [disabled])": { cursor: "not-allowed", "border-color": "var(--color-base-200)", "background-color": "var(--color-base-200)", color: "color-mix(in oklab, var(--color-base-content) 40%, transparent)", "&::placeholder": { color: "color-mix(in oklab, var(--color-base-content) 20%, transparent)" }, "box-shadow": "none" }, "&:has(> input[disabled]) > input[disabled]": { cursor: "not-allowed" }, "&::-webkit-date-and-time-value": { "text-align": "inherit" }, '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -3)", "margin-inline-end": "calc(0.25rem * -3)" } }, "&::-webkit-calendar-picker-indicator": { position: "absolute", "inset-inline-end": "0.75em" } }, ".input-ghost": { "background-color": "transparent", "box-shadow": "none", "border-color": "#0000", "&:focus, &:focus-within": { "background-color": "var(--color-base-100)", color: "var(--color-base-content)", "border-color": "#0000", "box-shadow": "none" } }, ".input-neutral": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-neutral)" } }, ".input-primary": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-primary)" } }, ".input-secondary": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-secondary)" } }, ".input-accent": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-accent)" } }, ".input-info": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-info)" } }, ".input-success": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-success)" } }, ".input-warning": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-warning)" } }, ".input-error": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-error)" } }, ".input-xs": { "--size": "calc(var(--size-field, 0.25rem) * 6)", "font-size": "0.6875rem", '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -1)", "margin-inline-end": "calc(0.25rem * -3)" } } }, ".input-sm": { "--size": "calc(var(--size-field, 0.25rem) * 8)", "font-size": "0.75rem", '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -2)", "margin-inline-end": "calc(0.25rem * -3)" } } }, ".input-md": { "--size": "calc(var(--size-field, 0.25rem) * 10)", "font-size": "0.875rem", '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -3)", "margin-inline-end": "calc(0.25rem * -3)" } } }, ".input-lg": { "--size": "calc(var(--size-field, 0.25rem) * 12)", "font-size": "1.125rem", '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -3)", "margin-inline-end": "calc(0.25rem * -3)" } } }, ".input-xl": { "--size": "calc(var(--size-field, 0.25rem) * 14)", "font-size": "1.375rem", '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -4)", "margin-inline-end": "calc(0.25rem * -3)" } } } }; + +// packages/daisyui/components/input/index.js +var input_default = ({ addComponents, prefix = "" }) => { + const prefixedinput = addPrefix(object_default27, prefix); + addComponents({ ...prefixedinput }); +}; + +// packages/daisyui/components/checkbox/object.js +var object_default28 = { ".checkbox": { border: "var(--border) solid var(--input-color, color-mix(in oklab, var(--color-base-content) 20%, #0000))", position: "relative", "flex-shrink": 0, cursor: "pointer", appearance: "none", "border-radius": "var(--radius-selector)", padding: "calc(0.25rem * 1)", "vertical-align": "middle", color: "var(--color-base-content)", "box-shadow": "0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 0 #0000 inset, 0 0 #0000", transition: "background-color 0.2s, box-shadow 0.2s", "--size": "calc(var(--size-selector, 0.25rem) * 6)", width: "var(--size)", height: "var(--size)", "background-size": "auto, calc(var(--noise) * 100%)", "background-image": "none, var(--fx-noise)", "&:before": { "--tw-content": '""', content: "var(--tw-content)", display: "block", width: "100%", height: "100%", rotate: "45deg", "background-color": "currentColor", opacity: "0%", transition: "clip-path 0.3s, opacity 0.1s, rotate 0.3s, translate 0.3s", "transition-delay": "0.1s", "clip-path": "polygon(20% 100%, 20% 80%, 50% 80%, 50% 80%, 70% 80%, 70% 100%)", "box-shadow": "0px 3px 0 0px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset", "font-size": "1rem", "line-height": 0.75 }, "&:focus-visible": { outline: "2px solid var(--input-color, currentColor)", "outline-offset": "2px" }, '&:checked, &[aria-checked="true"]': { "background-color": "var(--input-color, #0000)", "box-shadow": "0 0 #0000 inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1))", "&:before": { "clip-path": "polygon(20% 100%, 20% 80%, 50% 80%, 50% 0%, 70% 0%, 70% 100%)", opacity: "100%" }, "@media (forced-colors: active)": { "&:before": { rotate: "0deg", "background-color": "transparent", "--tw-content": '"✔︎"', "clip-path": "none" } }, "@media print": { "&:before": { rotate: "0deg", "background-color": "transparent", "--tw-content": '"✔︎"', "clip-path": "none" } } }, "&:indeterminate": { "&:before": { rotate: "0deg", opacity: "100%", translate: "0 -35%", "clip-path": "polygon(20% 100%, 20% 80%, 50% 80%, 50% 80%, 80% 80%, 80% 100%)" } } }, ".checkbox-primary": { color: "var(--color-primary-content)", "--input-color": "var(--color-primary)" }, ".checkbox-secondary": { color: "var(--color-secondary-content)", "--input-color": "var(--color-secondary)" }, ".checkbox-accent": { color: "var(--color-accent-content)", "--input-color": "var(--color-accent)" }, ".checkbox-neutral": { color: "var(--color-neutral-content)", "--input-color": "var(--color-neutral)" }, ".checkbox-info": { color: "var(--color-info-content)", "--input-color": "var(--color-info)" }, ".checkbox-success": { color: "var(--color-success-content)", "--input-color": "var(--color-success)" }, ".checkbox-warning": { color: "var(--color-warning-content)", "--input-color": "var(--color-warning)" }, ".checkbox-error": { color: "var(--color-error-content)", "--input-color": "var(--color-error)" }, ".checkbox:disabled": { cursor: "not-allowed", opacity: "20%" }, ".checkbox-xs": { padding: "0.125rem", "--size": "calc(var(--size-selector, 0.25rem) * 4)" }, ".checkbox-sm": { padding: "0.1875rem", "--size": "calc(var(--size-selector, 0.25rem) * 5)" }, ".checkbox-md": { padding: "0.25rem", "--size": "calc(var(--size-selector, 0.25rem) * 6)" }, ".checkbox-lg": { padding: "0.3125rem", "--size": "calc(var(--size-selector, 0.25rem) * 7)" }, ".checkbox-xl": { padding: "0.375rem", "--size": "calc(var(--size-selector, 0.25rem) * 8)" } }; + +// packages/daisyui/components/checkbox/index.js +var checkbox_default = ({ addComponents, prefix = "" }) => { + const prefixedcheckbox = addPrefix(object_default28, prefix); + addComponents({ ...prefixedcheckbox }); +}; + +// packages/daisyui/components/badge/object.js +var object_default29 = { ".badge": { display: "inline-flex", "align-items": "center", "justify-content": "center", gap: "calc(0.25rem * 2)", "border-radius": "var(--radius-selector)", "vertical-align": "middle", color: "var(--badge-fg)", border: "var(--border) solid var(--badge-color, var(--color-base-200))", "font-size": "0.875rem", width: "fit-content", "padding-inline": "calc(0.25rem * 3 - var(--border))", "background-size": "auto, calc(var(--noise) * 100%)", "background-image": "none, var(--fx-noise)", "background-color": "var(--badge-bg)", "--badge-bg": "var(--badge-color, var(--color-base-100))", "--badge-fg": "var(--color-base-content)", "--size": "calc(var(--size-selector, 0.25rem) * 6)", height: "var(--size)", "&.badge-outline": { "--badge-fg": "var(--badge-color)", "--badge-bg": "#0000", "background-image": "none", "border-color": "currentColor" }, "&.badge-dash": { "--badge-fg": "var(--badge-color)", "--badge-bg": "#0000", "background-image": "none", "border-color": "currentColor", "border-style": "dashed" }, "&.badge-soft": { color: "var(--badge-color, var(--color-base-content))", "background-color": "color-mix( in oklab, var(--badge-color, var(--color-base-content)) 8%, var(--color-base-100) )", "border-color": "color-mix( in oklab, var(--badge-color, var(--color-base-content)) 10%, var(--color-base-100) )", "background-image": "none" } }, ".badge-primary": { "--badge-color": "var(--color-primary)", "--badge-fg": "var(--color-primary-content)" }, ".badge-secondary": { "--badge-color": "var(--color-secondary)", "--badge-fg": "var(--color-secondary-content)" }, ".badge-accent": { "--badge-color": "var(--color-accent)", "--badge-fg": "var(--color-accent-content)" }, ".badge-neutral": { "--badge-color": "var(--color-neutral)", "--badge-fg": "var(--color-neutral-content)" }, ".badge-info": { "--badge-color": "var(--color-info)", "--badge-fg": "var(--color-info-content)" }, ".badge-success": { "--badge-color": "var(--color-success)", "--badge-fg": "var(--color-success-content)" }, ".badge-warning": { "--badge-color": "var(--color-warning)", "--badge-fg": "var(--color-warning-content)" }, ".badge-error": { "--badge-color": "var(--color-error)", "--badge-fg": "var(--color-error-content)" }, ".badge-ghost": { "border-color": "var(--color-base-200)", "background-color": "var(--color-base-200)", color: "var(--color-base-content)", "background-image": "none" }, ".badge-xs": { "--size": "calc(var(--size-selector, 0.25rem) * 4)", "font-size": "0.625rem", "padding-inline": "calc(0.25rem * 2 - var(--border))" }, ".badge-sm": { "--size": "calc(var(--size-selector, 0.25rem) * 5)", "font-size": "0.75rem", "padding-inline": "calc(0.25rem * 2.5 - var(--border))" }, ".badge-md": { "--size": "calc(var(--size-selector, 0.25rem) * 6)", "font-size": "0.875rem", "padding-inline": "calc(0.25rem * 3 - var(--border))" }, ".badge-lg": { "--size": "calc(var(--size-selector, 0.25rem) * 7)", "font-size": "1rem", "padding-inline": "calc(0.25rem * 3.5 - var(--border))" }, ".badge-xl": { "--size": "calc(var(--size-selector, 0.25rem) * 8)", "font-size": "1.125rem", "padding-inline": "calc(0.25rem * 4 - var(--border))" } }; + +// packages/daisyui/components/badge/index.js +var badge_default = ({ addComponents, prefix = "" }) => { + const prefixedbadge = addPrefix(object_default29, prefix); + addComponents({ ...prefixedbadge }); +}; + +// packages/daisyui/components/status/object.js +var object_default30 = { ".status": { display: "inline-block", "aspect-ratio": "1 / 1", width: "calc(0.25rem * 2)", height: "calc(0.25rem * 2)", "border-radius": "var(--radius-selector)", "background-color": "color-mix(in oklab, var(--color-base-content) 20%, transparent)", "background-position": "center", "background-repeat": "no-repeat", "vertical-align": "middle", color: "color-mix(in srgb, #000 30%, transparent)", "@supports (color: color-mix(in lab, red, red))": { color: "color-mix(in oklab, var(--color-black) 30%, transparent)" }, "background-image": "radial-gradient( circle at 35% 30%, oklch(1 0 0 / calc(var(--depth) * 0.5)), #0000 )", "box-shadow": "0 2px 3px -1px color-mix(in oklab, currentColor calc(var(--depth) * 100%), #0000)" }, ".status-primary": { "background-color": "var(--color-primary)", color: "var(--color-primary)" }, ".status-secondary": { "background-color": "var(--color-secondary)", color: "var(--color-secondary)" }, ".status-accent": { "background-color": "var(--color-accent)", color: "var(--color-accent)" }, ".status-neutral": { "background-color": "var(--color-neutral)", color: "var(--color-neutral)" }, ".status-info": { "background-color": "var(--color-info)", color: "var(--color-info)" }, ".status-success": { "background-color": "var(--color-success)", color: "var(--color-success)" }, ".status-warning": { "background-color": "var(--color-warning)", color: "var(--color-warning)" }, ".status-error": { "background-color": "var(--color-error)", color: "var(--color-error)" }, ".status-xs": { width: "calc(0.25rem * 0.5)", height: "calc(0.25rem * 0.5)" }, ".status-sm": { width: "calc(0.25rem * 1)", height: "calc(0.25rem * 1)" }, ".status-md": { width: "calc(0.25rem * 2)", height: "calc(0.25rem * 2)" }, ".status-lg": { width: "calc(0.25rem * 3)", height: "calc(0.25rem * 3)" }, ".status-xl": { width: "calc(0.25rem * 4)", height: "calc(0.25rem * 4)" } }; + +// packages/daisyui/components/status/index.js +var status_default = ({ addComponents, prefix = "" }) => { + const prefixedstatus = addPrefix(object_default30, prefix); + addComponents({ ...prefixedstatus }); +}; + +// packages/daisyui/components/diff/object.js +var object_default31 = { ".diff": { position: "relative", display: "grid", width: "100%", overflow: "hidden", "webkit-user-select": "none", "user-select": "none", direction: "ltr", "container-type": "inline-size", "grid-template-columns": "auto 1fr", "&:focus-visible, &:has(.diff-item-1:focus-visible)": { "outline-style": "var(--tw-outline-style)", "outline-width": "2px", "outline-offset": "1px", "outline-color": "var(--color-base-content)" }, "&:focus-visible": { "outline-style": "var(--tw-outline-style)", "outline-width": "2px", "outline-offset": "1px", "outline-color": "var(--color-base-content)", ".diff-resizer": { "min-width": "90cqi", "max-width": "90cqi" } }, "&:has(.diff-item-2:focus-visible)": { "outline-style": "var(--tw-outline-style)", "outline-width": "2px", "outline-offset": "1px", ".diff-resizer": { "min-width": "10cqi", "max-width": "10cqi" } }, "@supports (-webkit-overflow-scrolling: touch) and (overflow: -webkit-paged-x)": { "&:focus": { ".diff-resizer": { "min-width": "10cqi", "max-width": "10cqi" } }, "&:has(.diff-item-1:focus)": { ".diff-resizer": { "min-width": "90cqi", "max-width": "90cqi" } } } }, ".diff-resizer": { position: "relative", top: "calc(1/2 * 100%)", "z-index": 1, "grid-column-start": "1", "grid-row-start": "1", height: "calc(0.25rem * 2)", width: "50cqi", "max-width": "calc(100cqi - 1rem)", "min-width": "1rem", resize: "horizontal", overflow: "hidden", opacity: "0%", transform: "scaleY(3) translate(0.35rem, 0.08rem)", cursor: "ew-resize", "transform-origin": "100% 100%", "clip-path": "inset(calc(100% - 0.75rem) 0 0 calc(100% - 0.75rem))", transition: "min-width 0.3s ease-out, max-width 0.3s ease-out" }, ".diff-item-2": { position: "relative", "grid-column-start": "1", "grid-row-start": "1", "&:after": { "pointer-events": "none", position: "absolute", top: "calc(1/2 * 100%)", right: "1px", bottom: "calc(0.25rem * 0)", "z-index": 2, "border-radius": "calc(infinity * 1px)", "background-color": "color-mix(in oklab, var(--color-base-100) 50%, transparent)", width: "1.2rem", height: "1.8rem", border: "2px solid var(--color-base-100)", content: '""', outline: "1px solid color-mix(in oklab, var(--color-base-content) 5%, #0000)", "outline-offset": "-3px", "backdrop-filter": "blur(8px)", "box-shadow": "0 1px 2px 0 oklch(0% 0 0 / 0.1)", translate: "50% -50%" }, "> *": { "pointer-events": "none", position: "absolute", top: "calc(0.25rem * 0)", bottom: "calc(0.25rem * 0)", left: "calc(0.25rem * 0)", height: "100%", width: "100cqi", "max-width": "none", "object-fit": "cover", "object-position": "center" }, "@supports (-webkit-overflow-scrolling: touch) and (overflow: -webkit-paged-x)": { "&:after": { content: "none" } } }, ".diff-item-1": { position: "relative", "z-index": 1, "grid-column-start": "1", "grid-row-start": "1", overflow: "hidden", "border-right": "2px solid var(--color-base-100)", "> *": { "pointer-events": "none", position: "absolute", top: "calc(0.25rem * 0)", bottom: "calc(0.25rem * 0)", left: "calc(0.25rem * 0)", height: "100%", width: "100cqi", "max-width": "none", "object-fit": "cover", "object-position": "center" } } }; + +// packages/daisyui/components/diff/index.js +var diff_default = ({ addComponents, prefix = "" }) => { + const prefixeddiff = addPrefix(object_default31, prefix); + addComponents({ ...prefixeddiff }); +}; + +// packages/daisyui/components/hero/object.js +var object_default32 = { ".hero": { display: "grid", width: "100%", "place-items": "center", "background-size": "cover", "background-position": "center", "& > *": { "grid-column-start": "1", "grid-row-start": "1" } }, ".hero-overlay": { "grid-column-start": "1", "grid-row-start": "1", height: "100%", width: "100%", "background-color": "color-mix(in oklab, var(--color-neutral) 50%, transparent)" }, ".hero-content": { isolation: "isolate", display: "flex", "max-width": "80rem", "align-items": "center", "justify-content": "center", gap: "calc(0.25rem * 4)", padding: "calc(0.25rem * 4)" } }; + +// packages/daisyui/components/hero/index.js +var hero_default = ({ addComponents, prefix = "" }) => { + const prefixedhero = addPrefix(object_default32, prefix); + addComponents({ ...prefixedhero }); +}; + +// packages/daisyui/components/toggle/object.js +var object_default33 = { ".toggle": { border: "var(--border) solid currentColor", color: "var(--input-color)", position: "relative", display: "inline-grid", "flex-shrink": 0, cursor: "pointer", appearance: "none", "place-content": "center", "vertical-align": "middle", "webkit-user-select": "none", "user-select": "none", "grid-template-columns": "0fr 1fr 1fr", "--radius-selector-max": `calc( + var(--radius-selector) + var(--radius-selector) + var(--radius-selector) + )`, "border-radius": "calc( var(--radius-selector) + min(var(--toggle-p), var(--radius-selector-max)) + min(var(--border), var(--radius-selector-max)) )", padding: "var(--toggle-p)", "box-shadow": "0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000) inset", transition: "color 0.3s, grid-template-columns 0.2s", "--input-color": "color-mix(in oklab, var(--color-base-content) 50%, #0000)", "--toggle-p": "calc(var(--size) * 0.125)", "--size": "calc(var(--size-selector, 0.25rem) * 6)", width: "calc((var(--size) * 2) - (var(--border) + var(--toggle-p)) * 2)", height: "var(--size)", "> *": { "z-index": 1, "grid-column": "span 1 / span 1", "grid-column-start": "2", "grid-row-start": "1", height: "100%", cursor: "pointer", appearance: "none", "background-color": "transparent", padding: "calc(0.25rem * 0.5)", transition: "opacity 0.2s, rotate 0.4s", border: "none", "&:focus": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, "&:nth-child(2)": { color: "var(--color-base-100)", rotate: "0deg" }, "&:nth-child(3)": { color: "var(--color-base-100)", opacity: "0%", rotate: "-15deg" } }, "&:has(:checked)": { "> :nth-child(2)": { opacity: "0%", rotate: "15deg" }, "> :nth-child(3)": { opacity: "100%", rotate: "0deg" } }, "&:before": { position: "relative", "inset-inline-start": "calc(0.25rem * 0)", "grid-column-start": "2", "grid-row-start": "1", "aspect-ratio": "1 / 1", height: "100%", "border-radius": "var(--radius-selector)", "background-color": "currentColor", translate: "0", "--tw-content": '""', content: "var(--tw-content)", transition: "background-color 0.1s, translate 0.2s, inset-inline-start 0.2s", "box-shadow": "0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000)", "background-size": "auto, calc(var(--noise) * 100%)", "background-image": "none, var(--fx-noise)" }, "@media (forced-colors: active)": { "&:before": { "outline-style": "var(--tw-outline-style)", "outline-width": "1px", "outline-offset": "calc(1px * -1)" } }, "@media print": { "&:before": { outline: "0.25rem solid", "outline-offset": "-1rem" } }, "&:focus-visible, &:has(:focus-visible)": { outline: "2px solid currentColor", "outline-offset": "2px" }, '&:checked, &[aria-checked="true"], &:has(> input:checked)': { "grid-template-columns": "1fr 1fr 0fr", "background-color": "var(--color-base-100)", "--input-color": "var(--color-base-content)", "&:before": { "background-color": "currentColor" }, "@starting-style": { "&:before": { opacity: 0 } } }, "&:indeterminate": { "grid-template-columns": "0.5fr 1fr 0.5fr" }, "&:disabled": { cursor: "not-allowed", opacity: "30%", "&:before": { "background-color": "transparent", border: "var(--border) solid currentColor" } } }, ".toggle-primary": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-primary)" } }, ".toggle-secondary": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-secondary)" } }, ".toggle-accent": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-accent)" } }, ".toggle-neutral": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-neutral)" } }, ".toggle-success": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-success)" } }, ".toggle-warning": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-warning)" } }, ".toggle-info": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-info)" } }, ".toggle-error": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-error)" } }, ".toggle-xs": { '&:is([type="checkbox"]), &:has([type="checkbox"])': { "--size": "calc(var(--size-selector, 0.25rem) * 4)" } }, ".toggle-sm": { '&:is([type="checkbox"]), &:has([type="checkbox"])': { "--size": "calc(var(--size-selector, 0.25rem) * 5)" } }, ".toggle-md": { '&:is([type="checkbox"]), &:has([type="checkbox"])': { "--size": "calc(var(--size-selector, 0.25rem) * 6)" } }, ".toggle-lg": { '&:is([type="checkbox"]), &:has([type="checkbox"])': { "--size": "calc(var(--size-selector, 0.25rem) * 7)" } }, ".toggle-xl": { '&:is([type="checkbox"]), &:has([type="checkbox"])': { "--size": "calc(var(--size-selector, 0.25rem) * 8)" } } }; + +// packages/daisyui/components/toggle/index.js +var toggle_default = ({ addComponents, prefix = "" }) => { + const prefixedtoggle = addPrefix(object_default33, prefix); + addComponents({ ...prefixedtoggle }); +}; + +// packages/daisyui/components/stack/object.js +var object_default34 = { ".stack": { display: "inline-grid", "grid-template-columns": "3px 4px 1fr 4px 3px", "grid-template-rows": "3px 4px 1fr 4px 3px", "& > *": { height: "100%", width: "100%", "&:nth-child(n + 2)": { width: "100%", opacity: "70%" }, "&:nth-child(2)": { "z-index": 2, opacity: "90%" }, "&:nth-child(1)": { "z-index": 3, width: "100%" } }, "&, &.stack-bottom": { "> *": { "grid-column": "3 / 4", "grid-row": "3 / 6", "&:nth-child(2)": { "grid-column": "2 / 5", "grid-row": "2 / 5" }, "&:nth-child(1)": { "grid-column": "1 / 6", "grid-row": "1 / 4" } } }, "&.stack-top": { "> *": { "grid-column": "3 / 4", "grid-row": "1 / 4", "&:nth-child(2)": { "grid-column": "2 / 5", "grid-row": "2 / 5" }, "&:nth-child(1)": { "grid-column": "1 / 6", "grid-row": "3 / 6" } } }, "&.stack-start": { "> *": { "grid-column": "1 / 4", "grid-row": "3 / 4", "&:nth-child(2)": { "grid-column": "2 / 5", "grid-row": "2 / 5" }, "&:nth-child(1)": { "grid-column": "3 / 6", "grid-row": "1 / 6" } } }, "&.stack-end": { "> *": { "grid-column": "3 / 6", "grid-row": "3 / 4", "&:nth-child(2)": { "grid-column": "2 / 5", "grid-row": "2 / 5" }, "&:nth-child(1)": { "grid-column": "1 / 4", "grid-row": "1 / 6" } } } } }; + +// packages/daisyui/components/stack/index.js +var stack_default = ({ addComponents, prefix = "" }) => { + const prefixedstack = addPrefix(object_default34, prefix); + addComponents({ ...prefixedstack }); +}; + +// packages/daisyui/components/navbar/object.js +var object_default35 = { ".navbar": { display: "flex", width: "100%", "align-items": "center", padding: "0.5rem", "min-height": "4rem" }, ".navbar-start": { display: "inline-flex", "align-items": "center", width: "50%", "justify-content": "flex-start" }, ".navbar-center": { display: "inline-flex", "align-items": "center", "flex-shrink": 0 }, ".navbar-end": { display: "inline-flex", "align-items": "center", width: "50%", "justify-content": "flex-end" } }; + +// packages/daisyui/components/navbar/index.js +var navbar_default = ({ addComponents, prefix = "" }) => { + const prefixednavbar = addPrefix(object_default35, prefix); + addComponents({ ...prefixednavbar }); +}; + +// packages/daisyui/components/label/object.js +var object_default36 = { ".label": { display: "inline-flex", "align-items": "center", gap: "calc(0.25rem * 1.5)", "white-space": "nowrap", color: "color-mix(in oklab, currentColor 60%, transparent)", "&:has(input)": { cursor: "pointer" }, "&:is(.input > *, .select > *)": { display: "flex", height: "calc(100% - 0.5rem)", "align-items": "center", "padding-inline": "calc(0.25rem * 3)", "white-space": "nowrap", "font-size": "inherit", "&:first-child": { "margin-inline-start": "calc(0.25rem * -3)", "margin-inline-end": "calc(0.25rem * 3)", "border-inline-end": "var(--border) solid color-mix(in oklab, currentColor 10%, #0000)" }, "&:last-child": { "margin-inline-start": "calc(0.25rem * 3)", "margin-inline-end": "calc(0.25rem * -3)", "border-inline-start": "var(--border) solid color-mix(in oklab, currentColor 10%, #0000)" } } }, ".floating-label": { position: "relative", display: "block", input: { display: "block", "&::placeholder": { transition: "top 0.1s ease-out, translate 0.1s ease-out, scale 0.1s ease-out, opacity 0.1s ease-out" } }, textarea: { "&::placeholder": { transition: "top 0.1s ease-out, translate 0.1s ease-out, scale 0.1s ease-out, opacity 0.1s ease-out" } }, "> span": { position: "absolute", "inset-inline-start": "calc(0.25rem * 3)", "z-index": 1, "background-color": "var(--color-base-100)", "padding-inline": "calc(0.25rem * 1)", opacity: "0%", "font-size": "0.875rem", top: "calc(var(--size-field, 0.25rem) * 10 / 2)", "line-height": 1, "border-radius": "2px", "pointer-events": "none", translate: "0 -50%", transition: "top 0.1s ease-out, translate 0.1s ease-out, scale 0.1s ease-out, opacity 0.1s ease-out" }, "&:focus-within, &:not(:has(input:placeholder-shown, textarea:placeholder-shown))": { "::placeholder": { opacity: "0%", top: "0", translate: "-12.5% calc(-50% - 0.125em)", scale: "0.75", "pointer-events": "auto" }, "> span": { opacity: "100%", top: "0", translate: "-12.5% calc(-50% - 0.125em)", scale: "0.75", "pointer-events": "auto", "z-index": 2 } }, "&:has(:disabled, [disabled])": { "> span": { opacity: "0%" } }, "&:has(.input-xs, .select-xs, .textarea-xs) span": { "font-size": "0.6875rem", top: "calc(var(--size-field, 0.25rem) * 6 / 2)" }, "&:has(.input-sm, .select-sm, .textarea-sm) span": { "font-size": "0.75rem", top: "calc(var(--size-field, 0.25rem) * 8 / 2)" }, "&:has(.input-md, .select-md, .textarea-md) span": { "font-size": "0.875rem", top: "calc(var(--size-field, 0.25rem) * 10 / 2)" }, "&:has(.input-lg, .select-lg, .textarea-lg) span": { "font-size": "1.125rem", top: "calc(var(--size-field, 0.25rem) * 12 / 2)" }, "&:has(.input-xl, .select-xl, .textarea-xl) span": { "font-size": "1.375rem", top: "calc(var(--size-field, 0.25rem) * 14 / 2)" } } }; + +// packages/daisyui/components/label/index.js +var label_default = ({ addComponents, prefix = "" }) => { + const prefixedlabel = addPrefix(object_default36, prefix); + addComponents({ ...prefixedlabel }); +}; + +// packages/daisyui/components/menu/object.js +var object_default37 = { ".menu": { display: "flex", width: "fit-content", "flex-direction": "column", "flex-wrap": "wrap", padding: "calc(0.25rem * 2)", "--menu-active-fg": "var(--color-neutral-content)", "--menu-active-bg": "var(--color-neutral)", "font-size": "0.875rem", ":where(li ul)": { position: "relative", "margin-inline-start": "calc(0.25rem * 4)", "padding-inline-start": "calc(0.25rem * 2)", "white-space": "nowrap", "&:before": { position: "absolute", "inset-inline-start": "calc(0.25rem * 0)", top: "calc(0.25rem * 3)", bottom: "calc(0.25rem * 3)", "background-color": "var(--color-base-content)", opacity: "10%", width: "var(--border)", content: '""' } }, ":where(li > .menu-dropdown:not(.menu-dropdown-show))": { display: "none" }, ":where(li:not(.menu-title) > *:not(ul, details, .menu-title, .btn)), :where(li:not(.menu-title) > details > summary:not(.menu-title))": { display: "grid", "grid-auto-flow": "column", "align-content": "flex-start", "align-items": "center", gap: "calc(0.25rem * 2)", "border-radius": "var(--radius-field)", "padding-inline": "calc(0.25rem * 3)", "padding-block": "calc(0.25rem * 1.5)", "text-align": "start", "transition-property": "color, background-color, box-shadow", "transition-duration": "0.2s", "transition-timing-function": "cubic-bezier(0, 0, 0.2, 1)", "grid-auto-columns": "minmax(auto, max-content) auto max-content", "text-wrap": "balance", "user-select": "none" }, ":where(li > details > summary)": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" }, "&::-webkit-details-marker": { display: "none" } }, ":where(li > details > summary), :where(li > .menu-dropdown-toggle)": { "&:after": { "justify-self": "flex-end", display: "block", height: "0.375rem", width: "0.375rem", rotate: "-135deg", translate: "0 -1px", "transition-property": "rotate, translate", "transition-duration": "0.2s", content: '""', "transform-origin": "50% 50%", "box-shadow": "2px 2px inset", "pointer-events": "none" } }, ":where(li > details[open] > summary):after, :where(li > .menu-dropdown-toggle.menu-dropdown-show):after": { rotate: "45deg", translate: "0 1px" }, ":where( li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title), li:not(.menu-title, .disabled) > details > summary:not(.menu-title) ):not(.menu-active, :active, .btn)": { "&.menu-focus, &:focus-visible": { cursor: "pointer", "background-color": "color-mix(in oklab, var(--color-base-content) 10%, transparent)", color: "var(--color-base-content)", "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } } }, ":where( li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title):not(.menu-active, :active, .btn):hover, li:not(.menu-title, .disabled) > details > summary:not(.menu-title):not(.menu-active, :active, .btn):hover )": { cursor: "pointer", "background-color": "color-mix(in oklab, var(--color-base-content) 10%, transparent)", "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" }, "box-shadow": "0 1px oklch(0% 0 0 / 0.01) inset, 0 -1px oklch(100% 0 0 / 0.01) inset" }, ":where(li:empty)": { "background-color": "var(--color-base-content)", opacity: "10%", margin: "0.5rem 1rem", height: "1px" }, ":where(li)": { position: "relative", display: "flex", "flex-shrink": 0, "flex-direction": "column", "flex-wrap": "wrap", "align-items": "stretch", ".badge": { "justify-self": "flex-end" }, "& > *:not(ul, .menu-title, details, .btn):active, & > *:not(ul, .menu-title, details, .btn).menu-active, & > details > summary:active": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" }, color: "var(--menu-active-fg)", "background-color": "var(--menu-active-bg)", "background-size": "auto, calc(var(--noise) * 100%)", "background-image": "none, var(--fx-noise)", "&:not(&:active)": { "box-shadow": "0 2px calc(var(--depth) * 3px) -2px var(--menu-active-bg)" } }, "&.menu-disabled": { "pointer-events": "none", color: "color-mix(in oklab, var(--color-base-content) 20%, transparent)" } }, ".dropdown:focus-within": { ".menu-dropdown-toggle:after": { rotate: "45deg", translate: "0 1px" } }, ".dropdown-content": { "margin-top": "calc(0.25rem * 2)", padding: "calc(0.25rem * 2)", "&:before": { display: "none" } } }, ".menu-title": { "padding-inline": "calc(0.25rem * 3)", "padding-block": "calc(0.25rem * 2)", color: "color-mix(in oklab, var(--color-base-content) 40%, transparent)", "font-size": "0.875rem", "font-weight": 600 }, ".menu-horizontal": { display: "inline-flex", "flex-direction": "row", "& > li:not(.menu-title) > details > ul": { position: "absolute", "margin-inline-start": "calc(0.25rem * 0)", "margin-top": "calc(0.25rem * 4)", "padding-block": "calc(0.25rem * 2)", "padding-inline-end": "calc(0.25rem * 2)" }, "& > li > details > ul": { "&:before": { content: "none" } }, ":where(& > li:not(.menu-title) > details > ul)": { "border-radius": "var(--radius-box)", "background-color": "var(--color-base-100)", "box-shadow": "0 1px 3px 0 oklch(0% 0 0/0.1), 0 1px 2px -1px oklch(0% 0 0/0.1)" } }, ".menu-vertical": { display: "inline-flex", "flex-direction": "column", "& > li:not(.menu-title) > details > ul": { position: "relative", "margin-inline-start": "calc(0.25rem * 4)", "margin-top": "calc(0.25rem * 0)", "padding-block": "calc(0.25rem * 0)", "padding-inline-end": "calc(0.25rem * 0)" } }, ".menu-xs": { ":where(li:not(.menu-title) > *:not(ul, details, .menu-title)), :where(li:not(.menu-title) > details > summary:not(.menu-title))": { "border-radius": "var(--radius-field)", "padding-inline": "calc(0.25rem * 2)", "padding-block": "calc(0.25rem * 1)", "font-size": "0.6875rem" }, ".menu-title": { "padding-inline": "calc(0.25rem * 2)", "padding-block": "calc(0.25rem * 1)" } }, ".menu-sm": { ":where(li:not(.menu-title) > *:not(ul, details, .menu-title)), :where(li:not(.menu-title) > details > summary:not(.menu-title))": { "border-radius": "var(--radius-field)", "padding-inline": "calc(0.25rem * 2.5)", "padding-block": "calc(0.25rem * 1)", "font-size": "0.75rem" }, ".menu-title": { "padding-inline": "calc(0.25rem * 3)", "padding-block": "calc(0.25rem * 2)" } }, ".menu-md": { ":where(li:not(.menu-title) > *:not(ul, details, .menu-title)), :where(li:not(.menu-title) > details > summary:not(.menu-title))": { "border-radius": "var(--radius-field)", "padding-inline": "calc(0.25rem * 3)", "padding-block": "calc(0.25rem * 1.5)", "font-size": "0.875rem" }, ".menu-title": { "padding-inline": "calc(0.25rem * 3)", "padding-block": "calc(0.25rem * 2)" } }, ".menu-lg": { ":where(li:not(.menu-title) > *:not(ul, details, .menu-title)), :where(li:not(.menu-title) > details > summary:not(.menu-title))": { "border-radius": "var(--radius-field)", "padding-inline": "calc(0.25rem * 4)", "padding-block": "calc(0.25rem * 1.5)", "font-size": "1.125rem" }, ".menu-title": { "padding-inline": "calc(0.25rem * 6)", "padding-block": "calc(0.25rem * 3)" } }, ".menu-xl": { ":where(li:not(.menu-title) > *:not(ul, details, .menu-title)), :where(li:not(.menu-title) > details > summary:not(.menu-title))": { "border-radius": "var(--radius-field)", "padding-inline": "calc(0.25rem * 5)", "padding-block": "calc(0.25rem * 1.5)", "font-size": "1.375rem" }, ".menu-title": { "padding-inline": "calc(0.25rem * 6)", "padding-block": "calc(0.25rem * 3)" } } }; + +// packages/daisyui/components/menu/index.js +var menu_default = ({ addComponents, prefix = "" }) => { + const prefixedmenu = addPrefix(object_default37, prefix); + addComponents({ ...prefixedmenu }); +}; + +// packages/daisyui/components/toast/object.js +var object_default38 = { ".toast": { position: "fixed", "inset-inline-start": "auto", "inset-inline-end": "calc(0.25rem * 4)", top: "auto", bottom: "calc(0.25rem * 4)", display: "flex", "flex-direction": "column", gap: "calc(0.25rem * 2)", "background-color": "transparent", translate: "var(--toast-x, 0) var(--toast-y, 0)", width: "max-content", "max-width": "calc(100vw - 2rem)", "& > *": { animation: "toast 0.25s ease-out" }, "&:where(.toast-start)": { "inset-inline-start": "calc(0.25rem * 4)", "inset-inline-end": "auto", "--toast-x": "0" }, "&:where(.toast-center)": { "inset-inline-start": "calc(1/2 * 100%)", "inset-inline-end": "calc(1/2 * 100%)", "--toast-x": "-50%" }, "&:where(.toast-end)": { "inset-inline-start": "auto", "inset-inline-end": "calc(0.25rem * 4)", "--toast-x": "0" }, "&:where(.toast-bottom)": { top: "auto", bottom: "calc(0.25rem * 4)", "--toast-y": "0" }, "&:where(.toast-middle)": { top: "calc(1/2 * 100%)", bottom: "auto", "--toast-y": "-50%" }, "&:where(.toast-top)": { top: "calc(0.25rem * 4)", bottom: "auto", "--toast-y": "0" } }, "@keyframes toast": { "0%": { scale: "0.9", opacity: 0 }, "100%": { scale: "1", opacity: 1 } } }; + +// packages/daisyui/components/toast/index.js +var toast_default = ({ addComponents, prefix = "" }) => { + const prefixedtoast = addPrefix(object_default38, prefix); + addComponents({ ...prefixedtoast }); +}; + +// packages/daisyui/components/button/object.js +var object_default39 = { ":where(.btn)": { width: "unset" }, ".btn": { display: "inline-flex", "flex-shrink": 0, cursor: "pointer", "flex-wrap": "nowrap", "align-items": "center", "justify-content": "center", gap: "calc(0.25rem * 1.5)", "text-align": "center", "vertical-align": "middle", "outline-offset": "2px", "webkit-user-select": "none", "user-select": "none", "padding-inline": "var(--btn-p)", color: "var(--btn-fg)", "--tw-prose-links": "var(--btn-fg)", height: "var(--size)", "font-size": "var(--fontsize, 0.875rem)", "font-weight": 600, "outline-color": "var(--btn-color, var(--color-base-content))", "transition-property": "color, background-color, border-color, box-shadow", "transition-timing-function": "cubic-bezier(0, 0, 0.2, 1)", "transition-duration": "0.2s", "border-start-start-radius": "var(--join-ss, var(--radius-field))", "border-start-end-radius": "var(--join-se, var(--radius-field))", "border-end-start-radius": "var(--join-es, var(--radius-field))", "border-end-end-radius": "var(--join-ee, var(--radius-field))", "background-color": "var(--btn-bg)", "background-size": "auto, calc(var(--noise) * 100%)", "background-image": "none, var(--btn-noise)", "border-width": "var(--border)", "border-style": "solid", "border-color": "var(--btn-border)", "text-shadow": "0 0.5px oklch(100% 0 0 / calc(var(--depth) * 0.15))", "touch-action": "manipulation", "box-shadow": "0 0.5px 0 0.5px oklch(100% 0 0 / calc(var(--depth) * 6%)) inset, var(--btn-shadow)", "--size": "calc(var(--size-field, 0.25rem) * 10)", "--btn-bg": "var(--btn-color, var(--color-base-200))", "--btn-fg": "var(--color-base-content)", "--btn-p": "1rem", "--btn-border": "color-mix(in oklab, var(--btn-bg), #000 calc(var(--depth) * 5%))", "--btn-shadow": `0 3px 2px -2px color-mix(in oklab, var(--btn-bg) calc(var(--depth) * 30%), #0000), + 0 4px 3px -2px color-mix(in oklab, var(--btn-bg) calc(var(--depth) * 30%), #0000)`, "--btn-noise": "var(--fx-noise)", ".prose &": { "text-decoration-line": "none" }, "@media (hover: hover)": { "&:hover": { "--btn-bg": "color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 7%)" } }, "&:focus-visible": { "outline-width": "2px", "outline-style": "solid", isolation: "isolate" }, "&:active:not(.btn-active)": { translate: "0 0.5px", "--btn-bg": "color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 5%)", "--btn-border": "color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 7%)", "--btn-shadow": "0 0 0 0 oklch(0% 0 0/0), 0 0 0 0 oklch(0% 0 0/0)" }, "&:is(:disabled, [disabled], .btn-disabled)": { "&:not(.btn-link, .btn-ghost)": { "background-color": "color-mix(in oklab, var(--color-base-content) 10%, transparent)", "box-shadow": "none" }, "pointer-events": "none", "--btn-border": "#0000", "--btn-noise": "none", "--btn-fg": "color-mix(in oklch, var(--color-base-content) 20%, #0000)", "@media (hover: hover)": { "&:hover": { "pointer-events": "none", "background-color": "color-mix(in oklab, var(--color-neutral) 20%, transparent)", "--btn-border": "#0000", "--btn-fg": "color-mix(in oklch, var(--color-base-content) 20%, #0000)" } } }, '&:is(input[type="checkbox"], input[type="radio"])': { appearance: "none", "&::after": { content: "attr(aria-label)" } }, "&:where(input:checked:not(.filter .btn))": { "--btn-color": "var(--color-primary)", "--btn-fg": "var(--color-primary-content)", isolation: "isolate" } }, ".btn-active": { "--btn-bg": "color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 7%)", "--btn-shadow": "0 0 0 0 oklch(0% 0 0/0), 0 0 0 0 oklch(0% 0 0/0)", isolation: "isolate" }, ".btn-primary": { "--btn-color": "var(--color-primary)", "--btn-fg": "var(--color-primary-content)" }, ".btn-secondary": { "--btn-color": "var(--color-secondary)", "--btn-fg": "var(--color-secondary-content)" }, ".btn-accent": { "--btn-color": "var(--color-accent)", "--btn-fg": "var(--color-accent-content)" }, ".btn-neutral": { "--btn-color": "var(--color-neutral)", "--btn-fg": "var(--color-neutral-content)" }, ".btn-info": { "--btn-color": "var(--color-info)", "--btn-fg": "var(--color-info-content)" }, ".btn-success": { "--btn-color": "var(--color-success)", "--btn-fg": "var(--color-success-content)" }, ".btn-warning": { "--btn-color": "var(--color-warning)", "--btn-fg": "var(--color-warning-content)" }, ".btn-error": { "--btn-color": "var(--color-error)", "--btn-fg": "var(--color-error-content)" }, ".btn-ghost": { "&:not(.btn-active, :hover, :active:focus, :focus-visible)": { "--btn-shadow": '""', "--btn-bg": "#0000", "--btn-border": "#0000", "--btn-noise": "none", "&:not(:disabled, [disabled], .btn-disabled)": { "outline-color": "currentColor", "--btn-fg": "currentColor" } } }, ".btn-link": { "text-decoration-line": "underline", "outline-color": "currentColor", "--btn-border": "#0000", "--btn-bg": "#0000", "--btn-fg": "var(--color-primary)", "--btn-noise": "none", "--btn-shadow": '""', "&:is(.btn-active, :hover, :active:focus, :focus-visible)": { "text-decoration-line": "underline", "--btn-border": "#0000", "--btn-bg": "#0000" } }, ".btn-outline": { "&:not( .btn-active, :hover, :active:focus, :focus-visible, :disabled, [disabled], .btn-disabled, :checked )": { "--btn-shadow": '""', "--btn-bg": "#0000", "--btn-fg": "var(--btn-color)", "--btn-border": "var(--btn-color)", "--btn-noise": "none" }, "@media (hover: none)": { "&:hover:not( .btn-active, :active, :focus-visible, :disabled, [disabled], .btn-disabled, :checked )": { "--btn-shadow": '""', "--btn-bg": "#0000", "--btn-fg": "var(--btn-color)", "--btn-border": "var(--btn-color)", "--btn-noise": "none" } } }, ".btn-dash": { "&:not( .btn-active, :hover, :active:focus, :focus-visible, :disabled, [disabled], .btn-disabled, :checked )": { "--btn-shadow": '""', "border-style": "dashed", "--btn-bg": "#0000", "--btn-fg": "var(--btn-color)", "--btn-border": "var(--btn-color)", "--btn-noise": "none" }, "@media (hover: none)": { "&:hover:not( .btn-active, :active, :focus-visible, :disabled, [disabled], .btn-disabled, :checked )": { "--btn-shadow": '""', "border-style": "dashed", "--btn-bg": "#0000", "--btn-fg": "var(--btn-color)", "--btn-border": "var(--btn-color)", "--btn-noise": "none" } } }, ".btn-soft": { "&:not(.btn-active, :hover, :active:focus, :focus-visible, :disabled, [disabled], .btn-disabled)": { "--btn-shadow": '""', "--btn-fg": "var(--btn-color, var(--color-base-content))", "--btn-bg": `color-mix( + in oklab, + var(--btn-color, var(--color-base-content)) 8%, + var(--color-base-100) + )`, "--btn-border": `color-mix( + in oklab, + var(--btn-color, var(--color-base-content)) 10%, + var(--color-base-100) + )`, "--btn-noise": "none" }, "@media (hover: none)": { "&:hover:not(.btn-active, :active, :focus-visible, :disabled, [disabled], .btn-disabled)": { "--btn-shadow": '""', "--btn-fg": "var(--btn-color, var(--color-base-content))", "--btn-bg": `color-mix( + in oklab, + var(--btn-color, var(--color-base-content)) 8%, + var(--color-base-100) + )`, "--btn-border": `color-mix( + in oklab, + var(--btn-color, var(--color-base-content)) 10%, + var(--color-base-100) + )`, "--btn-noise": "none" } } }, ".btn-xs": { "--fontsize": "0.6875rem", "--btn-p": "0.5rem", "--size": "calc(var(--size-field, 0.25rem) * 6)" }, ".btn-sm": { "--fontsize": "0.75rem", "--btn-p": "0.75rem", "--size": "calc(var(--size-field, 0.25rem) * 8)" }, ".btn-md": { "--fontsize": "0.875rem", "--btn-p": "1rem", "--size": "calc(var(--size-field, 0.25rem) * 10)" }, ".btn-lg": { "--fontsize": "1.125rem", "--btn-p": "1.25rem", "--size": "calc(var(--size-field, 0.25rem) * 12)" }, ".btn-xl": { "--fontsize": "1.375rem", "--btn-p": "1.5rem", "--size": "calc(var(--size-field, 0.25rem) * 14)" }, ".btn-square": { "padding-inline": "calc(0.25rem * 0)", width: "var(--size)", height: "var(--size)" }, ".btn-circle": { "border-radius": "calc(infinity * 1px)", "padding-inline": "calc(0.25rem * 0)", width: "var(--size)", height: "var(--size)" }, ".btn-wide": { width: "100%", "max-width": "calc(0.25rem * 64)" }, ".btn-block": { width: "100%" } }; + +// packages/daisyui/components/button/index.js +var button_default = ({ addComponents, prefix = "" }) => { + const prefixedbutton = addPrefix(object_default39, prefix); + addComponents({ ...prefixedbutton }); +}; + +// packages/daisyui/components/list/object.js +var object_default40 = { ".list": { display: "flex", "flex-direction": "column", "font-size": "0.875rem", ":where(.list-row)": { "--list-grid-cols": "minmax(0, auto) 1fr", position: "relative", display: "grid", "grid-auto-flow": "column", gap: "calc(0.25rem * 4)", "border-radius": "var(--radius-box)", padding: "calc(0.25rem * 4)", "word-break": "break-word", "grid-template-columns": "var(--list-grid-cols)", "&:has(.list-col-grow:nth-child(1))": { "--list-grid-cols": "1fr" }, "&:has(.list-col-grow:nth-child(2))": { "--list-grid-cols": "minmax(0, auto) 1fr" }, "&:has(.list-col-grow:nth-child(3))": { "--list-grid-cols": "minmax(0, auto) minmax(0, auto) 1fr" }, "&:has(.list-col-grow:nth-child(4))": { "--list-grid-cols": "minmax(0, auto) minmax(0, auto) minmax(0, auto) 1fr" }, "&:has(.list-col-grow:nth-child(5))": { "--list-grid-cols": "minmax(0, auto) minmax(0, auto) minmax(0, auto) minmax(0, auto) 1fr" }, "&:has(.list-col-grow:nth-child(6))": { "--list-grid-cols": `minmax(0, auto) minmax(0, auto) minmax(0, auto) minmax(0, auto) + minmax(0, auto) 1fr` }, ":not(.list-col-wrap)": { "grid-row-start": "1" } }, "& > :not(:last-child)": { "&.list-row, .list-row": { "&:after": { content: '""', "border-bottom": "var(--border) solid", "inset-inline": "var(--radius-box)", position: "absolute", bottom: "calc(0.25rem * 0)", "border-color": "color-mix(in oklab, var(--color-base-content) 5%, transparent)" } } } }, ".list-col-wrap": { "grid-row-start": "2" } }; + +// packages/daisyui/components/list/index.js +var list_default = ({ addComponents, prefix = "" }) => { + const prefixedlist = addPrefix(object_default40, prefix); + addComponents({ ...prefixedlist }); +}; + +// packages/daisyui/components/mockup/object.js +var object_default41 = { ".mockup-code": { position: "relative", overflow: "hidden", "overflow-x": "auto", "border-radius": "var(--radius-box)", "background-color": "var(--color-neutral)", "padding-block": "calc(0.25rem * 5)", color: "var(--color-neutral-content)", "font-size": "0.875rem", direction: "ltr", "&:before": { content: '""', "margin-bottom": "calc(0.25rem * 4)", display: "block", height: "calc(0.25rem * 3)", width: "calc(0.25rem * 3)", "border-radius": "calc(infinity * 1px)", opacity: "30%", "box-shadow": "1.4em 0, 2.8em 0, 4.2em 0" }, pre: { "padding-right": "calc(0.25rem * 5)", "&:before": { content: '""', "margin-right": "2ch" }, "&[data-prefix]": { "&:before": { content: "attr(data-prefix)", display: "inline-block", width: "calc(0.25rem * 8)", "text-align": "right", opacity: "50%" } } } }, ".mockup-window": { position: "relative", display: "flex", "flex-direction": "column", overflow: "hidden", "overflow-x": "auto", "border-radius": "var(--radius-box)", "padding-top": "calc(0.25rem * 5)", "&:before": { content: '""', "margin-bottom": "calc(0.25rem * 4)", display: "block", "aspect-ratio": "1 / 1", height: "calc(0.25rem * 3)", "flex-shrink": 0, "align-self": "flex-start", "border-radius": "calc(infinity * 1px)", opacity: "30%", "box-shadow": "1.4em 0, 2.8em 0, 4.2em 0" }, '[dir="rtl"] &:before': { "align-self": "flex-end" }, "pre[data-prefix]": { "&:before": { content: "attr(data-prefix)", display: "inline-block", "text-align": "right" } } }, ".mockup-browser": { position: "relative", overflow: "hidden", "overflow-x": "auto", "border-radius": "var(--radius-box)", "pre[data-prefix]": { "&:before": { content: "attr(data-prefix)", display: "inline-block", "text-align": "right" } }, ".mockup-browser-toolbar": { "margin-block": "calc(0.25rem * 3)", display: "inline-flex", width: "100%", "align-items": "center", "padding-right": "1.4em", '&:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *)': { "flex-direction": "row-reverse" }, "&:before": { content: '""', "margin-right": "4.8rem", display: "inline-block", "aspect-ratio": "1 / 1", height: "calc(0.25rem * 3)", "border-radius": "calc(infinity * 1px)", opacity: "30%", "box-shadow": "1.4em 0, 2.8em 0, 4.2em 0" }, ".input": { "margin-inline": "auto", display: "flex", height: "100%", "align-items": "center", gap: "calc(0.25rem * 2)", overflow: "hidden", "background-color": "var(--color-base-200)", "text-overflow": "ellipsis", "white-space": "nowrap", "font-size": "0.75rem", direction: "ltr", "&:before": { content: '""', width: "calc(0.25rem * 4)", height: "calc(0.25rem * 4)", opacity: "30%", "background-image": `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='currentColor' class='size-4'%3E%3Cpath fill-rule='evenodd' d='M9.965 11.026a5 5 0 1 1 1.06-1.06l2.755 2.754a.75.75 0 1 1-1.06 1.06l-2.755-2.754ZM10.5 7a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0Z' clip-rule='evenodd' /%3E%3C/svg%3E%0A")` } } } }, ".mockup-phone": { display: "inline-grid", "justify-items": "center", border: "6px solid #6b6b6b", "border-radius": "65px", "background-color": "#000", padding: "11px", overflow: "hidden" }, ".mockup-phone-camera": { "grid-column": "1/1", "grid-row": "1/1", background: "#000", height: "32px", width: "126px", "border-radius": "17px", "z-index": 1, "margin-top": "6px" }, ".mockup-phone-display": { "grid-column": "1/1", "grid-row": "1/1", overflow: "hidden", "border-radius": "49px", width: "390px", height: "845px" } }; + +// packages/daisyui/components/mockup/index.js +var mockup_default = ({ addComponents, prefix = "" }) => { + const prefixedmockup = addPrefix(object_default41, prefix); + addComponents({ ...prefixedmockup }); +}; + +// packages/daisyui/components/calendar/object.js +var object_default42 = { ".cally": { "font-size": "0.7rem", "&::part(container)": { padding: "0.5rem 1rem", "user-select": "none" }, "::part(th)": { "font-weight": "normal", "block-size": "auto" }, "&::part(header)": { direction: "ltr" }, "::part(head)": { opacity: 0.5, "font-size": "0.7rem" }, "&::part(button)": { "border-radius": "var(--radius-field)", border: "none", padding: "0.5rem", background: "#0000" }, "&::part(button):hover": { background: "var(--color-base-200)" }, "::part(day)": { "border-radius": "var(--radius-field)", "font-size": "0.7rem" }, "::part(button day today)": { background: "var(--color-primary)", color: "var(--color-primary-content)" }, "::part(selected)": { color: "var(--color-base-100)", background: "var(--color-base-content)", "border-radius": "var(--radius-field)" }, "::part(range-inner)": { "border-radius": "0" }, "::part(range-start)": { "border-start-end-radius": "0", "border-end-end-radius": "0" }, "::part(range-end)": { "border-start-start-radius": "0", "border-end-start-radius": "0" }, "::part(range-start range-end)": { "border-radius": "var(--radius-field)" }, "calendar-month": { width: "100%" } }, ".react-day-picker": { "user-select": "none", "background-color": "var(--color-base-100)", "border-radius": "var(--radius-box)", border: "var(--border) solid var(--color-base-200)", "font-size": "0.75rem", display: "inline-block", position: "relative", overflow: "clip", '&[dir="rtl"]': { ".rdp-nav": { ".rdp-chevron": { "transform-origin": "50%", transform: "rotate(180deg)" } } }, "*": { "box-sizing": "border-box" }, ".rdp-day": { width: "2.25rem", height: "2.25rem", "text-align": "center" }, ".rdp-day_button": { cursor: "pointer", font: "inherit", color: "inherit", width: "2.25rem", height: "2.25rem", border: "2px solid #0000", "border-radius": "var(--radius-field)", background: "0 0", "justify-content": "center", "align-items": "center", margin: "0", padding: "0", display: "flex", "&:disabled": { cursor: "revert" }, "&:hover": { "background-color": "var(--color-base-200)" } }, ".rdp-caption_label": { "z-index": 1, "white-space": "nowrap", border: "0", "align-items": "center", display: "inline-flex", position: "relative" }, ".rdp-button_next": { "border-radius": "var(--radius-field)", "&:hover": { "background-color": "var(--color-base-200)" } }, ".rdp-button_previous": { "border-radius": "var(--radius-field)", "&:hover": { "background-color": "var(--color-base-200)" } }, ".rdp-button_next, .rdp-button_previous": { cursor: "pointer", font: "inherit", color: "inherit", appearance: "none", width: "2.25rem", height: "2.25rem", background: "0 0", border: "none", "justify-content": "center", "align-items": "center", margin: "0", padding: "0", display: "inline-flex", position: "relative", "&:disabled": { cursor: "revert", opacity: 0.5 } }, ".rdp-chevron": { fill: "var(--color-base-content)", width: "1rem", height: "1rem", display: "inline-block" }, ".rdp-dropdowns": { "align-items": "center", gap: "0.5rem", display: "inline-flex", position: "relative" }, ".rdp-dropdown": { "z-index": 2, opacity: 0, appearance: "none", cursor: "inherit", "line-height": "inherit", border: "none", width: "100%", margin: "0", padding: "0", position: "absolute", "inset-block": "0", "inset-inline-start": "0", "&:focus-visible": { "~ .rdp-caption_label": { outline: ["5px auto highlight", "5px auto -webkit-focus-ring-color"] } } }, ".rdp-dropdown_root": { "align-items": "center", display: "inline-flex", position: "relative", '&[data-disabled="true"]': { ".rdp-chevron": { opacity: 0.5 } } }, ".rdp-month_caption": { height: "2.75rem", "font-size": "0.75rem", "font-weight": "inherit", "place-content": "center", display: "flex" }, ".rdp-months": { gap: "2rem", "flex-wrap": "wrap", "max-width": "fit-content", padding: "0.5rem", display: "flex", position: "relative" }, ".rdp-month_grid": { "border-collapse": "collapse" }, ".rdp-nav": { height: "2.75rem", "inset-block-start": "0", "inset-inline-end": "0", "justify-content": "space-between", "align-items": "center", width: "100%", "padding-inline": "0.5rem", display: "flex", position: "absolute", top: "0.25rem" }, ".rdp-weekday": { opacity: 0.6, padding: "0.5rem 0rem", "text-align": "center", "font-size": "smaller", "font-weight": 500 }, ".rdp-week_number": { opacity: 0.6, height: "2.25rem", width: "2.25rem", border: "none", "border-radius": "100%", "text-align": "center", "font-size": "small", "font-weight": 400 }, ".rdp-today:not(.rdp-outside)": { ".rdp-day_button": { background: "var(--color-primary)", color: "var(--color-primary-content)" } }, ".rdp-selected": { "font-weight": "inherit", "font-size": "0.75rem", ".rdp-day_button": { color: "var(--color-base-100)", "background-color": "var(--color-base-content)", "border-radius": "var(--radius-field)", border: "none", "&:hover": { "background-color": "var(--color-base-content)" } } }, ".rdp-outside": { opacity: 0.75 }, ".rdp-disabled": { opacity: 0.5 }, ".rdp-hidden": { visibility: "hidden", color: "var(--color-base-content)" }, ".rdp-range_start": { ".rdp-day_button": { "border-radius": "var(--radius-field) 0 0 var(--radius-field)" } }, ".rdp-range_start .rdp-day_button": { "background-color": "var(--color-base-content)", color: "var(--color-base-content)" }, ".rdp-range_middle": { "background-color": "var(--color-base-200)" }, ".rdp-range_middle .rdp-day_button": { border: "unset", "border-radius": "unset", color: "inherit" }, ".rdp-range_end": { color: "var(--color-base-content)", ".rdp-day_button": { "border-radius": "0 var(--radius-field) var(--radius-field) 0" } }, ".rdp-range_end .rdp-day_button": { color: "var(--color-base-content)", "background-color": "var(--color-base-content)" }, ".rdp-range_start.rdp-range_end": { background: "revert" }, ".rdp-focusable": { cursor: "pointer" }, ".rdp-footer": { "border-top": "var(--border) solid var(--color-base-200)", padding: "0.5rem" } }, ".pika-single": { "&:is(div)": { "user-select": "none", "font-size": "0.75rem", "z-index": 999, display: "inline-block", position: "relative", color: "var(--color-base-content)", "background-color": "var(--color-base-100)", "border-radius": "var(--radius-box)", border: "var(--border) solid var(--color-base-200)", padding: "0.5rem", "&:before, &:after": { content: '""', display: "table" }, "&:after": { clear: "both" }, "&.is-hidden": { display: "none" }, "&.is-bound": { position: "absolute" }, ".pika-lendar": { "css-float": "left" }, ".pika-title": { position: "relative", "text-align": "center", select: { cursor: "pointer", position: "absolute", "z-index": 999, margin: "0", left: "0", top: "5px", opacity: 0 } }, ".pika-label": { display: "inline-block", position: "relative", "z-index": 999, overflow: "hidden", margin: "0", padding: "5px 3px", "background-color": "var(--color-base-100)" }, ".pika-prev, .pika-next": { display: "block", cursor: "pointer", position: "absolute", top: "0", outline: "none", border: "0", width: "2.25rem", height: "2.25rem", color: "#0000", "font-size": "1.2em", "border-radius": "var(--radius-field)", "&:hover": { "background-color": "var(--color-base-200)" }, "&.is-disabled": { cursor: "default", opacity: 0.2 }, "&:before": { display: "inline-block", width: "2.25rem", height: "2.25rem", "line-height": 2.25, color: "var(--color-base-content)" } }, ".pika-prev": { left: "0", "&:before": { content: '"‹"' } }, ".pika-next": { right: "0", "&:before": { content: '"›"' } }, ".pika-select": { display: "inline-block" }, ".pika-table": { width: "100%", "border-collapse": "collapse", "border-spacing": "0", border: "0", "th, td": { padding: "0" }, th: { opacity: 0.6, "text-align": "center", width: "2.25rem", height: "2.25rem" } }, ".pika-button": { cursor: "pointer", display: "block", outline: "none", border: "0", margin: "0", width: "2.25rem", height: "2.25rem", padding: "5px", "text-align": ["right", "center"] }, ".pika-week": { color: "var(--color-base-content)" }, ".is-today": { ".pika-button": { background: "var(--color-primary)", color: "var(--color-primary-content)" } }, ".is-selected, .has-event": { ".pika-button": { "&, &:hover": { color: "var(--color-base-100)", "background-color": "var(--color-base-content)", "border-radius": "var(--radius-field)" } } }, ".has-event": { ".pika-button": { background: "var(--color-base-primary)" } }, ".is-disabled, .is-inrange": { ".pika-button": { background: "var(--color-base-primary)" } }, ".is-startrange": { ".pika-button": { color: "var(--color-base-100)", background: "var(--color-base-content)", "border-radius": "var(--radius-field)" } }, ".is-endrange": { ".pika-button": { color: "var(--color-base-100)", background: "var(--color-base-content)", "border-radius": "var(--radius-field)" } }, ".is-disabled": { ".pika-button": { "pointer-events": "none", cursor: "default", color: "var(--color-base-content)", opacity: 0.3 } }, ".is-outside-current-month": { ".pika-button": { color: "var(--color-base-content)", opacity: 0.3 } }, ".is-selection-disabled": { "pointer-events": "none", cursor: "default" }, ".pika-button:hover, .pika-row.pick-whole-week:hover .pika-button": { color: "var(--color-base-content)", "background-color": "var(--color-base-200)", "border-radius": "var(--radius-field)" }, ".pika-table abbr": { "text-decoration": "none", "font-weight": "normal" } } } }; + +// packages/daisyui/components/calendar/index.js +var calendar_default = ({ addComponents, prefix = "" }) => { + const prefixedcalendar = addPrefix(object_default42, prefix); + addComponents({ ...prefixedcalendar }); +}; + +// packages/daisyui/components/indicator/object.js +var object_default43 = { ".indicator": { position: "relative", display: "inline-flex", width: "max-content", ":where(.indicator-item)": { "z-index": 1, position: "absolute", "white-space": "nowrap", top: "var(--inidicator-t, 0)", bottom: "var(--inidicator-b, auto)", left: "var(--inidicator-s, auto)", right: "var(--inidicator-e, 0)", translate: "var(--inidicator-x, 50%) var(--indicator-y, -50%)" } }, ".indicator-start": { "--inidicator-s": "0", "--inidicator-e": "auto", "--inidicator-x": "-50%" }, ".indicator-center": { "--inidicator-s": "50%", "--inidicator-e": "50%", "--inidicator-x": "-50%", '[dir="rtl"] &': { "--inidicator-x": "50%" } }, ".indicator-end": { "--inidicator-s": "auto", "--inidicator-e": "0", "--inidicator-x": "50%" }, ".indicator-bottom": { "--inidicator-t": "auto", "--inidicator-b": "0", "--indicator-y": "50%" }, ".indicator-middle": { "--inidicator-t": "50%", "--inidicator-b": "50%", "--indicator-y": "-50%" }, ".indicator-top": { "--inidicator-t": "0", "--inidicator-b": "auto", "--indicator-y": "-50%" } }; + +// packages/daisyui/components/indicator/index.js +var indicator_default = ({ addComponents, prefix = "" }) => { + const prefixedindicator = addPrefix(object_default43, prefix); + addComponents({ ...prefixedindicator }); +}; + +// packages/daisyui/components/rating/object.js +var object_default44 = { ".rating": { position: "relative", display: "inline-flex", "vertical-align": "middle", "& input": { border: "none", appearance: "none" }, ":where(*)": { animation: "rating 0.25s ease-out", height: "calc(0.25rem * 6)", width: "calc(0.25rem * 6)", "border-radius": "0", "background-color": "var(--color-base-content)", opacity: "20%", "&:is(input)": { cursor: "pointer" } }, "& .rating-hidden": { width: "calc(0.25rem * 2)", "background-color": "transparent" }, 'input[type="radio"]:checked': { "background-image": "none" }, "*": { '&:checked, &[aria-checked="true"], &[aria-current="true"], &:has(~ *:checked, ~ *[aria-checked="true"], ~ *[aria-current="true"])': { opacity: "100%" }, "&:focus-visible": { transition: "scale 0.2s ease-out", scale: "1.1" } }, "& *:active:focus": { animation: "none", scale: "1.1" }, "&.rating-xs :where(*:not(.rating-hidden))": { width: "calc(0.25rem * 4)", height: "calc(0.25rem * 4)" }, "&.rating-sm :where(*:not(.rating-hidden))": { width: "calc(0.25rem * 5)", height: "calc(0.25rem * 5)" }, "&.rating-md :where(*:not(.rating-hidden))": { width: "calc(0.25rem * 6)", height: "calc(0.25rem * 6)" }, "&.rating-lg :where(*:not(.rating-hidden))": { width: "calc(0.25rem * 7)", height: "calc(0.25rem * 7)" }, "&.rating-xl :where(*:not(.rating-hidden))": { width: "calc(0.25rem * 8)", height: "calc(0.25rem * 8)" } }, ".rating-half": { ":where(*:not(.rating-hidden))": { width: "calc(0.25rem * 3)" }, "&.rating-xs *:not(.rating-hidden)": { width: "calc(0.25rem * 2)" }, "&.rating-sm *:not(.rating-hidden)": { width: "calc(0.25rem * 2.5)" }, "&.rating-md *:not(.rating-hidden)": { width: "calc(0.25rem * 3)" }, "&.rating-lg *:not(.rating-hidden)": { width: ".875rem" }, "&.rating-xl *:not(.rating-hidden)": { width: "calc(0.25rem * 4)" } }, "@keyframes rating": { "0%, 40%": { scale: "1.1", filter: "brightness(1.05) contrast(1.05)" } } }; + +// packages/daisyui/components/rating/index.js +var rating_default = ({ addComponents, prefix = "" }) => { + const prefixedrating = addPrefix(object_default44, prefix); + addComponents({ ...prefixedrating }); +}; + +// packages/daisyui/components/tab/object.js +var object_default45 = { ".tabs": { display: "flex", "flex-wrap": "wrap", "--tabs-height": "auto", "--tabs-direction": "row", height: "var(--tabs-height)", "flex-direction": "var(--tabs-direction)" }, ".tab": { position: "relative", display: "inline-flex", cursor: "pointer", appearance: "none", "flex-wrap": "wrap", "align-items": "center", "justify-content": "center", "text-align": "center", "webkit-user-select": "none", "user-select": "none", "&:hover": { "@media (hover: hover)": { color: "var(--color-base-content)" } }, "--tab-p": "1rem", "--tab-bg": "var(--color-base-100)", "--tab-border-color": "var(--color-base-300)", "--tab-radius-ss": "0", "--tab-radius-se": "0", "--tab-radius-es": "0", "--tab-radius-ee": "0", "--tab-order": "0", "--tab-radius-min": "calc(0.75rem - var(--border))", "border-color": "#0000", order: "var(--tab-order)", height: "calc(var(--size-field, 0.25rem) * 10)", "font-size": "0.875rem", "padding-inline-start": "var(--tab-p)", "padding-inline-end": "var(--tab-p)", '&:is(input[type="radio"])': { "min-width": "fit-content", "&:after": { content: "attr(aria-label)" } }, "&:is(label)": { position: "relative", input: { position: "absolute", inset: "calc(0.25rem * 0)", cursor: "pointer", appearance: "none", opacity: "0%" } }, '&:checked, &:is(label:has(:checked)), &:is(.tab-active, [aria-selected="true"])': { "& + .tab-content": { display: "block", height: "100%" } }, '&:not(:checked, label:has(:checked), :hover, .tab-active, [aria-selected="true"])': { color: "color-mix(in oklab, var(--color-base-content) 50%, transparent)" }, "&:not(input):empty": { "flex-grow": 1, cursor: "default" }, "&:focus": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, "&:focus-visible, &:is(label:has(:checked:focus-visible))": { outline: "2px solid currentColor", "outline-offset": "-5px" }, "&[disabled]": { "pointer-events": "none", opacity: "40%" } }, ".tab-disabled": { "pointer-events": "none", opacity: "40%" }, ".tabs-border": { ".tab": { "--tab-border-color": "#0000 #0000 var(--tab-border-color) #0000", position: "relative", "border-radius": "var(--radius-field)", "&:before": { "--tw-content": '""', content: "var(--tw-content)", "background-color": "var(--tab-border-color)", transition: "background-color 0.2s ease", width: "80%", height: "3px", "border-radius": "var(--radius-field)", bottom: "0", left: "10%", position: "absolute" }, '&:is(.tab-active, [aria-selected="true"]):not(.tab-disabled, [disabled]), &:is(input:checked), &:is(label:has(:checked))': { "&:before": { "--tab-border-color": "currentColor", "border-top": "3px solid" } } } }, ".tabs-lift": { "--tabs-height": "auto", "--tabs-direction": "row", "> .tab": { "--tab-border": "0 0 var(--border) 0", "--tab-radius-ss": "min(var(--radius-field), var(--tab-radius-min))", "--tab-radius-se": "min(var(--radius-field), var(--tab-radius-min))", "--tab-radius-es": "0", "--tab-radius-ee": "0", "--tab-paddings": "var(--border) var(--tab-p) 0 var(--tab-p)", "--tab-border-colors": "#0000 #0000 var(--tab-border-color) #0000", "--tab-corner-width": "calc(100% + min(var(--radius-field), var(--tab-radius-min)) * 2)", "--tab-corner-height": "min(var(--radius-field), var(--tab-radius-min))", "--tab-corner-position": "top left, top right", "border-width": "var(--tab-border)", "border-start-start-radius": "var(--tab-radius-ss)", "border-start-end-radius": "var(--tab-radius-se)", "border-end-start-radius": "var(--tab-radius-es)", "border-end-end-radius": "var(--tab-radius-ee)", padding: "var(--tab-paddings)", "border-color": "var(--tab-border-colors)", '&:is(.tab-active, [aria-selected="true"]):not(.tab-disabled, [disabled]), &:is(input:checked, label:has(:checked))': { "--tab-border": "var(--border) var(--border) 0 var(--border)", "--tab-border-colors": `var(--tab-border-color) var(--tab-border-color) #0000 + var(--tab-border-color)`, "--tab-paddings": `0 calc(var(--tab-p) - var(--border)) var(--border) + calc(var(--tab-p) - var(--border))`, "--tab-inset": "auto auto 0 auto", "--tab-grad": "calc(69% - var(--border))", "--radius-start": `radial-gradient( + circle at top left, + #0000 var(--tab-grad), + var(--tab-border-color) calc(var(--tab-grad) + 0.25px), + var(--tab-border-color) calc(var(--tab-grad) + var(--border)), + var(--tab-bg) calc(var(--tab-grad) + var(--border) + 0.25px) + )`, "--radius-end": `radial-gradient( + circle at top right, + #0000 var(--tab-grad), + var(--tab-border-color) calc(var(--tab-grad) + 0.25px), + var(--tab-border-color) calc(var(--tab-grad) + var(--border)), + var(--tab-bg) calc(var(--tab-grad) + var(--border) + 0.25px) + )`, "background-color": "var(--tab-bg)", "&:before": { "z-index": 1, content: '""', display: "block", position: "absolute", width: "var(--tab-corner-width)", height: "var(--tab-corner-height)", "background-position": "var(--tab-corner-position)", "background-image": "var(--radius-start), var(--radius-end)", "background-size": "min(var(--radius-field), var(--tab-radius-min)) min(var(--radius-field), var(--tab-radius-min))", "background-repeat": "no-repeat", inset: "var(--tab-inset)" }, "&:first-child:before": { "--radius-start": "none" }, '[dir="rtl"] &:first-child:before': { transform: "rotateY(180deg)" }, "&:last-child:before": { "--radius-end": "none" }, '[dir="rtl"] &:last-child:before': { transform: "rotateY(180deg)" } } }, "&:has(.tab-content)": { "> .tab:first-child": { '&:not(.tab-active, [aria-selected="true"])': { "--tab-border-colors": `var(--tab-border-color) var(--tab-border-color) #0000 + var(--tab-border-color)` } } }, ".tab-content": { "--tabcontent-margin": "calc(-1 * var(--border)) 0 0 0", "--tabcontent-radius-ss": "0", "--tabcontent-radius-se": "var(--radius-box)", "--tabcontent-radius-es": "var(--radius-box)", "--tabcontent-radius-ee": "var(--radius-box)" }, ':checked, label:has(:checked), :is(.tab-active, [aria-selected="true"])': { "& + .tab-content": { "&:nth-child(1), &:nth-child(n + 3)": { "--tabcontent-radius-ss": "var(--radius-box)" } } } }, ".tabs-top": { "--tabs-height": "auto", "--tabs-direction": "row", ".tab": { "--tab-order": "0", "--tab-border": "0 0 var(--border) 0", "--tab-radius-ss": "min(var(--radius-field), var(--tab-radius-min))", "--tab-radius-se": "min(var(--radius-field), var(--tab-radius-min))", "--tab-radius-es": "0", "--tab-radius-ee": "0", "--tab-paddings": "var(--border) var(--tab-p) 0 var(--tab-p)", "--tab-border-colors": "#0000 #0000 var(--tab-border-color) #0000", "--tab-corner-width": "calc(100% + min(var(--radius-field), var(--tab-radius-min)) * 2)", "--tab-corner-height": "min(var(--radius-field), var(--tab-radius-min))", "--tab-corner-position": "top left, top right", '&:is(.tab-active, [aria-selected="true"]):not(.tab-disabled, [disabled]), &:is(input:checked), &:is(label:has(:checked))': { "--tab-border": "var(--border) var(--border) 0 var(--border)", "--tab-border-colors": `var(--tab-border-color) var(--tab-border-color) #0000 + var(--tab-border-color)`, "--tab-paddings": `0 calc(var(--tab-p) - var(--border)) var(--border) + calc(var(--tab-p) - var(--border))`, "--tab-inset": "auto auto 0 auto", "--radius-start": `radial-gradient( + circle at top left, + #0000 var(--tab-grad), + var(--tab-border-color) calc(var(--tab-grad) + 0.25px), + var(--tab-border-color) calc(var(--tab-grad) + var(--border)), + var(--tab-bg) calc(var(--tab-grad) + var(--border) + 0.25px) + )`, "--radius-end": `radial-gradient( + circle at top right, + #0000 var(--tab-grad), + var(--tab-border-color) calc(var(--tab-grad) + 0.25px), + var(--tab-border-color) calc(var(--tab-grad) + var(--border)), + var(--tab-bg) calc(var(--tab-grad) + var(--border) + 0.25px) + )` } }, "&:has(.tab-content)": { "> .tab:first-child": { '&:not(.tab-active, [aria-selected="true"])': { "--tab-border-colors": `var(--tab-border-color) var(--tab-border-color) #0000 + var(--tab-border-color)` } } }, ".tab-content": { "--tabcontent-order": "1", "--tabcontent-margin": "calc(-1 * var(--border)) 0 0 0", "--tabcontent-radius-ss": "0", "--tabcontent-radius-se": "var(--radius-box)", "--tabcontent-radius-es": "var(--radius-box)", "--tabcontent-radius-ee": "var(--radius-box)" }, ':checked, label:has(:checked), :is(.tab-active, [aria-selected="true"])': { "& + .tab-content": { "&:nth-child(1), &:nth-child(n + 3)": { "--tabcontent-radius-ss": "var(--radius-box)" } } } }, ".tabs-bottom": { "--tabs-height": "auto", "--tabs-direction": "row", ".tab": { "--tab-order": "1", "--tab-border": "var(--border) 0 0 0", "--tab-radius-ss": "0", "--tab-radius-se": "0", "--tab-radius-es": "min(var(--radius-field), var(--tab-radius-min))", "--tab-radius-ee": "min(var(--radius-field), var(--tab-radius-min))", "--tab-border-colors": "var(--tab-border-color) #0000 #0000 #0000", "--tab-paddings": "0 var(--tab-p) var(--border) var(--tab-p)", "--tab-corner-width": "calc(100% + min(var(--radius-field), var(--tab-radius-min)) * 2)", "--tab-corner-height": "min(var(--radius-field), var(--tab-radius-min))", "--tab-corner-position": "top left, top right", '&:is(.tab-active, [aria-selected="true"]):not(.tab-disabled, [disabled]), &:is(input:checked), &:is(label:has(:checked))': { "--tab-border": "0 var(--border) var(--border) var(--border)", "--tab-border-colors": `#0000 var(--tab-border-color) var(--tab-border-color) + var(--tab-border-color)`, "--tab-paddings": `var(--border) calc(var(--tab-p) - var(--border)) 0 + calc(var(--tab-p) - var(--border))`, "--tab-inset": "0 auto auto auto", "--radius-start": `radial-gradient( + circle at bottom left, + #0000 var(--tab-grad), + var(--tab-border-color) calc(var(--tab-grad) + 0.25px), + var(--tab-border-color) calc(var(--tab-grad) + var(--border)), + var(--tab-bg) calc(var(--tab-grad) + var(--border) + 0.25px) + )`, "--radius-end": `radial-gradient( + circle at bottom right, + #0000 var(--tab-grad), + var(--tab-border-color) calc(var(--tab-grad) + 0.25px), + var(--tab-border-color) calc(var(--tab-grad) + var(--border)), + var(--tab-bg) calc(var(--tab-grad) + var(--border) + 0.25px) + )` } }, "&:has(.tab-content)": { "> .tab:first-child": { '&:not(.tab-active, [aria-selected="true"])': { "--tab-border-colors": `#0000 var(--tab-border-color) var(--tab-border-color) + var(--tab-border-color)` } } }, ".tab-content": { "--tabcontent-order": "0", "--tabcontent-margin": "0 0 calc(-1 * var(--border)) 0", "--tabcontent-radius-ss": "var(--radius-box)", "--tabcontent-radius-se": "var(--radius-box)", "--tabcontent-radius-es": "0", "--tabcontent-radius-ee": "var(--radius-box)" }, '> :checked, > :is(label:has(:checked)), > :is(.tab-active, [aria-selected="true"])': { "& + .tab-content:not(:nth-child(2))": { "--tabcontent-radius-es": "var(--radius-box)" } } }, ".tabs-box": { "background-color": "var(--color-base-200)", padding: "calc(0.25rem * 1)", "--tabs-box-radius": "calc(var(--radius-field) + var(--radius-field) + var(--radius-field))", "border-radius": "calc(var(--radius-field) + min(0.25rem, var(--tabs-box-radius)))", "box-shadow": "0 -0.5px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 0.5px oklch(0% 0 0 / calc(var(--depth) * 0.05)) inset", ".tab": { "border-radius": "var(--radius-field)", "border-style": "none", "&:focus-visible, &:is(label:has(:checked:focus-visible))": { "outline-offset": "2px" } }, '> :is(.tab-active, [aria-selected="true"]):not(.tab-disabled, [disabled]), > :is(input:checked), > :is(label:has(:checked))': { "background-color": "var(--tab-bg, var(--color-base-100))", "box-shadow": "0 1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px 1px -1px color-mix(in oklab, var(--color-neutral) calc(var(--depth) * 50%), #0000), 0 1px 6px -4px color-mix(in oklab, var(--color-neutral) calc(var(--depth) * 100%), #0000)", "@media (forced-colors: active)": { border: "1px solid" } } }, ".tab-content": { order: [1, "var(--tabcontent-order)"], display: "none", "border-color": "transparent", "--tabcontent-radius-ss": "0", "--tabcontent-radius-se": "0", "--tabcontent-radius-es": "0", "--tabcontent-radius-ee": "0", "--tabcontent-order": "1", width: "100%", margin: "var(--tabcontent-margin)", "border-width": "var(--border)", "border-start-start-radius": "var(--tabcontent-radius-ss)", "border-start-end-radius": "var(--tabcontent-radius-se)", "border-end-start-radius": "var(--tabcontent-radius-es)", "border-end-end-radius": "var(--tabcontent-radius-ee)" }, ".tabs-xs": { ":where(.tab)": { height: "calc(var(--size-field, 0.25rem) * 6)", "font-size": "0.75rem", "--tab-p": "0.375rem", "--tab-radius-min": "calc(0.5rem - var(--border))" } }, ".tabs-sm": { ":where(.tab)": { height: "calc(var(--size-field, 0.25rem) * 8)", "font-size": "0.875rem", "--tab-p": "0.5rem", "--tab-radius-min": "calc(0.5rem - var(--border))" } }, ".tabs-md": { ":where(.tab)": { height: "calc(var(--size-field, 0.25rem) * 10)", "font-size": "0.875rem", "--tab-p": "0.75rem", "--tab-radius-min": "calc(0.75rem - var(--border))" } }, ".tabs-lg": { ":where(.tab)": { height: "calc(var(--size-field, 0.25rem) * 12)", "font-size": "1.125rem", "--tab-p": "1rem", "--tab-radius-min": "calc(1.5rem - var(--border))" } }, ".tabs-xl": { ":where(.tab)": { height: "calc(var(--size-field, 0.25rem) * 14)", "font-size": "1.125rem", "--tab-p": "1.25rem", "--tab-radius-min": "calc(2rem - var(--border))" } } }; + +// packages/daisyui/components/tab/index.js +var tab_default = ({ addComponents, prefix = "" }) => { + const prefixedtab = addPrefix(object_default45, prefix); + addComponents({ ...prefixedtab }); +}; + +// packages/daisyui/components/filter/object.js +var object_default46 = { ".filter": { display: "flex", "flex-wrap": "wrap", 'input[type="radio"]': { width: "auto" }, input: { overflow: "hidden", opacity: "100%", scale: "1", transition: "margin 0.1s, opacity 0.3s, padding 0.3s, border-width 0.1s", "&:not(:last-child)": { "margin-inline-end": "calc(0.25rem * 1)" }, "&.filter-reset": { "aspect-ratio": "1 / 1", "&::after": { content: '"×"' } } }, "&:not(:has(input:checked:not(.filter-reset)))": { '.filter-reset, input[type="reset"]': { scale: "0", "border-width": "0", "margin-inline": "calc(0.25rem * 0)", width: "calc(0.25rem * 0)", "padding-inline": "calc(0.25rem * 0)", opacity: "0%" } }, "&:has(input:checked:not(.filter-reset))": { 'input:not(:checked, .filter-reset, input[type="reset"])': { scale: "0", "border-width": "0", "margin-inline": "calc(0.25rem * 0)", width: "calc(0.25rem * 0)", "padding-inline": "calc(0.25rem * 0)", opacity: "0%" } } } }; + +// packages/daisyui/components/filter/index.js +var filter_default = ({ addComponents, prefix = "" }) => { + const prefixedfilter = addPrefix(object_default46, prefix); + addComponents({ ...prefixedfilter }); +}; + +// packages/daisyui/components/chat/object.js +var object_default47 = { ".chat": { display: "grid", "column-gap": "calc(0.25rem * 3)", "padding-block": "calc(0.25rem * 1)" }, ".chat-bubble": { position: "relative", display: "block", width: "fit-content", "border-radius": "var(--radius-field)", "background-color": "var(--color-base-300)", "padding-inline": "calc(0.25rem * 4)", "padding-block": "calc(0.25rem * 2)", color: "var(--color-base-content)", "grid-row-end": "3", "min-height": "2rem", "min-width": "2.5rem", "max-width": "90%", "&:before": { position: "absolute", bottom: "calc(0.25rem * 0)", height: "calc(0.25rem * 3)", width: "calc(0.25rem * 3)", "background-color": "inherit", content: '""', "mask-repeat": "no-repeat", "mask-image": "var(--mask-chat)", "mask-position": "0px -1px", "mask-size": "13px" } }, ".chat-bubble-primary": { "background-color": "var(--color-primary)", color: "var(--color-primary-content)" }, ".chat-bubble-secondary": { "background-color": "var(--color-secondary)", color: "var(--color-secondary-content)" }, ".chat-bubble-accent": { "background-color": "var(--color-accent)", color: "var(--color-accent-content)" }, ".chat-bubble-neutral": { "background-color": "var(--color-neutral)", color: "var(--color-neutral-content)" }, ".chat-bubble-info": { "background-color": "var(--color-info)", color: "var(--color-info-content)" }, ".chat-bubble-success": { "background-color": "var(--color-success)", color: "var(--color-success-content)" }, ".chat-bubble-warning": { "background-color": "var(--color-warning)", color: "var(--color-warning-content)" }, ".chat-bubble-error": { "background-color": "var(--color-error)", color: "var(--color-error-content)" }, ".chat-image": { "grid-row": "span 2 / span 2", "align-self": "flex-end" }, ".chat-header": { "grid-row-start": "1", display: "flex", gap: "calc(0.25rem * 1)", "font-size": "0.6875rem" }, ".chat-footer": { "grid-row-start": "3", display: "flex", gap: "calc(0.25rem * 1)", "font-size": "0.6875rem" }, ".chat-start": { "place-items": "start", "grid-template-columns": "auto 1fr", ".chat-header": { "grid-column-start": "2" }, ".chat-footer": { "grid-column-start": "2" }, ".chat-image": { "grid-column-start": "1" }, ".chat-bubble": { "grid-column-start": "2", "border-end-start-radius": "0", "&:before": { transform: "rotateY(0deg)", "inset-inline-start": "-0.75rem" }, '[dir="rtl"] &:before': { transform: "rotateY(180deg)" } } }, ".chat-end": { "place-items": "end", "grid-template-columns": "1fr auto", ".chat-header": { "grid-column-start": "1" }, ".chat-footer": { "grid-column-start": "1" }, ".chat-image": { "grid-column-start": "2" }, ".chat-bubble": { "grid-column-start": "1", "border-end-end-radius": "0", "&:before": { transform: "rotateY(180deg)", "inset-inline-start": "100%" }, '[dir="rtl"] &:before': { transform: "rotateY(0deg)" } } } }; + +// packages/daisyui/components/chat/index.js +var chat_default = ({ addComponents, prefix = "" }) => { + const prefixedchat = addPrefix(object_default47, prefix); + addComponents({ ...prefixedchat }); +}; + +// packages/daisyui/components/radialprogress/object.js +var object_default48 = { ".radial-progress": { position: "relative", display: "inline-grid", height: "var(--size)", width: "var(--size)", "place-content": "center", "border-radius": "calc(infinity * 1px)", "background-color": "transparent", "vertical-align": "middle", "box-sizing": "content-box", "--value": "0", "--size": "5rem", "--thickness": "calc(var(--size) / 10)", "--radialprogress": "calc(var(--value) * 1%)", transition: "--radialprogress 0.3s linear", "&:before": { position: "absolute", inset: "calc(0.25rem * 0)", "border-radius": "calc(infinity * 1px)", content: '""', background: "radial-gradient(farthest-side, currentColor 98%, #0000) top/var(--thickness) var(--thickness) no-repeat, conic-gradient(currentColor var(--radialprogress), #0000 0)", "webkit-mask": "radial-gradient( farthest-side, #0000 calc(100% - var(--thickness)), #000 calc(100% + 0.5px - var(--thickness)) )", mask: "radial-gradient( farthest-side, #0000 calc(100% - var(--thickness)), #000 calc(100% + 0.5px - var(--thickness)) )" }, "&:after": { position: "absolute", "border-radius": "calc(infinity * 1px)", "background-color": "currentColor", transition: "transform 0.3s linear", content: '""', inset: "calc(50% - var(--thickness) / 2)", transform: "rotate(calc(var(--value) * 3.6deg - 90deg)) translate(calc(var(--size) / 2 - 50%))" } } }; + +// packages/daisyui/components/radialprogress/index.js +var radialprogress_default = ({ addComponents, prefix = "" }) => { + const prefixedradialprogress = addPrefix(object_default48, prefix); + addComponents({ ...prefixedradialprogress }); +}; + +// packages/daisyui/components/countdown/object.js +var object_default49 = { ".countdown": { display: "inline-flex", "&.countdown": { "line-height": "1em" }, "& > *": { display: "inline-block", "overflow-y": "hidden", height: "1em", "&:before": { position: "relative", content: '"00\\A 01\\A 02\\A 03\\A 04\\A 05\\A 06\\A 07\\A 08\\A 09\\A 10\\A 11\\A 12\\A 13\\A 14\\A 15\\A 16\\A 17\\A 18\\A 19\\A 20\\A 21\\A 22\\A 23\\A 24\\A 25\\A 26\\A 27\\A 28\\A 29\\A 30\\A 31\\A 32\\A 33\\A 34\\A 35\\A 36\\A 37\\A 38\\A 39\\A 40\\A 41\\A 42\\A 43\\A 44\\A 45\\A 46\\A 47\\A 48\\A 49\\A 50\\A 51\\A 52\\A 53\\A 54\\A 55\\A 56\\A 57\\A 58\\A 59\\A 60\\A 61\\A 62\\A 63\\A 64\\A 65\\A 66\\A 67\\A 68\\A 69\\A 70\\A 71\\A 72\\A 73\\A 74\\A 75\\A 76\\A 77\\A 78\\A 79\\A 80\\A 81\\A 82\\A 83\\A 84\\A 85\\A 86\\A 87\\A 88\\A 89\\A 90\\A 91\\A 92\\A 93\\A 94\\A 95\\A 96\\A 97\\A 98\\A 99\\A"', "white-space": "pre", top: "calc(var(--value) * -1em)", "text-align": "center", transition: "all 1s cubic-bezier(1, 0, 0, 1)" } } } }; + +// packages/daisyui/components/countdown/index.js +var countdown_default = ({ addComponents, prefix = "" }) => { + const prefixedcountdown = addPrefix(object_default49, prefix); + addComponents({ ...prefixedcountdown }); +}; + +// packages/daisyui/components/tooltip/object.js +var object_default50 = { ".tooltip": { position: "relative", display: "inline-block", "--tt-bg": "var(--color-neutral)", "--tt-off": "calc(100% + 0.5rem)", "--tt-tail": "calc(100% + 1px + 0.25rem)", "> :where(.tooltip-content), &:where([data-tip]):before": { position: "absolute", "max-width": "20rem", "border-radius": "var(--radius-field)", "padding-inline": "calc(0.25rem * 2)", "padding-block": "calc(0.25rem * 1)", "text-align": "center", "white-space": "normal", color: "var(--color-neutral-content)", opacity: "0%", "font-size": "0.875rem", "line-height": 1.25, transition: "opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1) 75ms, transform 0.2s cubic-bezier(0.4, 0, 0.2, 1) 75ms", "background-color": "var(--tt-bg)", width: "max-content", "pointer-events": "none", "z-index": 1, "--tw-content": "attr(data-tip)", content: "var(--tw-content)" }, "&:after": { position: ["absolute", "absolute"], opacity: "0%", "background-color": "var(--tt-bg)", transition: "opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1) 75ms, transform 0.2s cubic-bezier(0.4, 0, 0.2, 1) 75ms", content: '""', "pointer-events": "none", width: "0.625rem", height: "0.25rem", display: "block", "mask-repeat": "no-repeat", "mask-position": "-1px 0", "--mask-tooltip": `url("data:image/svg+xml,%3Csvg width='10' height='4' viewBox='0 0 8 4' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0.500009 1C3.5 1 3.00001 4 5.00001 4C7 4 6.5 1 9.5 1C10 1 10 0.499897 10 0H0C-1.99338e-08 0.5 0 1 0.500009 1Z' fill='black'/%3E%3C/svg%3E%0A")`, "mask-image": "var(--mask-tooltip)" }, '&.tooltip-open, &[data-tip]:not([data-tip=""]):hover, &:not(:has(.tooltip-content:empty)):has(.tooltip-content):hover, &:has(:focus-visible)': { "> .tooltip-content, &[data-tip]:before, &:after": { opacity: "100%", "--tt-pos": "0rem", transition: "opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1) 0s, transform 0.2s cubic-bezier(0.4, 0, 0.2, 1) 0ms" } } }, ".tooltip, .tooltip-top": { "> .tooltip-content, &[data-tip]:before": { transform: "translateX(-50%) translateY(var(--tt-pos, 0.25rem))", inset: "auto auto var(--tt-off) 50%" }, "&:after": { transform: "translateX(-50%) translateY(var(--tt-pos, 0.25rem))", inset: "auto auto var(--tt-tail) 50%" } }, ".tooltip-bottom": { "> .tooltip-content, &[data-tip]:before": { transform: "translateX(-50%) translateY(var(--tt-pos, -0.25rem))", inset: "var(--tt-off) auto auto 50%" }, "&:after": { transform: "translateX(-50%) translateY(var(--tt-pos, -0.25rem)) rotate(180deg)", inset: "var(--tt-tail) auto auto 50%" } }, ".tooltip-left": { "> .tooltip-content, &[data-tip]:before": { transform: "translateX(calc(var(--tt-pos, 0.25rem) - 0.25rem)) translateY(-50%)", inset: "50% var(--tt-off) auto auto" }, "&:after": { transform: "translateX(var(--tt-pos, 0.25rem)) translateY(-50%) rotate(-90deg)", inset: "50% calc(var(--tt-tail) + 1px) auto auto" } }, ".tooltip-right": { "> .tooltip-content, &[data-tip]:before": { transform: "translateX(calc(var(--tt-pos, -0.25rem) + 0.25rem)) translateY(-50%)", inset: "50% auto auto var(--tt-off)" }, "&:after": { transform: "translateX(var(--tt-pos, -0.25rem)) translateY(-50%) rotate(90deg)", inset: "50% auto auto calc(var(--tt-tail) + 1px)" } }, ".tooltip-primary": { "--tt-bg": "var(--color-primary)", "> .tooltip-content, &[data-tip]:before": { color: "var(--color-primary-content)" } }, ".tooltip-secondary": { "--tt-bg": "var(--color-secondary)", "> .tooltip-content, &[data-tip]:before": { color: "var(--color-secondary-content)" } }, ".tooltip-accent": { "--tt-bg": "var(--color-accent)", "> .tooltip-content, &[data-tip]:before": { color: "var(--color-accent-content)" } }, ".tooltip-info": { "--tt-bg": "var(--color-info)", "> .tooltip-content, &[data-tip]:before": { color: "var(--color-info-content)" } }, ".tooltip-success": { "--tt-bg": "var(--color-success)", "> .tooltip-content, &[data-tip]:before": { color: "var(--color-success-content)" } }, ".tooltip-warning": { "--tt-bg": "var(--color-warning)", "> .tooltip-content, &[data-tip]:before": { color: "var(--color-warning-content)" } }, ".tooltip-error": { "--tt-bg": "var(--color-error)", "> .tooltip-content, &[data-tip]:before": { color: "var(--color-error-content)" } } }; + +// packages/daisyui/components/tooltip/index.js +var tooltip_default = ({ addComponents, prefix = "" }) => { + const prefixedtooltip = addPrefix(object_default50, prefix); + addComponents({ ...prefixedtooltip }); +}; + +// packages/daisyui/components/timeline/object.js +var object_default51 = { ".timeline": { position: "relative", display: "flex", "> li": { position: "relative", display: "grid", "flex-shrink": 0, "align-items": "center", "grid-template-rows": "var(--timeline-row-start, minmax(0, 1fr)) auto var( --timeline-row-end, minmax(0, 1fr) )", "grid-template-columns": "var(--timeline-col-start, minmax(0, 1fr)) auto var( --timeline-col-end, minmax(0, 1fr) )", "> hr": { border: "none", width: "100%", "&:first-child": { "grid-column-start": "1", "grid-row-start": "2" }, "&:last-child": { "grid-column-start": "3", "grid-column-end": "none", "grid-row-start": "2", "grid-row-end": "auto" }, "@media print": { border: "0.1px solid var(--color-base-300)" } } }, ":where(hr)": { height: "calc(0.25rem * 1)", "background-color": "var(--color-base-300)" }, "&:has(.timeline-middle hr)": { "&:first-child": { "border-start-start-radius": "0", "border-end-start-radius": "0", "border-start-end-radius": "var(--radius-selector)", "border-end-end-radius": "var(--radius-selector)" }, "&:last-child": { "border-start-start-radius": "var(--radius-selector)", "border-end-start-radius": "var(--radius-selector)", "border-start-end-radius": "0", "border-end-end-radius": "0" } }, "&:not(:has(.timeline-middle))": { ":first-child hr:last-child": { "border-start-start-radius": "var(--radius-selector)", "border-end-start-radius": "var(--radius-selector)", "border-start-end-radius": "0", "border-end-end-radius": "0" }, ":last-child hr:first-child": { "border-start-start-radius": "0", "border-end-start-radius": "0", "border-start-end-radius": "var(--radius-selector)", "border-end-end-radius": "var(--radius-selector)" } } }, ".timeline-box": { border: "var(--border) solid", "border-radius": "var(--radius-box)", "border-color": "var(--color-base-300)", "background-color": "var(--color-base-100)", "padding-inline": "calc(0.25rem * 4)", "padding-block": "calc(0.25rem * 2)", "font-size": "0.75rem", "box-shadow": "0 1px 2px 0 oklch(0% 0 0/0.05)" }, ".timeline-start": { "grid-column-start": "1", "grid-column-end": "4", "grid-row-start": "1", "grid-row-end": "2", margin: "calc(0.25rem * 1)", "align-self": "flex-end", "justify-self": "center" }, ".timeline-middle": { "grid-column-start": "2", "grid-row-start": "2" }, ".timeline-end": { "grid-column-start": "1", "grid-column-end": "4", "grid-row-start": "3", "grid-row-end": "4", margin: "calc(0.25rem * 1)", "align-self": "flex-start", "justify-self": "center" }, ".timeline-compact": { "--timeline-row-start": "0", ".timeline-start": { "grid-column-start": "1", "grid-column-end": "4", "grid-row-start": "3", "grid-row-end": "4", "align-self": "flex-start", "justify-self": "center" }, "li:has(.timeline-start)": { ".timeline-end": { "grid-column-start": "none", "grid-row-start": "auto" } }, "&.timeline-vertical": { "> li": { "--timeline-col-start": "0" }, ".timeline-start": { "grid-column-start": "3", "grid-column-end": "4", "grid-row-start": "1", "grid-row-end": "4", "align-self": "center", "justify-self": "flex-start" }, "li:has(.timeline-start)": { ".timeline-end": { "grid-column-start": "auto", "grid-row-start": "none" } } } }, ".timeline-snap-icon": { "> li": { "--timeline-col-start": "0.5rem", "--timeline-row-start": "minmax(0, 1fr)" } }, ".timeline-vertical": { "flex-direction": "column", "> li": { "justify-items": "center", "--timeline-row-start": "minmax(0, 1fr)", "--timeline-row-end": "minmax(0, 1fr)", "> hr": { height: "100%", width: "calc(0.25rem * 1)", "&:first-child": { "grid-column-start": "2", "grid-row-start": "1" }, "&:last-child": { "grid-column-start": "2", "grid-column-end": "auto", "grid-row-start": "3", "grid-row-end": "none" } } }, ".timeline-start": { "grid-column-start": "1", "grid-column-end": "2", "grid-row-start": "1", "grid-row-end": "4", "align-self": "center", "justify-self": "flex-end" }, ".timeline-end": { "grid-column-start": "3", "grid-column-end": "4", "grid-row-start": "1", "grid-row-end": "4", "align-self": "center", "justify-self": "flex-start" }, "&:has(.timeline-middle)": { "> li": { "> hr": { "&:first-child": { "border-top-left-radius": "0", "border-top-right-radius": "0", "border-bottom-right-radius": "var(--radius-selector)", "border-bottom-left-radius": "var(--radius-selector)" }, "&:last-child": { "border-top-left-radius": "var(--radius-selector)", "border-top-right-radius": "var(--radius-selector)", "border-bottom-right-radius": "0", "border-bottom-left-radius": "0" } } } }, "&:not(:has(.timeline-middle))": { ":first-child": { "> hr:last-child": { "border-top-left-radius": "var(--radius-selector)", "border-top-right-radius": "var(--radius-selector)", "border-bottom-right-radius": "0", "border-bottom-left-radius": "0" } }, ":last-child": { "> hr:first-child": { "border-top-left-radius": "0", "border-top-right-radius": "0", "border-bottom-right-radius": "var(--radius-selector)", "border-bottom-left-radius": "var(--radius-selector)" } } }, "&.timeline-snap-icon": { "> li": { "--timeline-col-start": "minmax(0, 1fr)", "--timeline-row-start": "0.5rem" } } }, ".timeline-horizontal": { "flex-direction": "row", "> li": { "align-items": "center", "> hr": { height: "calc(0.25rem * 1)", width: "100%", "&:first-child": { "grid-column-start": "1", "grid-row-start": "2" }, "&:last-child": { "grid-column-start": "3", "grid-column-end": "none", "grid-row-start": "2", "grid-row-end": "auto" } } }, ".timeline-start": { "grid-column-start": "1", "grid-column-end": "4", "grid-row-start": "1", "grid-row-end": "2", "align-self": "flex-end", "justify-self": "center" }, ".timeline-end": { "grid-column-start": "1", "grid-column-end": "4", "grid-row-start": "3", "grid-row-end": "4", "align-self": "flex-start", "justify-self": "center" }, "&:has(.timeline-middle)": { "> li": { "> hr": { "&:first-child": { "border-start-start-radius": "0", "border-end-start-radius": "0", "border-start-end-radius": "var(--radius-selector)", "border-end-end-radius": "var(--radius-selector)" }, "&:last-child": { "border-start-start-radius": "var(--radius-selector)", "border-end-start-radius": "var(--radius-selector)", "border-start-end-radius": "0", "border-end-end-radius": "0" } } } }, "&:not(:has(.timeline-middle))": { ":first-child": { "> hr:last-child": { "border-start-start-radius": "var(--radius-selector)", "border-end-start-radius": "var(--radius-selector)", "border-start-end-radius": "0", "border-end-end-radius": "0" } }, ":last-child": { "> hr:first-child": { "border-start-start-radius": "0", "border-end-start-radius": "0", "border-start-end-radius": "var(--radius-selector)", "border-end-end-radius": "var(--radius-selector)" } } } } }; + +// packages/daisyui/components/timeline/index.js +var timeline_default = ({ addComponents, prefix = "" }) => { + const prefixedtimeline = addPrefix(object_default51, prefix); + addComponents({ ...prefixedtimeline }); +}; + +// packages/daisyui/components/textarea/object.js +var object_default52 = { ".textarea": { border: "var(--border) solid #0000", "min-height": "calc(0.25rem * 20)", "flex-shrink": 1, appearance: "none", "border-radius": "var(--radius-field)", "background-color": "var(--color-base-100)", "padding-block": "calc(0.25rem * 2)", "vertical-align": "middle", width: "clamp(3rem, 20rem, 100%)", "padding-inline-start": "0.75rem", "padding-inline-end": "0.75rem", "font-size": "0.875rem", "border-color": "var(--input-color)", "box-shadow": "0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset", "--input-color": "color-mix(in oklab, var(--color-base-content) 20%, #0000)", textarea: { appearance: "none", "background-color": "transparent", border: "none", "&:focus, &:focus-within": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } } }, "&:focus, &:focus-within": { "--input-color": "var(--color-base-content)", "box-shadow": "0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000)", outline: "2px solid var(--input-color)", "outline-offset": "2px", isolation: "isolate" }, "&:has(> textarea[disabled]), &:is(:disabled, [disabled])": { cursor: "not-allowed", "border-color": "var(--color-base-200)", "background-color": "var(--color-base-200)", color: "color-mix(in oklab, var(--color-base-content) 40%, transparent)", "&::placeholder": { color: "color-mix(in oklab, var(--color-base-content) 20%, transparent)" }, "box-shadow": "none" }, "&:has(> textarea[disabled]) > textarea[disabled]": { cursor: "not-allowed" } }, ".textarea-ghost": { "background-color": "transparent", "box-shadow": "none", "border-color": "#0000", "&:focus, &:focus-within": { "background-color": "var(--color-base-100)", color: "var(--color-base-content)", "border-color": "#0000", "box-shadow": "none" } }, ".textarea-neutral": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-neutral)" } }, ".textarea-primary": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-primary)" } }, ".textarea-secondary": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-secondary)" } }, ".textarea-accent": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-accent)" } }, ".textarea-info": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-info)" } }, ".textarea-success": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-success)" } }, ".textarea-warning": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-warning)" } }, ".textarea-error": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-error)" } }, ".textarea-xs": { "font-size": "0.6875rem" }, ".textarea-sm": { "font-size": "0.75rem" }, ".textarea-md": { "font-size": "0.875rem" }, ".textarea-lg": { "font-size": "1.125rem" }, ".textarea-xl": { "font-size": "1.375rem" } }; + +// packages/daisyui/components/textarea/index.js +var textarea_default = ({ addComponents, prefix = "" }) => { + const prefixedtextarea = addPrefix(object_default52, prefix); + addComponents({ ...prefixedtextarea }); +}; + +// packages/daisyui/components/range/object.js +var object_default53 = { ".range": { appearance: "none", "webkit-appearance": "none", "--range-thumb": "var(--color-base-100)", "--range-thumb-size": "calc(var(--size-selector, 0.25rem) * 6)", "--range-progress": "currentColor", "--range-fill": "1", "--range-p": "0.25rem", "--range-bg": "color-mix(in oklab, currentColor 10%, #0000)", cursor: "pointer", overflow: "hidden", "background-color": "transparent", "vertical-align": "middle", width: "clamp(3rem, 20rem, 100%)", "--radius-selector-max": `calc( + var(--radius-selector) + var(--radius-selector) + var(--radius-selector) + )`, "border-radius": "calc(var(--radius-selector) + min(var(--range-p), var(--radius-selector-max)))", border: "none", height: "var(--range-thumb-size)", '[dir="rtl"] &': { "--range-dir": "-1" }, "&:focus": { outline: "none" }, "&:focus-visible": { outline: "2px solid", "outline-offset": "2px" }, "&::-webkit-slider-runnable-track": { width: "100%", "background-color": "var(--range-bg)", "border-radius": "var(--radius-selector)", height: "calc(var(--range-thumb-size) * 0.5)" }, "@media (forced-colors: active)": [{ "&::-webkit-slider-runnable-track": { border: "1px solid" } }, { "&::-moz-range-track": { border: "1px solid" } }], "&::-webkit-slider-thumb": { position: "relative", "box-sizing": "border-box", "border-radius": "calc(var(--radius-selector) + min(var(--range-p), var(--radius-selector-max)))", "background-color": "currentColor", height: "var(--range-thumb-size)", width: "var(--range-thumb-size)", border: "var(--range-p) solid", appearance: "none", "webkit-appearance": "none", top: "50%", color: "var(--range-progress)", transform: "translateY(-50%)", "box-shadow": "0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000), 0 0 0 2rem var(--range-thumb) inset, calc((var(--range-dir, 1) * -100rem) - (var(--range-dir, 1) * var(--range-thumb-size) / 2)) 0 0 calc(100rem * var(--range-fill))" }, "&::-moz-range-track": { width: "100%", "background-color": "var(--range-bg)", "border-radius": "var(--radius-selector)", height: "calc(var(--range-thumb-size) * 0.5)" }, "&::-moz-range-thumb": { position: "relative", "box-sizing": "border-box", "border-radius": "calc(var(--radius-selector) + min(var(--range-p), var(--radius-selector-max)))", "background-color": "currentColor", height: "var(--range-thumb-size)", width: "var(--range-thumb-size)", border: "var(--range-p) solid", top: "50%", color: "var(--range-progress)", "box-shadow": "0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000), 0 0 0 2rem var(--range-thumb) inset, calc((var(--range-dir, 1) * -100rem) - (var(--range-dir, 1) * var(--range-thumb-size) / 2)) 0 0 calc(100rem * var(--range-fill))" }, "&:disabled": { cursor: "not-allowed", opacity: "30%" } }, ".range-primary": { color: "var(--color-primary)", "--range-thumb": "var(--color-primary-content)" }, ".range-secondary": { color: "var(--color-secondary)", "--range-thumb": "var(--color-secondary-content)" }, ".range-accent": { color: "var(--color-accent)", "--range-thumb": "var(--color-accent-content)" }, ".range-neutral": { color: "var(--color-neutral)", "--range-thumb": "var(--color-neutral-content)" }, ".range-success": { color: "var(--color-success)", "--range-thumb": "var(--color-success-content)" }, ".range-warning": { color: "var(--color-warning)", "--range-thumb": "var(--color-warning-content)" }, ".range-info": { color: "var(--color-info)", "--range-thumb": "var(--color-info-content)" }, ".range-error": { color: "var(--color-error)", "--range-thumb": "var(--color-error-content)" }, ".range-xs": { "--range-thumb-size": "calc(var(--size-selector, 0.25rem) * 4)" }, ".range-sm": { "--range-thumb-size": "calc(var(--size-selector, 0.25rem) * 5)" }, ".range-md": { "--range-thumb-size": "calc(var(--size-selector, 0.25rem) * 6)" }, ".range-lg": { "--range-thumb-size": "calc(var(--size-selector, 0.25rem) * 7)" }, ".range-xl": { "--range-thumb-size": "calc(var(--size-selector, 0.25rem) * 8)" } }; + +// packages/daisyui/components/range/index.js +var range_default = ({ addComponents, prefix = "" }) => { + const prefixedrange = addPrefix(object_default53, prefix); + addComponents({ ...prefixedrange }); +}; + +// packages/daisyui/components/dock/object.js +var object_default54 = { ".dock": { position: "fixed", right: "calc(0.25rem * 0)", bottom: "calc(0.25rem * 0)", left: "calc(0.25rem * 0)", "z-index": 1, display: "flex", width: "100%", "flex-direction": "row", "align-items": "center", "justify-content": "space-around", "background-color": "var(--color-base-100)", padding: "calc(0.25rem * 2)", color: "currentColor", "border-top": "0.5px solid color-mix(in oklab, var(--color-base-content) 5%, #0000)", height: ["4rem", "calc(4rem + env(safe-area-inset-bottom))"], "padding-bottom": "env(safe-area-inset-bottom)", "> *": { position: "relative", "margin-bottom": "calc(0.25rem * 2)", display: "flex", height: "100%", "max-width": "calc(0.25rem * 32)", "flex-shrink": 1, "flex-basis": "100%", cursor: "pointer", "flex-direction": "column", "align-items": "center", "justify-content": "center", gap: "1px", "border-radius": "var(--radius-box)", "background-color": "transparent", transition: "opacity 0.2s ease-out", "@media (hover: hover)": { "&:hover": { opacity: "80%" } }, '&[aria-disabled="true"], &[disabled]': { "&, &:hover": { "pointer-events": "none", color: "color-mix(in oklab, var(--color-base-content) 10%, transparent)", opacity: "100%" } }, ".dock-label": { "font-size": "0.6875rem" }, "&:after": { content: '""', position: "absolute", height: "calc(0.25rem * 1)", width: "calc(0.25rem * 6)", "border-radius": "calc(infinity * 1px)", "background-color": "transparent", bottom: "0.2rem", "border-top": "3px solid transparent", transition: "background-color 0.1s ease-out, text-color 0.1s ease-out, width 0.1s ease-out" } } }, ".dock-active": { "&:after": { width: "calc(0.25rem * 10)", "background-color": "currentColor", color: "currentColor" } }, ".dock-xs": { height: ["3rem", "calc(3rem + env(safe-area-inset-bottom))"], ".dock-active": { "&:after": { bottom: "-0.1rem" } }, ".dock-label": { "font-size": "0.625rem" } }, ".dock-sm": { height: ["calc(0.25rem * 14)", "3.5rem", "calc(3.5rem + env(safe-area-inset-bottom))"], ".dock-active": { "&:after": { bottom: "-0.1rem" } }, ".dock-label": { "font-size": "0.625rem" } }, ".dock-md": { height: ["4rem", "calc(4rem + env(safe-area-inset-bottom))"], ".dock-label": { "font-size": "0.6875rem" } }, ".dock-lg": { height: ["4.5rem", "calc(4.5rem + env(safe-area-inset-bottom))"], ".dock-active": { "&:after": { bottom: "0.4rem" } }, ".dock-label": { "font-size": "0.6875rem" } }, ".dock-xl": { height: ["5rem", "calc(5rem + env(safe-area-inset-bottom))"], ".dock-active": { "&:after": { bottom: "0.4rem" } }, ".dock-label": { "font-size": "0.75rem" } } }; + +// packages/daisyui/components/dock/index.js +var dock_default = ({ addComponents, prefix = "" }) => { + const prefixeddock = addPrefix(object_default54, prefix); + addComponents({ ...prefixeddock }); +}; + +// packages/daisyui/components/breadcrumbs/object.js +var object_default55 = { ".breadcrumbs": { "max-width": "100%", "overflow-x": "auto", "padding-block": "calc(0.25rem * 2)", "> menu, > ul, > ol": { display: "flex", "min-height": "min-content", "align-items": "center", "white-space": "nowrap", "> li": { display: "flex", "align-items": "center", "> *": { display: "flex", cursor: "pointer", "align-items": "center", gap: "calc(0.25rem * 2)", "&:hover": { "@media (hover: hover)": { "text-decoration-line": "underline" } }, "&:focus": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, "&:focus-visible": { outline: "2px solid currentColor", "outline-offset": "2px" } }, "& + *:before": { content: '""', "margin-right": "calc(0.25rem * 3)", "margin-left": "calc(0.25rem * 2)", display: "block", height: "calc(0.25rem * 1.5)", width: "calc(0.25rem * 1.5)", opacity: "40%", rotate: "45deg", "border-top": "1px solid", "border-right": "1px solid", "background-color": "#0000" }, '[dir="rtl"] & + *:before': { rotate: "-135deg" } } } } }; + +// packages/daisyui/components/breadcrumbs/index.js +var breadcrumbs_default = ({ addComponents, prefix = "" }) => { + const prefixedbreadcrumbs = addPrefix(object_default55, prefix); + addComponents({ ...prefixedbreadcrumbs }); +}; + +// packages/daisyui/components/radio/object.js +var object_default56 = { ".radio": { position: "relative", "flex-shrink": 0, cursor: "pointer", appearance: "none", "border-radius": "calc(infinity * 1px)", padding: "calc(0.25rem * 1)", "vertical-align": "middle", border: "var(--border) solid var(--input-color, color-mix(in srgb, currentColor 20%, #0000))", "box-shadow": "0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset", "--size": "calc(var(--size-selector, 0.25rem) * 6)", width: "var(--size)", height: "var(--size)", color: "var(--input-color, currentColor)", "&:before": { display: "block", width: "100%", height: "100%", "border-radius": "calc(infinity * 1px)", "--tw-content": '""', content: "var(--tw-content)", "background-size": "auto, calc(var(--noise) * 100%)", "background-image": "none, var(--fx-noise)" }, "&:focus-visible": { outline: "2px solid currentColor" }, '&:checked, &[aria-checked="true"]': { animation: "radio 0.2s ease-out", "border-color": "currentColor", "background-color": "var(--color-base-100)", "&:before": { "background-color": "currentColor", "box-shadow": "0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1))" }, "@media (forced-colors: active)": { "&:before": { "outline-style": "var(--tw-outline-style)", "outline-width": "1px", "outline-offset": "calc(1px * -1)" } }, "@media print": { "&:before": { outline: "0.25rem solid", "outline-offset": "-1rem" } } } }, ".radio-primary": { "--input-color": "var(--color-primary)" }, ".radio-secondary": { "--input-color": "var(--color-secondary)" }, ".radio-accent": { "--input-color": "var(--color-accent)" }, ".radio-neutral": { "--input-color": "var(--color-neutral)" }, ".radio-info": { "--input-color": "var(--color-info)" }, ".radio-success": { "--input-color": "var(--color-success)" }, ".radio-warning": { "--input-color": "var(--color-warning)" }, ".radio-error": { "--input-color": "var(--color-error)" }, ".radio:disabled": { cursor: "not-allowed", opacity: "20%" }, ".radio-xs": { padding: "0.125rem", '&:is([type="radio"])': { "--size": "calc(var(--size-selector, 0.25rem) * 4)" } }, ".radio-sm": { padding: "0.1875rem", '&:is([type="radio"])': { "--size": "calc(var(--size-selector, 0.25rem) * 5)" } }, ".radio-md": { padding: "0.25rem", '&:is([type="radio"])': { "--size": "calc(var(--size-selector, 0.25rem) * 6)" } }, ".radio-lg": { padding: "0.3125rem", '&:is([type="radio"])': { "--size": "calc(var(--size-selector, 0.25rem) * 7)" } }, ".radio-xl": { padding: "0.375rem", '&:is([type="radio"])': { "--size": "calc(var(--size-selector, 0.25rem) * 8)" } }, "@keyframes radio": { "0%": { padding: "5px" }, "50%": { padding: "3px" } } }; + +// packages/daisyui/components/radio/index.js +var radio_default = ({ addComponents, prefix = "" }) => { + const prefixedradio = addPrefix(object_default56, prefix); + addComponents({ ...prefixedradio }); +}; + +// packages/daisyui/components/skeleton/object.js +var object_default57 = { ".skeleton": { "border-radius": "var(--radius-box)", "background-color": "var(--color-base-300)", "@media (prefers-reduced-motion: reduce)": { "transition-duration": "15s" }, "will-change": "background-position", animation: "skeleton 1.8s ease-in-out infinite", "background-image": "linear-gradient( 105deg, #0000 0% 40%, var(--color-base-100) 50%, #0000 60% 100% )", "background-size": "200% auto", "background-repeat": "no-repeat", "background-position-x": "-50%" }, "@keyframes skeleton": { "0%": { "background-position": "150%" }, "100%": { "background-position": "-50%" } } }; + +// packages/daisyui/components/skeleton/index.js +var skeleton_default = ({ addComponents, prefix = "" }) => { + const prefixedskeleton = addPrefix(object_default57, prefix); + addComponents({ ...prefixedskeleton }); +}; + +// packages/daisyui/components/loading/object.js +var object_default58 = { ".loading": { "pointer-events": "none", display: "inline-block", "aspect-ratio": "1 / 1", "background-color": "currentColor", "vertical-align": "middle", width: "calc(var(--size-selector, 0.25rem) * 6)", "mask-size": "100%", "mask-repeat": "no-repeat", "mask-position": "center", "mask-image": `url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E")` }, ".loading-spinner": { "mask-image": `url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E")` }, ".loading-dots": { "mask-image": `url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='4' cy='12' r='3'%3E%3Canimate attributeName='cy' values='12;6;12;12' keyTimes='0;0.286;0.571;1' dur='1.05s' repeatCount='indefinite' keySplines='.33,0,.66,.33;.33,.66,.66,1'/%3E%3C/circle%3E%3Ccircle cx='12' cy='12' r='3'%3E%3Canimate attributeName='cy' values='12;6;12;12' keyTimes='0;0.286;0.571;1' dur='1.05s' repeatCount='indefinite' keySplines='.33,0,.66,.33;.33,.66,.66,1' begin='0.1s'/%3E%3C/circle%3E%3Ccircle cx='20' cy='12' r='3'%3E%3Canimate attributeName='cy' values='12;6;12;12' keyTimes='0;0.286;0.571;1' dur='1.05s' repeatCount='indefinite' keySplines='.33,0,.66,.33;.33,.66,.66,1' begin='0.2s'/%3E%3C/circle%3E%3C/svg%3E")` }, ".loading-ring": { "mask-image": `url("data:image/svg+xml,%3Csvg width='44' height='44' viewBox='0 0 44 44' xmlns='http://www.w3.org/2000/svg' stroke='white'%3E%3Cg fill='none' fill-rule='evenodd' stroke-width='2'%3E%3Ccircle cx='22' cy='22' r='1'%3E%3Canimate attributeName='r' begin='0s' dur='1.8s' values='1;20' calcMode='spline' keyTimes='0;1' keySplines='0.165,0.84,0.44,1' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-opacity' begin='0s' dur='1.8s' values='1;0' calcMode='spline' keyTimes='0;1' keySplines='0.3,0.61,0.355,1' repeatCount='indefinite'/%3E%3C/circle%3E%3Ccircle cx='22' cy='22' r='1'%3E%3Canimate attributeName='r' begin='-0.9s' dur='1.8s' values='1;20' calcMode='spline' keyTimes='0;1' keySplines='0.165,0.84,0.44,1' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-opacity' begin='-0.9s' dur='1.8s' values='1;0' calcMode='spline' keyTimes='0;1' keySplines='0.3,0.61,0.355,1' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E")` }, ".loading-ball": { "mask-image": `url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cellipse cx='12' cy='5' rx='4' ry='4'%3E%3Canimate attributeName='cy' values='5;20;20.5;20;5' keyTimes='0;0.469;0.5;0.531;1' dur='.8s' repeatCount='indefinite' keySplines='.33,0,.66,.33;.33,.66,.66,1'/%3E%3Canimate attributeName='rx' values='4;4;4.8;4;4' keyTimes='0;0.469;0.5;0.531;1' dur='.8s' repeatCount='indefinite'/%3E%3Canimate attributeName='ry' values='4;4;3;4;4' keyTimes='0;0.469;0.5;0.531;1' dur='.8s' repeatCount='indefinite'/%3E%3C/ellipse%3E%3C/svg%3E")` }, ".loading-bars": { "mask-image": `url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='1' y='1' width='6' height='22'%3E%3Canimate attributeName='y' values='1;5;1' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite'/%3E%3Canimate attributeName='height' values='22;14;22' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite'/%3E%3Canimate attributeName='opacity' values='1;0.2;1' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite'/%3E%3C/rect%3E%3Crect x='9' y='1' width='6' height='22'%3E%3Canimate attributeName='y' values='1;5;1' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite' begin='-0.65s'/%3E%3Canimate attributeName='height' values='22;14;22' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite' begin='-0.65s'/%3E%3Canimate attributeName='opacity' values='1;0.2;1' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite' begin='-0.65s'/%3E%3C/rect%3E%3Crect x='17' y='1' width='6' height='22'%3E%3Canimate attributeName='y' values='1;5;1' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite' begin='-0.5s'/%3E%3Canimate attributeName='height' values='22;14;22' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite' begin='-0.5s'/%3E%3Canimate attributeName='opacity' values='1;0.2;1' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite' begin='-0.5s'/%3E%3C/rect%3E%3C/svg%3E")` }, ".loading-infinity": { "mask-image": `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' style='shape-rendering:auto;' width='200px' height='200px' viewBox='0 0 100 100' preserveAspectRatio='xMidYMid'%3E%3Cpath fill='none' stroke='black' stroke-width='10' stroke-dasharray='205.271 51.318' d='M24.3 30C11.4 30 5 43.3 5 50s6.4 20 19.3 20c19.3 0 32.1-40 51.4-40C88.6 30 95 43.3 95 50s-6.4 20-19.3 20C56.4 70 43.6 30 24.3 30z' stroke-linecap='round' style='transform:scale(0.8);transform-origin:50px 50px'%3E%3Canimate attributeName='stroke-dashoffset' repeatCount='indefinite' dur='2s' keyTimes='0;1' values='0;256.589'/%3E%3C/path%3E%3C/svg%3E")` }, ".loading-xs": { width: "calc(var(--size-selector, 0.25rem) * 4)" }, ".loading-sm": { width: "calc(var(--size-selector, 0.25rem) * 5)" }, ".loading-md": { width: "calc(var(--size-selector, 0.25rem) * 6)" }, ".loading-lg": { width: "calc(var(--size-selector, 0.25rem) * 7)" }, ".loading-xl": { width: "calc(var(--size-selector, 0.25rem) * 8)" } }; + +// packages/daisyui/components/loading/index.js +var loading_default = ({ addComponents, prefix = "" }) => { + const prefixedloading = addPrefix(object_default58, prefix); + addComponents({ ...prefixedloading }); +}; + +// packages/daisyui/components/validator/object.js +var object_default59 = { ".validator": { "&:user-valid, &:has(:user-valid)": { '&, &:focus, &:checked, &[aria-checked="true"], &:focus-within': { "--input-color": "var(--color-success)" } }, "&:user-invalid, &:has(:user-invalid), &[aria-invalid]": { '&, &:focus, &:checked, &[aria-checked="true"], &:focus-within': { "--input-color": "var(--color-error)" }, "& ~ .validator-hint": { visibility: "visible", display: "block", color: "var(--color-error)" } } }, ".validator-hint": { visibility: "hidden", "margin-top": "calc(0.25rem * 2)", "font-size": "0.75rem" } }; + +// packages/daisyui/components/validator/index.js +var validator_default = ({ addComponents, prefix = "" }) => { + const prefixedvalidator = addPrefix(object_default59, prefix); + addComponents({ ...prefixedvalidator }); +}; + +// packages/daisyui/components/collapse/object.js +var object_default60 = { ".collapse:not(td, tr, colgroup)": { visibility: "visible" }, ".collapse": { position: "relative", display: "grid", overflow: "hidden", "border-radius": "var(--radius-box, 1rem)", width: "100%", "grid-template-rows": "max-content 0fr", transition: "grid-template-rows 0.2s", isolation: "isolate", '> input:is([type="checkbox"], [type="radio"])': { "grid-column-start": "1", "grid-row-start": "1", appearance: "none", opacity: 0, "z-index": 1, width: "100%", padding: "1rem", "padding-inline-end": "3rem", "min-height": "3.75rem", transition: "background-color 0.2s ease-out" }, '&:is([open], :focus:not(.collapse-close)), &:not(.collapse-close):has(> input:is([type="checkbox"], [type="radio"]):checked)': { "grid-template-rows": "max-content 1fr" }, '&:is([open], :focus:not(.collapse-close)) > .collapse-content, &:not(.collapse-close) > :where(input:is([type="checkbox"], [type="radio"]):checked ~ .collapse-content)': { visibility: "visible", "min-height": "fit-content" }, '&:focus-visible, &:has(> input:is([type="checkbox"], [type="radio"]):focus-visible)': { "outline-color": "var(--color-base-content)", "outline-style": "solid", "outline-width": "2px", "outline-offset": "2px" }, "&:not(.collapse-close)": { '> input[type="checkbox"], > input[type="radio"]:not(:checked), > .collapse-title': { cursor: "pointer" } }, "&:focus:not(.collapse-close, .collapse[open]) > .collapse-title": { cursor: "unset" }, '&:is([open], :focus:not(.collapse-close)) > :where(.collapse-content), &:not(.collapse-close) > :where(input:is([type="checkbox"], [type="radio"]):checked ~ .collapse-content)': { "padding-bottom": "1rem", transition: "padding 0.2s ease-out, background-color 0.2s ease-out" }, "&:is([open])": { "&.collapse-arrow": { "> .collapse-title:after": { transform: "translateY(-50%) rotate(225deg)" } } }, "&.collapse-open": { "&.collapse-arrow": { "> .collapse-title:after": { transform: "translateY(-50%) rotate(225deg)" } }, "&.collapse-plus": { "> .collapse-title:after": { content: '"−"' } } }, "&.collapse-arrow:focus:not(.collapse-close)": { "> .collapse-title:after": { transform: "translateY(-50%) rotate(225deg)" } }, "&.collapse-arrow:not(.collapse-close)": { '> input:is([type="checkbox"], [type="radio"]):checked ~ .collapse-title:after': { transform: "translateY(-50%) rotate(225deg)" } }, "&[open]": { "&.collapse-plus": { "> .collapse-title:after": { content: '"−"' } } }, "&.collapse-plus:focus:not(.collapse-close)": { "> .collapse-title:after": { content: '"−"' } }, "&.collapse-plus:not(.collapse-close)": { '> input:is([type="checkbox"], [type="radio"]):checked ~ .collapse-title:after': { content: '"−"' } } }, ".collapse-title, .collapse-content": { "grid-column-start": "1", "grid-row-start": "1" }, ".collapse-content": { visibility: "hidden", "grid-column-start": "1", "grid-row-start": "2", "min-height": "0", "padding-left": "1rem", "padding-right": "1rem", cursor: "unset", transition: "visibility 0.2s, padding 0.2s ease-out, background-color 0.2s ease-out" }, ".collapse:is(details)": { width: "100%", "& summary": { position: "relative", display: "block", "&::-webkit-details-marker": { display: "none" } } }, ".collapse:is(details) summary": { outline: "none" }, ".collapse-arrow": { "> .collapse-title:after": { position: "absolute", display: "block", height: "0.5rem", width: "0.5rem", transform: "translateY(-100%) rotate(45deg)", "transition-property": "all", "transition-timing-function": "cubic-bezier(0.4, 0, 0.2, 1)", "transition-duration": "0.2s", top: "1.9rem", "inset-inline-end": "1.4rem", content: '""', "transform-origin": "75% 75%", "box-shadow": "2px 2px", "pointer-events": "none" } }, ".collapse-plus": { "> .collapse-title:after": { position: "absolute", display: "block", height: "0.5rem", width: "0.5rem", "transition-property": "all", "transition-duration": "300ms", "transition-timing-function": "cubic-bezier(0.4, 0, 0.2, 1)", top: "0.9rem", "inset-inline-end": "1.4rem", content: '"+"', "pointer-events": "none" } }, ".collapse-title": { position: "relative", width: "100%", padding: "1rem", "padding-inline-end": "3rem", "min-height": "3.75rem", transition: "background-color 0.2s ease-out" }, ".collapse-open": { "grid-template-rows": "max-content 1fr", "> .collapse-content": { visibility: "visible", "min-height": "fit-content", "padding-bottom": "1rem", transition: "padding 0.2s ease-out, background-color 0.2s ease-out" } } }; + +// packages/daisyui/components/collapse/index.js +var collapse_default = ({ addComponents, prefix = "" }) => { + const prefixedcollapse = addPrefix(object_default60, prefix); + addComponents({ ...prefixedcollapse }); +}; + +// packages/daisyui/components/swap/object.js +var object_default61 = { ".swap": { position: "relative", display: "inline-grid", cursor: "pointer", "place-content": "center", "vertical-align": "middle", "webkit-user-select": "none", "user-select": "none", input: { appearance: "none", border: "none" }, "> *": { "grid-column-start": "1", "grid-row-start": "1", "transition-property": "transform, rotate, opacity", "transition-duration": "0.2s", "transition-timing-function": "cubic-bezier(0, 0, 0.2, 1)" }, ".swap-on, .swap-indeterminate, input:indeterminate ~ .swap-on": { opacity: "0%" }, "input:is(:checked, :indeterminate)": { "& ~ .swap-off": { opacity: "0%" } }, "input:checked ~ .swap-on, input:indeterminate ~ .swap-indeterminate": { opacity: "100%", "backface-visibility": "visible" } }, ".swap-active": { ".swap-off": { opacity: "0%" }, ".swap-on": { opacity: "100%" } }, ".swap-rotate": { ".swap-on, input:indeterminate ~ .swap-on": { rotate: "45deg" }, "input:is(:checked, :indeterminate) ~ .swap-on, &.swap-active .swap-on": { rotate: "0deg" }, "input:is(:checked, :indeterminate) ~ .swap-off, &.swap-active .swap-off": { rotate: "calc(45deg * -1)" } }, ".swap-flip": { "transform-style": "preserve-3d", perspective: "20rem", ".swap-on, .swap-indeterminate, input:indeterminate ~ .swap-on": { transform: "rotateY(180deg)", "backface-visibility": "hidden" }, "input:is(:checked, :indeterminate) ~ .swap-on, &.swap-active .swap-on": { transform: "rotateY(0deg)" }, "input:is(:checked, :indeterminate) ~ .swap-off, &.swap-active .swap-off": { transform: "rotateY(-180deg)", "backface-visibility": "hidden", opacity: "100%" } } }; + +// packages/daisyui/components/swap/index.js +var swap_default = ({ addComponents, prefix = "" }) => { + const prefixedswap = addPrefix(object_default61, prefix); + addComponents({ ...prefixedswap }); +}; + +// packages/daisyui/utilities/typography/object.js +var object_default62 = { ":root .prose": { "--tw-prose-body": "color-mix(in oklab, var(--color-base-content) 80%, #0000)", "--tw-prose-headings": "var(--color-base-content)", "--tw-prose-lead": "var(--color-base-content)", "--tw-prose-links": "var(--color-base-content)", "--tw-prose-bold": "var(--color-base-content)", "--tw-prose-counters": "var(--color-base-content)", "--tw-prose-bullets": "color-mix(in oklab, var(--color-base-content) 50%, #0000)", "--tw-prose-hr": "color-mix(in oklab, var(--color-base-content) 20%, #0000)", "--tw-prose-quotes": "var(--color-base-content)", "--tw-prose-quote-borders": "color-mix(in oklab, var(--color-base-content) 20%, #0000)", "--tw-prose-captions": "color-mix(in oklab, var(--color-base-content) 50%, #0000)", "--tw-prose-code": "var(--color-base-content)", "--tw-prose-pre-code": "var(--color-neutral-content)", "--tw-prose-pre-bg": "var(--color-neutral)", "--tw-prose-th-borders": "color-mix(in oklab, var(--color-base-content) 50%, #0000)", "--tw-prose-td-borders": "color-mix(in oklab, var(--color-base-content) 20%, #0000)", "--tw-prose-kbd": "color-mix(in oklab, var(--color-base-content) 80%, #0000)", ":where(code):not(pre > code)": { "background-color": "var(--color-base-200)", "border-radius": "var(--radius-selector)", border: "var(--border) solid var(--color-base-300)", "padding-inline": "0.5em", "font-weight": "inherit", "&:before, &:after": { display: "none" } } } }; + +// packages/daisyui/utilities/typography/index.js +var typography_default = ({ addUtilities, prefix = "" }) => { + const prefixedtypography = addPrefix(object_default62, prefix); + addUtilities({ ...prefixedtypography }); +}; + +// packages/daisyui/utilities/glass/object.js +var object_default63 = { ".glass": { border: "none", "backdrop-filter": "blur(var(--glass-blur, 40px))", "background-color": "#0000", "background-image": "linear-gradient( 135deg, oklch(100% 0 0 / var(--glass-opacity, 30%)) 0%, oklch(0% 0 0 / 0%) 100% ), linear-gradient( var(--glass-reflect-degree, 100deg), oklch(100% 0 0 / var(--glass-reflect-opacity, 5%)) 25%, oklch(0% 0 0 / 0%) 25% )", "box-shadow": "0 0 0 1px oklch(100% 0 0 / var(--glass-border-opacity, 20%)) inset, 0 0 0 2px oklch(0% 0 0 / 5%)", "text-shadow": "0 1px oklch(0% 0 0 / var(--glass-text-shadow-opacity, 5%))" } }; + +// packages/daisyui/utilities/glass/index.js +var glass_default = ({ addUtilities, prefix = "" }) => { + const prefixedglass = addPrefix(object_default63, prefix); + addUtilities({ ...prefixedglass }); +}; + +// packages/daisyui/utilities/join/object.js +var object_default64 = { ".join": { display: "inline-flex", "align-items": "stretch", "--join-ss": "0", "--join-se": "0", "--join-es": "0", "--join-ee": "0", ":where(.join-item)": { "border-start-start-radius": "var(--join-ss, 0)", "border-start-end-radius": "var(--join-se, 0)", "border-end-start-radius": "var(--join-es, 0)", "border-end-end-radius": "var(--join-ee, 0)", "*": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" } }, "> .join-item:where(:first-child)": { "--join-ss": "var(--radius-field)", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "0" }, ":first-child:not(:last-child)": { ":where(.join-item)": { "--join-ss": "var(--radius-field)", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "0" } }, "> .join-item:where(:last-child)": { "--join-ss": "0", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "var(--radius-field)" }, ":last-child:not(:first-child)": { ":where(.join-item)": { "--join-ss": "0", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "var(--radius-field)" } }, "> .join-item:where(:only-child)": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" }, ":only-child": { ":where(.join-item)": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" } } }, ".join-item": { "&:where(*:not(:first-child, :disabled, [disabled], .btn-disabled))": { "margin-inline-start": "calc(var(--border, 1px) * -1)", "margin-block-start": "0" } }, ".join-vertical": { "flex-direction": "column", "> .join-item:first-child": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "0" }, ":first-child:not(:last-child)": { ".join-item": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "0" } }, "> .join-item:last-child": { "--join-ss": "0", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" }, ":last-child:not(:first-child)": { ".join-item": { "--join-ss": "0", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" } }, "> .join-item:only-child": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" }, ":only-child": { ".join-item": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" } }, ".join-item": { "&:where(*:not(:first-child))": { "margin-inline-start": "0", "margin-block-start": "calc(var(--border, 1px) * -1)" } } }, ".join-horizontal": { "flex-direction": "row", "> .join-item:first-child": { "--join-ss": "var(--radius-field)", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "0" }, ":first-child:not(:last-child)": { ".join-item": { "--join-ss": "var(--radius-field)", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "0" } }, "> .join-item:last-child": { "--join-ss": "0", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "var(--radius-field)" }, ":last-child:not(:first-child)": { ".join-item": { "--join-ss": "0", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "var(--radius-field)" } }, "> .join-item:only-child": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" }, ":only-child": { ".join-item": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" } }, ".join-item": { "&:where(*:not(:first-child))": { "margin-inline-start": "calc(var(--border, 1px) * -1)", "margin-block-start": "0" } } } }; + +// packages/daisyui/utilities/join/index.js +var join_default = ({ addUtilities, prefix = "" }) => { + const prefixedjoin = addPrefix(object_default64, prefix); + addUtilities({ ...prefixedjoin }); +}; + +// packages/daisyui/utilities/radius/object.js +var object_default65 = { ".rounded-box": { "border-radius": "var(--radius-box)" }, ".rounded-field": { "border-radius": "var(--radius-field)" }, ".rounded-selector": { "border-radius": "var(--radius-selector)" }, ".rounded-t-box": { "border-top-left-radius": "var(--radius-box)", "border-top-right-radius": "var(--radius-box)" }, ".rounded-b-box": { "border-bottom-left-radius": "var(--radius-box)", "border-bottom-right-radius": "var(--radius-box)" }, ".rounded-l-box": { "border-top-left-radius": "var(--radius-box)", "border-bottom-left-radius": "var(--radius-box)" }, ".rounded-r-box": { "border-top-right-radius": "var(--radius-box)", "border-bottom-right-radius": "var(--radius-box)" }, ".rounded-tl-box": { "border-top-left-radius": "var(--radius-box)" }, ".rounded-tr-box": { "border-top-right-radius": "var(--radius-box)" }, ".rounded-br-box": { "border-bottom-right-radius": "var(--radius-box)" }, ".rounded-bl-box": { "border-bottom-left-radius": "var(--radius-box)" }, ".rounded-t-field": { "border-top-left-radius": "var(--radius-field)", "border-top-right-radius": "var(--radius-field)" }, ".rounded-b-field": { "border-bottom-left-radius": "var(--radius-field)", "border-bottom-right-radius": "var(--radius-field)" }, ".rounded-l-field": { "border-top-left-radius": "var(--radius-field)", "border-bottom-left-radius": "var(--radius-field)" }, ".rounded-r-field": { "border-top-right-radius": "var(--radius-field)", "border-bottom-right-radius": "var(--radius-field)" }, ".rounded-tl-field": { "border-top-left-radius": "var(--radius-field)" }, ".rounded-tr-field": { "border-top-right-radius": "var(--radius-field)" }, ".rounded-br-field": { "border-bottom-right-radius": "var(--radius-field)" }, ".rounded-bl-field": { "border-bottom-left-radius": "var(--radius-field)" }, ".rounded-t-selector": { "border-top-left-radius": "var(--radius-selector)", "border-top-right-radius": "var(--radius-selector)" }, ".rounded-b-selector": { "border-bottom-left-radius": "var(--radius-selector)", "border-bottom-right-radius": "var(--radius-selector)" }, ".rounded-l-selector": { "border-top-left-radius": "var(--radius-selector)", "border-bottom-left-radius": "var(--radius-selector)" }, ".rounded-r-selector": { "border-top-right-radius": "var(--radius-selector)", "border-bottom-right-radius": "var(--radius-selector)" }, ".rounded-tl-selector": { "border-top-left-radius": "var(--radius-selector)" }, ".rounded-tr-selector": { "border-top-right-radius": "var(--radius-selector)" }, ".rounded-br-selector": { "border-bottom-right-radius": "var(--radius-selector)" }, ".rounded-bl-selector": { "border-bottom-left-radius": "var(--radius-selector)" } }; + +// packages/daisyui/utilities/radius/index.js +var radius_default = ({ addUtilities, prefix = "" }) => { + const prefixedradius = addPrefix(object_default65, prefix); + addUtilities({ ...prefixedradius }); +}; + +// packages/daisyui/imports.js +var base = { rootscrolllock: rootscrolllock_default, rootcolor: rootcolor_default, scrollbar: scrollbar_default, properties: properties_default, rootscrollgutter: rootscrollgutter_default, svg: svg_default }; +var components = { drawer: drawer_default, link: link_default, stat: stat_default, carousel: carousel_default, divider: divider_default, mask: mask_default, fieldset: fieldset_default, dropdown: dropdown_default, card: card_default, steps: steps_default, alert: alert_default, kbd: kbd_default, select: select_default, progress: progress_default, fileinput: fileinput_default, modal: modal_default, footer: footer_default, table: table_default, avatar: avatar_default, input: input_default, checkbox: checkbox_default, badge: badge_default, status: status_default, diff: diff_default, hero: hero_default, toggle: toggle_default, stack: stack_default, navbar: navbar_default, label: label_default, menu: menu_default, toast: toast_default, button: button_default, list: list_default, mockup: mockup_default, calendar: calendar_default, indicator: indicator_default, rating: rating_default, tab: tab_default, filter: filter_default, chat: chat_default, radialprogress: radialprogress_default, countdown: countdown_default, tooltip: tooltip_default, timeline: timeline_default, textarea: textarea_default, range: range_default, dock: dock_default, breadcrumbs: breadcrumbs_default, radio: radio_default, skeleton: skeleton_default, loading: loading_default, validator: validator_default, collapse: collapse_default, swap: swap_default }; +var utilities = { typography: typography_default, glass: glass_default, join: join_default, radius: radius_default }; + +// packages/daisyui/index.js +var version = "5.0.35"; +var daisyui_default = plugin.withOptions((options) => { + return ({ addBase, addComponents, addUtilities }) => { + const { + include, + exclude, + prefix = "" + } = pluginOptionsHandler(options, addBase, object_default, version); + const shouldIncludeItem = (name) => { + if (include && exclude) { + return include.includes(name) && !exclude.includes(name); + } + if (include) { + return include.includes(name); + } + if (exclude) { + return !exclude.includes(name); + } + return true; + }; + Object.entries(base).forEach(([name, item]) => { + if (!shouldIncludeItem(name)) + return; + item({ addBase, prefix }); + }); + Object.entries(components).forEach(([name, item]) => { + if (!shouldIncludeItem(name)) + return; + item({ addComponents, prefix }); + }); + Object.entries(utilities).forEach(([name, item]) => { + if (!shouldIncludeItem(name)) + return; + item({ addUtilities, prefix }); + }); + }; +}, () => ({ + theme: { + extend: variables_default + } +})); + + +/* + + MIT License + + Copyright (c) 2020 Pouya Saadeghi – https://daisyui.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +*/ diff --git a/assets/vendor/heroicons.js b/assets/vendor/heroicons.js new file mode 100644 index 0000000..296f80e --- /dev/null +++ b/assets/vendor/heroicons.js @@ -0,0 +1,43 @@ +const plugin = require("tailwindcss/plugin") +const fs = require("fs") +const path = require("path") + +module.exports = plugin(function({matchComponents, theme}) { + let iconsDir = path.join(__dirname, "../../deps/heroicons/optimized") + let values = {} + let icons = [ + ["", "/24/outline"], + ["-solid", "/24/solid"], + ["-mini", "/20/solid"], + ["-micro", "/16/solid"] + ] + icons.forEach(([suffix, dir]) => { + fs.readdirSync(path.join(iconsDir, dir)).forEach(file => { + let name = path.basename(file, ".svg") + suffix + values[name] = {name, fullPath: path.join(iconsDir, dir, file)} + }) + }) + matchComponents({ + "hero": ({name, fullPath}) => { + let content = fs.readFileSync(fullPath).toString().replace(/\r?\n|\r/g, "") + content = encodeURIComponent(content) + let size = theme("spacing.6") + if (name.endsWith("-mini")) { + size = theme("spacing.5") + } else if (name.endsWith("-micro")) { + size = theme("spacing.4") + } + return { + [`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`, + "-webkit-mask": `var(--hero-${name})`, + "mask": `var(--hero-${name})`, + "mask-repeat": "no-repeat", + "background-color": "currentColor", + "vertical-align": "middle", + "display": "inline-block", + "width": size, + "height": size + } + } + }, {values}) +}) diff --git a/assets/vendor/tailwindcss-animate.js b/assets/vendor/tailwindcss-animate.js new file mode 100644 index 0000000..1021698 --- /dev/null +++ b/assets/vendor/tailwindcss-animate.js @@ -0,0 +1,188 @@ +const plugin = require("tailwindcss/plugin") + +function filterDefault(values) { + return Object.fromEntries( + Object.entries(values).filter(([key]) => key !== "DEFAULT"), + ) +} + +module.exports = plugin( + ({ addUtilities, matchUtilities, theme }) => { + addUtilities({ + "@keyframes enter": theme("keyframes.enter"), + "@keyframes exit": theme("keyframes.exit"), + ".animate-in": { + animationName: "enter", + animationDuration: theme("animationDuration.DEFAULT"), + "--tw-enter-opacity": "initial", + "--tw-enter-scale": "initial", + "--tw-enter-rotate": "initial", + "--tw-enter-translate-x": "initial", + "--tw-enter-translate-y": "initial", + }, + ".animate-out": { + animationName: "exit", + animationDuration: theme("animationDuration.DEFAULT"), + "--tw-exit-opacity": "initial", + "--tw-exit-scale": "initial", + "--tw-exit-rotate": "initial", + "--tw-exit-translate-x": "initial", + "--tw-exit-translate-y": "initial", + }, + }) + + matchUtilities( + { + "fade-in": (value) => ({ "--tw-enter-opacity": value }), + "fade-out": (value) => ({ "--tw-exit-opacity": value }), + }, + { values: theme("animationOpacity") }, + ) + + matchUtilities( + { + "zoom-in": (value) => ({ "--tw-enter-scale": value }), + "zoom-out": (value) => ({ "--tw-exit-scale": value }), + }, + { values: theme("animationScale") }, + ) + + matchUtilities( + { + "spin-in": (value) => ({ "--tw-enter-rotate": value }), + "spin-out": (value) => ({ "--tw-exit-rotate": value }), + }, + { values: theme("animationRotate") }, + ) + + matchUtilities( + { + "slide-in-from-top": (value) => ({ + "--tw-enter-translate-y": `-${value}`, + }), + "slide-in-from-bottom": (value) => ({ + "--tw-enter-translate-y": value, + }), + "slide-in-from-left": (value) => ({ + "--tw-enter-translate-x": `-${value}`, + }), + "slide-in-from-right": (value) => ({ + "--tw-enter-translate-x": value, + }), + "slide-out-to-top": (value) => ({ + "--tw-exit-translate-y": `-${value}`, + }), + "slide-out-to-bottom": (value) => ({ + "--tw-exit-translate-y": value, + }), + "slide-out-to-left": (value) => ({ + "--tw-exit-translate-x": `-${value}`, + }), + "slide-out-to-right": (value) => ({ + "--tw-exit-translate-x": value, + }), + }, + { values: theme("animationTranslate") }, + ) + + matchUtilities( + { duration: (value) => ({ animationDuration: value }) }, + { values: filterDefault(theme("animationDuration")) }, + ) + + matchUtilities( + { delay: (value) => ({ animationDelay: value }) }, + { values: theme("animationDelay") }, + ) + + matchUtilities( + { ease: (value) => ({ animationTimingFunction: value }) }, + { values: filterDefault(theme("animationTimingFunction")) }, + ) + + addUtilities({ + ".running": { animationPlayState: "running" }, + ".paused": { animationPlayState: "paused" }, + }) + + matchUtilities( + { "fill-mode": (value) => ({ animationFillMode: value }) }, + { values: theme("animationFillMode") }, + ) + + matchUtilities( + { direction: (value) => ({ animationDirection: value }) }, + { values: theme("animationDirection") }, + ) + + matchUtilities( + { repeat: (value) => ({ animationIterationCount: value }) }, + { values: theme("animationRepeat") }, + ) + }, + { + theme: { + extend: { + animationDelay: ({ theme }) => ({ + ...theme("transitionDelay"), + }), + animationDuration: ({ theme }) => ({ + 0: "0ms", + ...theme("transitionDuration"), + }), + animationTimingFunction: ({ theme }) => ({ + ...theme("transitionTimingFunction"), + }), + animationFillMode: { + none: "none", + forwards: "forwards", + backwards: "backwards", + both: "both", + }, + animationDirection: { + normal: "normal", + reverse: "reverse", + alternate: "alternate", + "alternate-reverse": "alternate-reverse", + }, + animationOpacity: ({ theme }) => ({ + DEFAULT: 0, + ...theme("opacity"), + }), + animationTranslate: ({ theme }) => ({ + DEFAULT: "100%", + ...theme("translate"), + }), + animationScale: ({ theme }) => ({ + DEFAULT: 0, + ...theme("scale"), + }), + animationRotate: ({ theme }) => ({ + DEFAULT: "30deg", + ...theme("rotate"), + }), + animationRepeat: { + 0: "0", + 1: "1", + infinite: "infinite", + }, + keyframes: { + enter: { + from: { + opacity: "var(--tw-enter-opacity, 1)", + transform: + "translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0) scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))", + }, + }, + exit: { + to: { + opacity: "var(--tw-exit-opacity, 1)", + transform: + "translate3d(var(--tw-exit-translate-x, 0), var(--tw-exit-translate-y, 0), 0) scale3d(var(--tw-exit-scale, 1), var(--tw-exit-scale, 1), var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0))", + }, + }, + }, + }, + }, + }, +) diff --git a/assets/vendor/topbar.js b/assets/vendor/topbar.js new file mode 100644 index 0000000..0552337 --- /dev/null +++ b/assets/vendor/topbar.js @@ -0,0 +1,138 @@ +/** + * @license MIT + * topbar 3.0.0 + * http://buunguyen.github.io/topbar + * Copyright (c) 2024 Buu Nguyen + */ +(function (window, document) { + "use strict"; + + var canvas, + currentProgress, + showing, + progressTimerId = null, + fadeTimerId = null, + delayTimerId = null, + addEvent = function (elem, type, handler) { + if (elem.addEventListener) elem.addEventListener(type, handler, false); + else if (elem.attachEvent) elem.attachEvent("on" + type, handler); + else elem["on" + type] = handler; + }, + options = { + autoRun: true, + barThickness: 3, + barColors: { + 0: "rgba(26, 188, 156, .9)", + ".25": "rgba(52, 152, 219, .9)", + ".50": "rgba(241, 196, 15, .9)", + ".75": "rgba(230, 126, 34, .9)", + "1.0": "rgba(211, 84, 0, .9)", + }, + shadowBlur: 10, + shadowColor: "rgba(0, 0, 0, .6)", + className: null, + }, + repaint = function () { + canvas.width = window.innerWidth; + canvas.height = options.barThickness * 5; // need space for shadow + + var ctx = canvas.getContext("2d"); + ctx.shadowBlur = options.shadowBlur; + ctx.shadowColor = options.shadowColor; + + var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0); + for (var stop in options.barColors) + lineGradient.addColorStop(stop, options.barColors[stop]); + ctx.lineWidth = options.barThickness; + ctx.beginPath(); + ctx.moveTo(0, options.barThickness / 2); + ctx.lineTo( + Math.ceil(currentProgress * canvas.width), + options.barThickness / 2 + ); + ctx.strokeStyle = lineGradient; + ctx.stroke(); + }, + createCanvas = function () { + canvas = document.createElement("canvas"); + var style = canvas.style; + style.position = "fixed"; + style.top = style.left = style.right = style.margin = style.padding = 0; + style.zIndex = 100001; + style.display = "none"; + if (options.className) canvas.classList.add(options.className); + addEvent(window, "resize", repaint); + }, + topbar = { + config: function (opts) { + for (var key in opts) + if (options.hasOwnProperty(key)) options[key] = opts[key]; + }, + show: function (delay) { + if (showing) return; + if (delay) { + if (delayTimerId) return; + delayTimerId = setTimeout(() => topbar.show(), delay); + } else { + showing = true; + if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId); + if (!canvas) createCanvas(); + if (!canvas.parentElement) document.body.appendChild(canvas); + canvas.style.opacity = 1; + canvas.style.display = "block"; + topbar.progress(0); + if (options.autoRun) { + (function loop() { + progressTimerId = window.requestAnimationFrame(loop); + topbar.progress( + "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2) + ); + })(); + } + } + }, + progress: function (to) { + if (typeof to === "undefined") return currentProgress; + if (typeof to === "string") { + to = + (to.indexOf("+") >= 0 || to.indexOf("-") >= 0 + ? currentProgress + : 0) + parseFloat(to); + } + currentProgress = to > 1 ? 1 : to; + repaint(); + return currentProgress; + }, + hide: function () { + clearTimeout(delayTimerId); + delayTimerId = null; + if (!showing) return; + showing = false; + if (progressTimerId != null) { + window.cancelAnimationFrame(progressTimerId); + progressTimerId = null; + } + (function loop() { + if (topbar.progress("+.1") >= 1) { + canvas.style.opacity -= 0.05; + if (canvas.style.opacity <= 0.05) { + canvas.style.display = "none"; + fadeTimerId = null; + return; + } + } + fadeTimerId = window.requestAnimationFrame(loop); + })(); + }, + }; + + if (typeof module === "object" && typeof module.exports === "object") { + module.exports = topbar; + } else if (typeof define === "function" && define.amd) { + define(function () { + return topbar; + }); + } else { + this.topbar = topbar; + } +}.call(this, window, document)); diff --git a/config/config.exs b/config/config.exs new file mode 100644 index 0000000..27eb817 --- /dev/null +++ b/config/config.exs @@ -0,0 +1,70 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Config module. +# +# This configuration file is loaded before any dependency and +# is restricted to this project. + +# General application configuration +import Config + +config :river_connect, + ecto_repos: [RiverConnect.Repo], + generators: [timestamp_type: :utc_datetime] + +# Configure the endpoint +config :river_connect, RiverConnectWeb.Endpoint, + url: [host: "localhost"], + adapter: Bandit.PhoenixAdapter, + render_errors: [ + formats: [html: RiverConnectWeb.ErrorHTML, json: RiverConnectWeb.ErrorJSON], + layout: false + ], + pubsub_server: RiverConnect.PubSub, + live_view: [signing_salt: "E5aSlroC"] + +# Configure the mailer +# +# By default it uses the "Local" adapter which stores the emails +# locally. You can see the emails in your browser, at "/dev/mailbox". +# +# For production it's recommended to configure a different adapter +# at the `config/runtime.exs`. +config :river_connect, RiverConnect.Mailer, adapter: Swoosh.Adapters.Local + +# Configure esbuild (the version is required) +config :esbuild, + version: "0.25.4", + river_connect: [ + args: + ~w(js/app.js --bundle --target=es2022 --outdir=../priv/static/assets/js --external:/fonts/* --external:/images/* --alias:@=.), + cd: Path.expand("../assets", __DIR__), + env: %{"NODE_PATH" => [Path.expand("../deps", __DIR__), Mix.Project.build_path()]} + ] + +# Configure tailwind (the version is required) +config :tailwind, + version: "4.1.12", + river_connect: [ + args: ~w( + --input=assets/css/app.css + --output=priv/static/assets/css/app.css + ), + cd: Path.expand("..", __DIR__) + ] + +# Configure Elixir's Logger +config :logger, :default_formatter, + format: "$time $metadata[$level] $message\n", + metadata: [:request_id] + +# Use Jason for JSON parsing in Phoenix +config :phoenix, :json_library, Jason + +config :river_connect, Oban, + repo: RiverConnect.Repo, + queues: [default: 10], + plugins: [Oban.Plugins.Pruner] + +# Import environment specific config. This must remain at the bottom +# of this file so it overrides the configuration defined above. +import_config "#{config_env()}.exs" diff --git a/config/dev.exs b/config/dev.exs new file mode 100644 index 0000000..834de24 --- /dev/null +++ b/config/dev.exs @@ -0,0 +1,92 @@ +import Config + +# Configure your database +config :river_connect, RiverConnect.Repo, + username: "admin", + password: "21Nt26", + hostname: "localhost", + database: "river_connect_dev", + stacktrace: true, + show_sensitive_data_on_connection_error: true, + pool_size: 10 + +# For development, we disable any cache and enable +# debugging and code reloading. +# +# The watchers configuration can be used to run external +# watchers to your application. For example, we can use it +# to bundle .js and .css sources. +config :river_connect, RiverConnectWeb.Endpoint, + # Binding to loopback ipv4 address prevents access from other machines. + # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. + http: [ip: {127, 0, 0, 1}, port: 4005], + check_origin: false, + code_reloader: true, + debug_errors: true, + secret_key_base: "yzfuJmd+Bd93mCTaH1Yo5MNn1buUd4qrO5BoJKfgvSgbSAP3Pe0IMD86k0WB+8wi", + watchers: [ + esbuild: {Esbuild, :install_and_run, [:river_connect, ~w(--sourcemap=inline --watch)]}, + tailwind: {Tailwind, :install_and_run, [:river_connect, ~w(--watch)]} + ] + +# ## SSL Support +# +# In order to use HTTPS in development, a self-signed +# certificate can be generated by running the following +# Mix task: +# +# mix phx.gen.cert +# +# Run `mix help phx.gen.cert` for more information. +# +# The `http:` config above can be replaced with: +# +# https: [ +# port: 4001, +# cipher_suite: :strong, +# keyfile: "priv/cert/selfsigned_key.pem", +# certfile: "priv/cert/selfsigned.pem" +# ], +# +# If desired, both `http:` and `https:` keys can be +# configured to run both http and https servers on +# different ports. + +# Reload browser tabs when matching files change. +config :river_connect, RiverConnectWeb.Endpoint, + live_reload: [ + web_console_logger: true, + patterns: [ + # Static assets, except user uploads + ~r"priv/static/(?!uploads/).*\.(js|css|png|jpeg|jpg|gif|svg)$"E, + # Gettext translations + ~r"priv/gettext/.*\.po$"E, + # Router, Controllers, LiveViews and LiveComponents + ~r"lib/river_connect_web/router\.ex$"E, + ~r"lib/river_connect_web/(controllers|live|components)/.*\.(ex|heex)$"E + ] + ] + +# Enable dev routes for dashboard and mailbox +config :river_connect, dev_routes: true + +# Do not include metadata nor timestamps in development logs +config :logger, :default_formatter, format: "[$level] $message\n" + +# Set a higher stacktrace during development. Avoid configuring such +# in production as building large stacktraces may be expensive. +config :phoenix, :stacktrace_depth, 20 + +# Initialize plugs at runtime for faster development compilation +config :phoenix, :plug_init_mode, :runtime + +config :phoenix_live_view, + # Include debug annotations and locations in rendered markup. + # Changing this configuration will require mix clean and a full recompile. + debug_heex_annotations: true, + debug_attributes: true, + # Enable helpful, but potentially expensive runtime checks + enable_expensive_runtime_checks: true + +# Disable swoosh api client as it is only required for production adapters. +config :swoosh, :api_client, false diff --git a/config/prod.exs b/config/prod.exs new file mode 100644 index 0000000..f578edd --- /dev/null +++ b/config/prod.exs @@ -0,0 +1,31 @@ +import Config + +# Note we also include the path to a cache manifest +# containing the digested version of static files. This +# manifest is generated by the `mix assets.deploy` task, +# which you should run after static files are built and +# before starting your production server. +config :river_connect, RiverConnectWeb.Endpoint, + cache_static_manifest: "priv/static/cache_manifest.json" + +# Force using SSL in production. This also sets the "strict-security-transport" header, +# known as HSTS. If you have a health check endpoint, you may want to exclude it below. +# Note `:force_ssl` is required to be set at compile-time. +config :river_connect, RiverConnectWeb.Endpoint, + force_ssl: [rewrite_on: [:x_forwarded_proto]], + exclude: [ + # paths: ["/health"], + hosts: ["localhost", "127.0.0.1"] + ] + +# Configure Swoosh API Client +config :swoosh, api_client: Swoosh.ApiClient.Req + +# Disable Swoosh Local Memory Storage +config :swoosh, local: false + +# Do not print debug messages in production +config :logger, level: :info + +# Runtime production configuration, including reading +# of environment variables, is done on config/runtime.exs. diff --git a/config/runtime.exs b/config/runtime.exs new file mode 100644 index 0000000..e152ee6 --- /dev/null +++ b/config/runtime.exs @@ -0,0 +1,120 @@ +import Config + +# config/runtime.exs is executed for all environments, including +# during releases. It is executed after compilation and before the +# system starts, so it is typically used to load production configuration +# and secrets from environment variables or elsewhere. Do not define +# any compile-time configuration in here, as it won't be applied. +# The block below contains prod specific runtime configuration. + +# ## Using releases +# +# If you use `mix release`, you need to explicitly enable the server +# by passing the PHX_SERVER=true when you start it: +# +# PHX_SERVER=true bin/river_connect start +# +# Alternatively, you can use `mix phx.gen.release` to generate a `bin/server` +# script that automatically sets the env var above. +if System.get_env("PHX_SERVER") do + config :river_connect, RiverConnectWeb.Endpoint, server: true +end + +config :river_connect, RiverConnectWeb.Endpoint, + http: [port: String.to_integer(System.get_env("PORT", "4005"))] + +if config_env() == :prod do + database_url = + System.get_env("DATABASE_URL") || + raise """ + environment variable DATABASE_URL is missing. + For example: ecto://USER:PASS@HOST/DATABASE + """ + + maybe_ipv6 = if System.get_env("ECTO_IPV6") in ~w(true 1), do: [:inet6], else: [] + + config :river_connect, RiverConnect.Repo, + # ssl: true, + url: database_url, + pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"), + # For machines with several cores, consider starting multiple pools of `pool_size` + # pool_count: 4, + socket_options: maybe_ipv6 + + # The secret key base is used to sign/encrypt cookies and other secrets. + # A default value is used in config/dev.exs and config/test.exs but you + # want to use a different value for prod and you most likely don't want + # to check this value into version control, so we use an environment + # variable instead. + secret_key_base = + System.get_env("SECRET_KEY_BASE") || + raise """ + environment variable SECRET_KEY_BASE is missing. + You can generate one by calling: mix phx.gen.secret + """ + + host = System.get_env("PHX_HOST") || "example.com" + + config :river_connect, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY") + + config :river_connect, RiverConnectWeb.Endpoint, + url: [host: host, port: 443, scheme: "https"], + http: [ + # Enable IPv6 and bind on all interfaces. + # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. + # See the documentation on https://hexdocs.pm/bandit/Bandit.html#t:options/0 + # for details about using IPv6 vs IPv4 and loopback vs public addresses. + ip: {0, 0, 0, 0, 0, 0, 0, 0} + ], + secret_key_base: secret_key_base + + # ## SSL Support + # + # To get SSL working, you will need to add the `https` key + # to your endpoint configuration: + # + # config :river_connect, RiverConnectWeb.Endpoint, + # https: [ + # ..., + # port: 443, + # cipher_suite: :strong, + # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), + # certfile: System.get_env("SOME_APP_SSL_CERT_PATH") + # ] + # + # The `cipher_suite` is set to `:strong` to support only the + # latest and more secure SSL ciphers. This means old browsers + # and clients may not be supported. You can set it to + # `:compatible` for wider support. + # + # `:keyfile` and `:certfile` expect an absolute path to the key + # and cert in disk or a relative path inside priv, for example + # "priv/ssl/server.key". For all supported SSL configuration + # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 + # + # We also recommend setting `force_ssl` in your config/prod.exs, + # ensuring no data is ever sent via http, always redirecting to https: + # + # config :river_connect, RiverConnectWeb.Endpoint, + # force_ssl: [hsts: true] + # + # Check `Plug.SSL` for all available options in `force_ssl`. + + # ## Configuring the mailer + # + # In production you need to configure the mailer to use a different adapter. + # Here is an example configuration for Mailgun: + # + # config :river_connect, RiverConnect.Mailer, + # adapter: Swoosh.Adapters.Mailgun, + # api_key: System.get_env("MAILGUN_API_KEY"), + # domain: System.get_env("MAILGUN_DOMAIN") + # + # Most non-SMTP adapters require an API client. Swoosh supports Req, Hackney, + # and Finch out-of-the-box. This configuration is typically done at + # compile-time in your config/prod.exs: + # + # config :swoosh, :api_client, Swoosh.ApiClient.Req + # + # See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details. +end diff --git a/config/test.exs b/config/test.exs new file mode 100644 index 0000000..a98a641 --- /dev/null +++ b/config/test.exs @@ -0,0 +1,41 @@ +import Config + +# Configure your database +# +# The MIX_TEST_PARTITION environment variable can be used +# to provide built-in test partitioning in CI environment. +# Run `mix help test` for more information. +config :river_connect, RiverConnect.Repo, + username: "admin", + password: "21Nt26", + hostname: "localhost", + database: "river_connect_test", + pool: Ecto.Adapters.SQL.Sandbox, + pool_size: 10 + +# We don't run a server during test. If one is required, +# you can enable the server option below. +config :river_connect, RiverConnectWeb.Endpoint, + http: [ip: {127, 0, 0, 1}, port: 4006], + secret_key_base: "nL8KkeOJhgu4Emju7nfXUwuWALkDLVPeg0koFDaCZaU8V8Ug12QKt9qUdRzak1Ll", + server: false + +# In test we don't send emails +config :river_connect, RiverConnect.Mailer, adapter: Swoosh.Adapters.Test + +# Disable swoosh api client as it is only required for production adapters +config :swoosh, :api_client, false + +# Print only warnings and errors during test +config :logger, level: :warning + +# Initialize plugs at runtime for faster test compilation +config :phoenix, :plug_init_mode, :runtime + +# Enable helpful, but potentially expensive runtime checks +config :phoenix_live_view, + enable_expensive_runtime_checks: true + +# Sort query params output of verified routes for robust url comparisons +config :phoenix, + sort_verified_routes_query_params: true diff --git a/lib/river_connect.ex b/lib/river_connect.ex new file mode 100644 index 0000000..8988b6e --- /dev/null +++ b/lib/river_connect.ex @@ -0,0 +1,9 @@ +defmodule RiverConnect do + @moduledoc """ + RiverConnect keeps the contexts that define your domain + and business logic. + + Contexts are also responsible for managing your data, regardless + if it comes from the database, an external API or others. + """ +end diff --git a/lib/river_connect/application.ex b/lib/river_connect/application.ex new file mode 100644 index 0000000..965e047 --- /dev/null +++ b/lib/river_connect/application.ex @@ -0,0 +1,36 @@ +defmodule RiverConnect.Application do + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + @moduledoc false + + use Application + + @impl true + def start(_type, _args) do + children = [ + TwMerge.Cache, + RiverConnectWeb.Telemetry, + RiverConnect.Repo, + {DNSCluster, query: Application.get_env(:river_connect, :dns_cluster_query) || :ignore}, + {Phoenix.PubSub, name: RiverConnect.PubSub}, + # Start a worker by calling: RiverConnect.Worker.start_link(arg) + # {RiverConnect.Worker, arg}, + # Start to serve requests, typically the last entry + {Oban, Application.fetch_env!(:river_connect, Oban)}, + RiverConnectWeb.Endpoint + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: RiverConnect.Supervisor] + Supervisor.start_link(children, opts) + end + + # Tell Phoenix to update the endpoint configuration + # whenever the application is updated. + @impl true + def config_change(changed, _new, removed) do + RiverConnectWeb.Endpoint.config_change(changed, removed) + :ok + end +end diff --git a/lib/river_connect/audio.ex b/lib/river_connect/audio.ex new file mode 100644 index 0000000..753f61e --- /dev/null +++ b/lib/river_connect/audio.ex @@ -0,0 +1,104 @@ +defmodule RiverConnect.Audio do + @moduledoc """ + The Audio context. + """ + + import Ecto.Query, warn: false + alias RiverConnect.Repo + + alias RiverConnect.Audio.Message + + @doc """ + Returns the list of audio_messages. + + ## Examples + + iex> list_messages() + [%Message{}, ...] + + """ + def list_messages do + Repo.all(from m in Message, order_by: [desc: m.inserted_at]) + end + + @doc """ + Gets a single message. + + Raises `Ecto.NoResultsError` if the Message does not exist. + + ## Examples + + iex> get_message!(123) + %Message{} + + iex> get_message!(456) + ** (Ecto.NoResultsError) + + """ + def get_message!(id), do: Repo.get!(Message, id) + + @doc """ + Creates a message. + + ## Examples + + iex> create_message(%{field: value}) + {:ok, %Message{}} + + iex> create_message(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_message(attrs \\ %{}) do + %Message{} + |> Message.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a message. + + ## Examples + + iex> update_message(message, %{field: new_value}) + {:ok, %Message{}} + + iex> update_message(message, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_message(%Message{} = message, attrs) do + message + |> Message.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a message. + + ## Examples + + iex> delete_message(message) + {:ok, %Message{}} + + iex> delete_message(message) + {:error, %Ecto.Changeset{}} + + """ + def delete_message(%Message{} = message) do + Repo.delete(message) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking message changes. + + ## Examples + + iex> change_message(message) + %Ecto.Changeset{data: %Message{}} + + """ + def change_message(%Message{} = message, attrs \\ %{}) do + Message.changeset(message, attrs) + end +end diff --git a/lib/river_connect/audio/message.ex b/lib/river_connect/audio/message.ex new file mode 100644 index 0000000..695b3ef --- /dev/null +++ b/lib/river_connect/audio/message.ex @@ -0,0 +1,21 @@ +defmodule RiverConnect.Audio.Message do + use Ecto.Schema + import Ecto.Changeset + + @derive {Jason.Encoder, only: [:id, :user_id, :file_path, :duration_ms, :status, :inserted_at]} + schema "audio_messages" do + field :status, :string + field :user_id, :string + field :file_path, :string + field :duration_ms, :integer + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(message, attrs) do + message + |> cast(attrs, [:user_id, :file_path, :duration_ms, :status]) + |> validate_required([:user_id, :file_path, :status]) + end +end diff --git a/lib/river_connect/mailer.ex b/lib/river_connect/mailer.ex new file mode 100644 index 0000000..4a46875 --- /dev/null +++ b/lib/river_connect/mailer.ex @@ -0,0 +1,3 @@ +defmodule RiverConnect.Mailer do + use Swoosh.Mailer, otp_app: :river_connect +end diff --git a/lib/river_connect/repo.ex b/lib/river_connect/repo.ex new file mode 100644 index 0000000..4edf382 --- /dev/null +++ b/lib/river_connect/repo.ex @@ -0,0 +1,5 @@ +defmodule RiverConnect.Repo do + use Ecto.Repo, + otp_app: :river_connect, + adapter: Ecto.Adapters.Postgres +end diff --git a/lib/river_connect/workers/process_recording.ex b/lib/river_connect/workers/process_recording.ex new file mode 100644 index 0000000..b218652 --- /dev/null +++ b/lib/river_connect/workers/process_recording.ex @@ -0,0 +1,25 @@ +defmodule RiverConnect.Workers.ProcessRecording do + use Oban.Worker, queue: :default + + alias RiverConnect.Audio + + @impl Oban.Worker + def perform(%Oban.Job{args: %{"id" => id}}) do + case Audio.get_message!(id) do + %Audio.Message{} = message -> + # Simulate processing time (e.g. transcoding or upload) + Process.sleep(1000) + + # Update status to "ready" + {:ok, _updated_message} = Audio.update_message(message, %{status: "ready"}) + + # Broadcast update + RiverConnectWeb.Endpoint.broadcast!("audio:lobby", "audio_message_ready", %{id: message.id}) + + :ok + + _ -> + {:error, :not_found} + end + end +end diff --git a/lib/river_connect_web.ex b/lib/river_connect_web.ex new file mode 100644 index 0000000..a72f666 --- /dev/null +++ b/lib/river_connect_web.ex @@ -0,0 +1,114 @@ +defmodule RiverConnectWeb do + @moduledoc """ + The entrypoint for defining your web interface, such + as controllers, components, channels, and so on. + + This can be used in your application as: + + use RiverConnectWeb, :controller + use RiverConnectWeb, :html + + The definitions below will be executed for every controller, + component, etc, so keep them short and clean, focused + on imports, uses and aliases. + + Do NOT define functions inside the quoted expressions + below. Instead, define additional modules and import + those modules here. + """ + + def static_paths, do: ~w(assets fonts images favicon.ico robots.txt) + + def router do + quote do + use Phoenix.Router, helpers: false + + # Import common connection and controller functions to use in pipelines + import Plug.Conn + import Phoenix.Controller + import Phoenix.LiveView.Router + end + end + + def channel do + quote do + use Phoenix.Channel + end + end + + def controller do + quote do + use Phoenix.Controller, formats: [:html, :json] + + use Gettext, backend: RiverConnectWeb.Gettext + + import Plug.Conn + + unquote(verified_routes()) + end + end + + def live_view do + quote do + use Phoenix.LiveView + + unquote(html_helpers()) + end + end + + def live_component do + quote do + use Phoenix.LiveComponent + + unquote(html_helpers()) + end + end + + def html do + quote do + use Phoenix.Component + + # Import convenience functions from controllers + import Phoenix.Controller, + only: [get_csrf_token: 0, view_module: 1, view_template: 1] + + # Include general helpers for rendering HTML + unquote(html_helpers()) + end + end + + defp html_helpers do + quote do + # Translation + use Gettext, backend: RiverConnectWeb.Gettext + + # HTML escaping functionality + import Phoenix.HTML + # Core UI components + import RiverConnectWeb.CoreComponents + + # Common modules used in templates + alias Phoenix.LiveView.JS + alias RiverConnectWeb.Layouts + + # Routes generation with the ~p sigil + unquote(verified_routes()) + end + end + + def verified_routes do + quote do + use Phoenix.VerifiedRoutes, + endpoint: RiverConnectWeb.Endpoint, + router: RiverConnectWeb.Router, + statics: RiverConnectWeb.static_paths() + end + end + + @doc """ + When used, dispatch to the appropriate controller/live_view/etc. + """ + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, []) + end +end diff --git a/lib/river_connect_web/channels/audio_channel.ex b/lib/river_connect_web/channels/audio_channel.ex new file mode 100644 index 0000000..4ee6b9a --- /dev/null +++ b/lib/river_connect_web/channels/audio_channel.ex @@ -0,0 +1,79 @@ +defmodule RiverConnectWeb.AudioChannel do + use RiverConnectWeb, :channel + alias RiverConnect.Audio + + @impl true + def join("audio:lobby", _payload, socket) do + {:ok, socket} + end + + @impl true + def handle_in("audio_start", %{"id" => id}, socket) do + # Ensure upload directory exists + upload_path = Application.app_dir(:river_connect, "priv/static/uploads") + File.mkdir_p!(upload_path) + + user_id = socket.assigns[:user_id] || "guest" + + # Create DB record immediately with "recording" status + {:ok, message} = + Audio.create_message(%{ + user_id: user_id, + file_path: "/uploads/#{id}.webm", + status: "recording" + }) + + # Store temporary file path and message_id in socket assigns + temp_file_path = Path.join(upload_path, "#{id}.webm") + + # Broadcast start and new message to other listeners + broadcast_from!(socket, "audio_start", %{id: id}) + broadcast!(socket, "audio_message_created", %{message: message}) + + {:noreply, + assign(socket, :current_recording, %{id: id, path: temp_file_path, message_id: message.id})} + end + + @impl true + def handle_in("audio_chunk", {:binary, payload}, socket) do + case socket.assigns[:current_recording] do + %{path: path} -> + # Append binary chunk to file + File.write!(path, payload, [:append]) + # Broadcast chunk to other listeners for live playback + broadcast_from!(socket, "audio_chunk", %{data: Base.encode64(payload)}) + {:noreply, socket} + + _ -> + {:noreply, socket} + end + end + + @impl true + def handle_in("audio_end", %{"duration_ms" => duration_ms}, socket) do + case socket.assigns[:current_recording] do + %{message_id: message_id} -> + message = Audio.get_message!(message_id) + + # Update DB record to "processing" status + {:ok, message} = + Audio.update_message(message, %{ + duration_ms: duration_ms, + status: "processing" + }) + + # Queue Oban job for processing + %{id: message.id} + |> RiverConnect.Workers.ProcessRecording.new() + |> Oban.insert!() + + # Notify UI that message status changed + broadcast!(socket, "audio_message_ready", %{id: message.id}) + + {:noreply, assign(socket, :current_recording, nil)} + + _ -> + {:noreply, socket} + end + end +end diff --git a/lib/river_connect_web/channels/user_socket.ex b/lib/river_connect_web/channels/user_socket.ex new file mode 100644 index 0000000..44904b7 --- /dev/null +++ b/lib/river_connect_web/channels/user_socket.ex @@ -0,0 +1,37 @@ +defmodule RiverConnectWeb.UserSocket do + use Phoenix.Socket + + ## Channels + channel "audio:*", RiverConnectWeb.AudioChannel + + # Socket params are passed from the client and can + # be used to verify and authenticate a user. After + # verification, you can put default assigns into + # the socket that will be set for all channels, ie + # + # {:ok, assign(socket, :user_id, verified_user_id)} + # + # To deny connection, return `:error`. + # + # See `Phoenix.Token` documentation for examples in + # performing token verification on connect. + @impl true + def connect(params, socket, _connect_info) do + # Simple user identification using client-provided ID or generating one + user_id = params["user_id"] || Ecto.UUID.generate() + {:ok, assign(socket, :user_id, user_id)} + end + + # Socket id's are topics that allow you to identify all sockets for a given user: + # + # def id(socket), do: "user_socket:#{socket.assigns.user_id}" + # + # Would allow you to broadcast a "disconnect" event and terminate + # all active sockets and channels for a given user: + # + # Elixir.RiverConnectWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{}) + # + # Returning `nil` makes this socket anonymous. + @impl true + def id(_socket), do: nil +end diff --git a/lib/river_connect_web/components/core_components.ex b/lib/river_connect_web/components/core_components.ex new file mode 100644 index 0000000..d54a16d --- /dev/null +++ b/lib/river_connect_web/components/core_components.ex @@ -0,0 +1,498 @@ +defmodule RiverConnectWeb.CoreComponents do + @moduledoc """ + Provides core UI components. + + At first glance, this module may seem daunting, but its goal is to provide + core building blocks for your application, such as tables, forms, and + inputs. The components consist mostly of markup and are well-documented + with doc strings and declarative assigns. You may customize and style + them in any way you want, based on your application growth and needs. + + The foundation for styling is Tailwind CSS, a utility-first CSS framework, + augmented with daisyUI, a Tailwind CSS plugin that provides UI components + and themes. Here are useful references: + + * [daisyUI](https://daisyui.com/docs/intro/) - a good place to get + started and see the available components. + + * [Tailwind CSS](https://tailwindcss.com) - the foundational framework + we build on. You will use it for layout, sizing, flexbox, grid, and + spacing. + + * [Heroicons](https://heroicons.com) - see `icon/1` for usage. + + * [Phoenix.Component](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html) - + the component system used by Phoenix. Some components, such as `<.link>` + and `<.form>`, are defined there. + + """ + use Phoenix.Component + use Gettext, backend: RiverConnectWeb.Gettext + + alias Phoenix.LiveView.JS + + @doc """ + Renders flash notices. + + ## Examples + + <.flash kind={:info} flash={@flash} /> + <.flash kind={:info} phx-mounted={show("#flash")}>Welcome Back! + """ + attr :id, :string, doc: "the optional id of flash container" + attr :flash, :map, default: %{}, doc: "the map of flash messages to display" + attr :title, :string, default: nil + attr :kind, :atom, values: [:info, :error], doc: "used for styling and flash lookup" + attr :rest, :global, doc: "the arbitrary HTML attributes to add to the flash container" + + slot :inner_block, doc: "the optional inner block that renders the flash message" + + def flash(assigns) do + assigns = assign_new(assigns, :id, fn -> "flash-#{assigns.kind}" end) + + ~H""" +
hide("##{@id}")} + role="alert" + class="toast toast-top toast-end z-50" + {@rest} + > +
+ <.icon :if={@kind == :info} name="hero-information-circle" class="size-5 shrink-0" /> + <.icon :if={@kind == :error} name="hero-exclamation-circle" class="size-5 shrink-0" /> +
+

{@title}

+

{msg}

+
+
+ +
+
+ """ + end + + @doc """ + Renders a button with navigation support. + + ## Examples + + <.button>Send! + <.button phx-click="go" variant="primary">Send! + <.button navigate={~p"/"}>Home + """ + attr :rest, :global, include: ~w(href navigate patch method download name value disabled) + attr :class, :any + attr :variant, :string, values: ~w(primary) + slot :inner_block, required: true + + def button(%{rest: rest} = assigns) do + variants = %{"primary" => "btn-primary", nil => "btn-primary btn-soft"} + + assigns = + assign_new(assigns, :class, fn -> + ["btn", Map.fetch!(variants, assigns[:variant])] + end) + + if rest[:href] || rest[:navigate] || rest[:patch] do + ~H""" + <.link class={@class} {@rest}> + {render_slot(@inner_block)} + + """ + else + ~H""" + + """ + end + end + + @doc """ + Renders an input with label and error messages. + + A `Phoenix.HTML.FormField` may be passed as argument, + which is used to retrieve the input name, id, and values. + Otherwise all attributes may be passed explicitly. + + ## Types + + This function accepts all HTML input types, considering that: + + * You may also set `type="select"` to render a ` + """ + end + + def input(%{type: "checkbox"} = assigns) do + assigns = + assign_new(assigns, :checked, fn -> + Phoenix.HTML.Form.normalize_value("checkbox", assigns[:value]) + end) + + ~H""" +
+ + <.error :for={msg <- @errors}>{msg} +
+ """ + end + + def input(%{type: "select"} = assigns) do + ~H""" +
+ + <.error :for={msg <- @errors}>{msg} +
+ """ + end + + def input(%{type: "textarea"} = assigns) do + ~H""" +
+ + <.error :for={msg <- @errors}>{msg} +
+ """ + end + + # All other inputs text, datetime-local, url, password, etc. are handled here... + def input(assigns) do + ~H""" +
+ + <.error :for={msg <- @errors}>{msg} +
+ """ + end + + # Helper used by inputs to generate form errors + defp error(assigns) do + ~H""" +

+ <.icon name="hero-exclamation-circle" class="size-5" /> + {render_slot(@inner_block)} +

+ """ + end + + @doc """ + Renders a header with title. + """ + slot :inner_block, required: true + slot :subtitle + slot :actions + + def header(assigns) do + ~H""" +
+
+

+ {render_slot(@inner_block)} +

+

+ {render_slot(@subtitle)} +

+
+
{render_slot(@actions)}
+
+ """ + end + + @doc """ + Renders a table with generic styling. + + ## Examples + + <.table id="users" rows={@users}> + <:col :let={user} label="id">{user.id} + <:col :let={user} label="username">{user.username} + + """ + attr :id, :string, required: true + attr :rows, :list, required: true + attr :row_id, :any, default: nil, doc: "the function for generating the row id" + attr :row_click, :any, default: nil, doc: "the function for handling phx-click on each row" + + attr :row_item, :any, + default: &Function.identity/1, + doc: "the function for mapping each row before calling the :col and :action slots" + + slot :col, required: true do + attr :label, :string + end + + slot :action, doc: "the slot for showing user actions in the last table column" + + def table(assigns) do + assigns = + with %{rows: %Phoenix.LiveView.LiveStream{}} <- assigns do + assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end) + end + + ~H""" + + + + + + + + + + + + + +
{col[:label]} + {gettext("Actions")} +
+ {render_slot(col, @row_item.(row))} + +
+ <%= for action <- @action do %> + {render_slot(action, @row_item.(row))} + <% end %> +
+
+ """ + end + + @doc """ + Renders a data list. + + ## Examples + + <.list> + <:item title="Title">{@post.title} + <:item title="Views">{@post.views} + + """ + slot :item, required: true do + attr :title, :string, required: true + end + + def list(assigns) do + ~H""" +
    +
  • +
    +
    {item.title}
    +
    {render_slot(item)}
    +
    +
  • +
+ """ + end + + @doc """ + Renders a [Heroicon](https://heroicons.com). + + Heroicons come in three styles – outline, solid, and mini. + By default, the outline style is used, but solid and mini may + be applied by using the `-solid` and `-mini` suffix. + + You can customize the size and colors of the icons by setting + width, height, and background color classes. + + Icons are extracted from the `deps/heroicons` directory and bundled within + your compiled app.css by the plugin in `assets/vendor/heroicons.js`. + + ## Examples + + <.icon name="hero-x-mark" /> + <.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" /> + """ + attr :name, :string, required: true + attr :class, :any, default: "size-4" + + def icon(%{name: "hero-" <> _} = assigns) do + ~H""" + + """ + end + + ## JS Commands + + def show(js \\ %JS{}, selector) do + JS.show(js, + to: selector, + time: 300, + transition: + {"transition-all ease-out duration-300", + "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95", + "opacity-100 translate-y-0 sm:scale-100"} + ) + end + + def hide(js \\ %JS{}, selector) do + JS.hide(js, + to: selector, + time: 200, + transition: + {"transition-all ease-in duration-200", "opacity-100 translate-y-0 sm:scale-100", + "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"} + ) + end + + @doc """ + Translates an error message using gettext. + """ + def translate_error({msg, opts}) do + # When using gettext, we typically pass the strings we want + # to translate as a static argument: + # + # # Translate the number of files with plural rules + # dngettext("errors", "1 file", "%{count} files", count) + # + # However the error messages in our forms and APIs are generated + # dynamically, so we need to translate them by calling Gettext + # with our gettext backend as first argument. Translations are + # available in the errors.po file (as we use the "errors" domain). + if count = opts[:count] do + Gettext.dngettext(RiverConnectWeb.Gettext, "errors", msg, msg, count, opts) + else + Gettext.dgettext(RiverConnectWeb.Gettext, "errors", msg, opts) + end + end + + @doc """ + Translates the errors for a field from a keyword list of errors. + """ + def translate_errors(errors, field) when is_list(errors) do + for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts}) + end +end diff --git a/lib/river_connect_web/components/layouts.ex b/lib/river_connect_web/components/layouts.ex new file mode 100644 index 0000000..bfd30b1 --- /dev/null +++ b/lib/river_connect_web/components/layouts.ex @@ -0,0 +1,154 @@ +defmodule RiverConnectWeb.Layouts do + @moduledoc """ + This module holds layouts and related functionality + used by your application. + """ + use RiverConnectWeb, :html + + # Embed all files in layouts/* within this module. + # The default root.html.heex file contains the HTML + # skeleton of your application, namely HTML headers + # and other static content. + embed_templates "layouts/*" + + @doc """ + Renders your app layout. + + This function is typically invoked from every template, + and it often contains your application menu, sidebar, + or similar. + + ## Examples + + +

Content

+
+ + """ + attr :flash, :map, required: true, doc: "the map of flash messages" + + attr :current_scope, :map, + default: nil, + doc: "the current [scope](https://hexdocs.pm/phoenix/scopes.html)" + + slot :inner_block, required: true + + def app(assigns) do + ~H""" +
+ +
+
+ {render_slot(@inner_block)} +
+
+ + <.flash_group flash={@flash} /> + """ + end + + @doc """ + Shows the flash group with standard titles and content. + + ## Examples + + <.flash_group flash={@flash} /> + """ + attr :flash, :map, required: true, doc: "the map of flash messages" + attr :id, :string, default: "flash-group", doc: "the optional id of flash container" + + def flash_group(assigns) do + ~H""" +
+ <.flash kind={:info} flash={@flash} /> + <.flash kind={:error} flash={@flash} /> + + <.flash + id="client-error" + kind={:error} + title={gettext("We can't find the internet")} + phx-disconnected={show(".phx-client-error #client-error") |> JS.remove_attribute("hidden")} + phx-connected={hide("#client-error") |> JS.set_attribute({"hidden", ""})} + hidden + > + {gettext("Attempting to reconnect")} + <.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" /> + + + <.flash + id="server-error" + kind={:error} + title={gettext("Something went wrong!")} + phx-disconnected={show(".phx-server-error #server-error") |> JS.remove_attribute("hidden")} + phx-connected={hide("#server-error") |> JS.set_attribute({"hidden", ""})} + hidden + > + {gettext("Attempting to reconnect")} + <.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" /> + +
+ """ + end + + @doc """ + Provides dark vs light theme toggle based on themes defined in app.css. + + See in root.html.heex which applies the theme before page load. + """ + def theme_toggle(assigns) do + ~H""" +
+
+ + + + + + +
+ """ + end +end diff --git a/lib/river_connect_web/components/layouts/root.html.heex b/lib/river_connect_web/components/layouts/root.html.heex new file mode 100644 index 0000000..cc68fd1 --- /dev/null +++ b/lib/river_connect_web/components/layouts/root.html.heex @@ -0,0 +1,36 @@ + + + + + + + <.live_title default="RiverConnect" suffix=" · Phoenix Framework"> + {assigns[:page_title]} + + + + + + + {@inner_content} + + diff --git a/lib/river_connect_web/components/ui.ex b/lib/river_connect_web/components/ui.ex new file mode 100644 index 0000000..d248094 --- /dev/null +++ b/lib/river_connect_web/components/ui.ex @@ -0,0 +1,220 @@ +defmodule RiverConnectWeb.Components.UI do + @moduledoc """ + SaladUI - A comprehensive UI component library for Phoenix LiveView applications. + + SaladUI provides a collection of accessible, customizable UI components that combine + server-side rendering with client-side interactivity. Built specifically for Phoenix + LiveView, it offers seamless integration between Elixir and JavaScript. + + ## Features + + - **40+ UI Components** - Buttons, forms, overlays, navigation, and data display + - **Accessibility First** - ARIA compliant with keyboard navigation and screen reader support + - **LiveView Integration** - Two-way communication between server and client + - **Customizable** - Tailwind CSS based with variant support + - **Type Safe** - Full attribute validation and documentation + + ## Quick Start + + Add SaladUI to your Phoenix application: + + # In your core components module + use SaladUI + + # In templates + <.button variant="primary" phx-click="save"> + Save Changes + + + ## Component Categories + + ### Layout & Structure + - `card/1` - Content containers with headers and footers + - `separator/1` - Visual dividers between content sections + - `scroll_area/1` - Custom scrollable containers + + ### Forms & Input + - `button/1` - Interactive buttons with multiple variants + - `input/1` - Text inputs with validation support + - `textarea/1` - Multi-line text input areas + - `checkbox/1` - Binary choice inputs + - `radio_group/1` - Single choice from multiple options + - `select/1` - Dropdown selection menus + - `slider/1` - Range value selection + - `switch/1` - Toggle controls + - `form/1`, `form_item/1`, `form_label/1` - Form structure and validation + + ### Navigation + - `tabs/1` - Tabbed content navigation + - `breadcrumb/1` - Hierarchical navigation paths + - `pagination/1` - Page navigation controls + - `sidebar/1` - Application navigation sidebars + + ### Overlays & Dialogs + - `dialog/1` - Modal dialogs and confirmations + - `alert_dialog/1` - Alert and confirmation dialogs + - `sheet/1` - Slide-out panels + - `popover/1` - Contextual popup content + - `tooltip/1` - Hover information displays + - `hover_card/1` - Rich hover content + - `dropdown_menu/1` - Contextual action menus + + ### Feedback & Status + - `alert/1` - Status messages and notifications + - `badge/1` - Labels and status indicators + - `progress/1` - Progress bars and loading states + - `skeleton/1` - Loading placeholder content + + ### Data Display + - `table/1` - Data tables with sorting and selection + - `chart/1` - Data visualization charts + - `avatar/1` - User profile images + - `accordion/1` - Collapsible content sections + + ### Utility + - `toggle/1`, `toggle_group/1` - Toggle controls and groups + - `collapsible/1` - Expandable content areas + - `command/1` - Command palette interfaces + + ## Architecture + + SaladUI uses a hybrid architecture combining: + + - **Phoenix Function Components** - Server-side rendering with HEEx templates + - **JavaScript State Machines** - Client-side behavior and interactivity + - **LiveView Hooks** - Seamless server-client communication + - **Automatic ARIA** - Accessibility attributes managed by the framework + + ## Usage Patterns + + ### Basic Component Usage + + <.button variant="outline" size="lg"> + Large Outline Button + + + ### Form Integration + + <.form for={@form} phx-change="validate" phx-submit="save"> + <.form_item> + <.form_label>Email + <.form_control> + <.input field={@form[:email]} type="email" /> + + <.form_message field={@form[:email]} /> + + + + ### Interactive Components with Events + + <.dialog id="user-dialog" on-open={JS.push("dialog_opened")}> + <.dialog_trigger> + <.button>Edit Profile + + <.dialog_content> + + + + + ### Server-Client Communication + + # Send commands to components from LiveView + def handle_event("open_dialog", _params, socket) do + socket = RiverConnectWeb.Components.UI.LiveView.send_command(socket, "user-dialog", "open") + {:noreply, socket} + end + + ## Customization + + Components support extensive customization through: + + - **Variants** - Pre-defined style variations (e.g., `variant="destructive"`) + - **Classes** - Custom CSS classes via the `class` attribute + - **Slots** - Flexible content areas within components + - **Attributes** - Comprehensive configuration options + + ## Accessibility + + All components include: + + - Proper ARIA attributes and roles + - Keyboard navigation support + - Focus management for overlays + - Screen reader announcements + - High contrast support + + ## Development + + When extending SaladUI: + + 1. **Follow naming conventions** - Use `kebab-case` for data attributes + 2. **Implement state machines** - Define clear states and transitions + 3. **Handle accessibility** - Include ARIA configuration + 4. **Test thoroughly** - Verify keyboard and screen reader usage + 5. **Document examples** - Provide clear usage examples + """ + + def component do + quote do + use Phoenix.Component + + import RiverConnectWeb.Components.UI.Helpers + + alias Phoenix.LiveView.JS + + defp classes(input) do + TwMerge.merge(List.flatten(input)) + end + end + end + + @doc """ + When used, dispatch to the appropriate macro. + """ + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, []) + end + + defmacro __using__(_) do + quote do + import RiverConnectWeb.Components.UI.Accordion + import RiverConnectWeb.Components.UI.Alert + import RiverConnectWeb.Components.UI.AlertDialog + import RiverConnectWeb.Components.UI.Avatar + import RiverConnectWeb.Components.UI.Badge + import RiverConnectWeb.Components.UI.Breadcrumb + import RiverConnectWeb.Components.UI.Button + import RiverConnectWeb.Components.UI.Card + import RiverConnectWeb.Components.UI.Chart + import RiverConnectWeb.Components.UI.Checkbox + import RiverConnectWeb.Components.UI.Collapsible + import RiverConnectWeb.Components.UI.Dialog + import RiverConnectWeb.Components.UI.DropdownMenu + import RiverConnectWeb.Components.UI.Form + import RiverConnectWeb.Components.UI.Helpers + import RiverConnectWeb.Components.UI.HoverCard + import RiverConnectWeb.Components.UI.Icon + import RiverConnectWeb.Components.UI.Input + import RiverConnectWeb.Components.UI.Label + import RiverConnectWeb.Components.UI.Menu + import RiverConnectWeb.Components.UI.Pagination + import RiverConnectWeb.Components.UI.Popover + import RiverConnectWeb.Components.UI.Progress + import RiverConnectWeb.Components.UI.RadioGroup + import RiverConnectWeb.Components.UI.ScrollArea + import RiverConnectWeb.Components.UI.Select + import RiverConnectWeb.Components.UI.Separator + import RiverConnectWeb.Components.UI.Sheet + import RiverConnectWeb.Components.UI.Sidebar + import RiverConnectWeb.Components.UI.Skeleton + import RiverConnectWeb.Components.UI.Slider + import RiverConnectWeb.Components.UI.Switch + import RiverConnectWeb.Components.UI.Table + import RiverConnectWeb.Components.UI.Tabs + import RiverConnectWeb.Components.UI.Textarea + import RiverConnectWeb.Components.UI.Toggle + import RiverConnectWeb.Components.UI.ToggleGroup + import RiverConnectWeb.Components.UI.Tooltip + end + end +end diff --git a/lib/river_connect_web/components/ui/accordion.ex b/lib/river_connect_web/components/ui/accordion.ex new file mode 100644 index 0000000..ba00fc0 --- /dev/null +++ b/lib/river_connect_web/components/ui/accordion.ex @@ -0,0 +1,208 @@ +defmodule RiverConnectWeb.Components.UI.Accordion do + @moduledoc """ + Implementation of the Accordion component. + + Accordions are vertically stacked sections that can be expanded/collapsed + to reveal their content. They are useful for breaking down complex content + into digestible sections. + + ## Examples: + + <.accordion + id="faq-accordion" + type="single" + default-value="item-1" + on-value-changed={JS.push("accordion_changed")} + > + <.accordion_item value="item-1"> + <.accordion_trigger> + Is it accessible? + + <.accordion_content> + Yes. It adheres to the WAI-ARIA design pattern. + + + <.accordion_item value="item-2"> + <.accordion_trigger> + Is it styled? + + <.accordion_content> + Yes. It comes with default styles that matches the other components' aesthetic. + + + + """ + use RiverConnectWeb.Components.UI, :component + + @doc """ + The main accordion component that manages the accordion state. + + ## Options + + * `:id` - Required unique identifier for the accordion. + * `:type` - Type of accordion: "single" or "multiple". Defaults to "single". + * `:value` - The currently expanded items. String for "single", list for "multiple". + * `:default-value` - The default expanded items. Used if `:value` is not provided. + * `:disabled` - Whether the accordion is disabled. Defaults to `false`. + * `:on-value-changed` - Handler for accordion value change event. + * `:class` - Additional CSS classes. + """ + attr :id, :string, required: true, doc: "Unique identifier for the accordion" + + attr :type, :string, + values: ~w(single multiple), + default: "single", + doc: "Whether only one item can be open at a time" + + attr :value, :any, default: nil, doc: "The value(s) of the currently expanded item(s)" + attr :"default-value", :any, default: nil, doc: "The default value(s) of the expanded item(s)" + attr :disabled, :boolean, default: false, doc: "Whether the accordion is disabled" + attr :"on-value-changed", :any, default: nil, doc: "Handler for accordion value change event" + attr :class, :string, default: nil + attr :rest, :global + slot :inner_block, required: true + + def accordion(assigns) do + # Collect event mappings + event_map = + add_event_mapping(%{}, assigns, "value-changed", :"on-value-changed") + + assigns = + assigns + |> assign(:event_map, Jason.encode!(event_map)) + |> assign( + :options, + Jason.encode!(%{ + type: assigns.type, + value: assigns.value, + defaultValue: assigns[:"default-value"], + disabled: assigns.disabled + }) + ) + + ~H""" +
+ {render_slot(@inner_block)} +
+ """ + end + + @doc """ + An accordion item that contains a trigger and content. + + ## Options + + * `:value` - Required unique value for this accordion item. + * `:disabled` - Whether this item is disabled. Defaults to `false`. + * `:class` - Additional CSS classes. + """ + attr :value, :string, required: true, doc: "Unique value for this accordion item" + attr :disabled, :boolean, default: false, doc: "Whether this item is disabled" + attr :class, :string, default: nil + attr :rest, :global + slot :inner_block, required: true + + def accordion_item(assigns) do + ~H""" +
+ {render_slot(@inner_block)} +
+ """ + end + + @doc """ + The trigger element that expands/collapses an accordion item. + + ## Options + + * `:class` - Additional CSS classes. + """ + attr :class, :string, default: nil + attr :rest, :global + slot :inner_block, required: true + + def accordion_trigger(assigns) do + ~H""" + + """ + end + + @doc """ + The content element of an accordion item that shows when expanded. + + ## Options + + * `:class` - Additional CSS classes. + """ + attr :class, :string, default: nil + attr :rest, :global + slot :inner_block, required: true + + def accordion_content(assigns) do + ~H""" + + """ + end +end diff --git a/lib/river_connect_web/components/ui/alert.ex b/lib/river_connect_web/components/ui/alert.ex new file mode 100644 index 0000000..ca24b1d --- /dev/null +++ b/lib/river_connect_web/components/ui/alert.ex @@ -0,0 +1,173 @@ +defmodule RiverConnectWeb.Components.UI.Alert do + @moduledoc """ + Implementation of alert component for displaying important messages to users. + + Alerts are used to communicate status, warnings, or contextual feedback to users. + They can be configured with different variants to indicate severity and typically + consist of a title and a description. + + ## Examples: + + <.alert> + <.alert_title>Note + <.alert_description>This is a standard informational alert. + + + <.alert variant="destructive"> + <.alert_title>Error + <.alert_description> + There was a problem with your request. Please try again. + + + + <.alert> + + + + + + <.alert_title>Success + <.alert_description>Your changes have been saved successfully. + + """ + use RiverConnectWeb.Components.UI, :component + + @doc """ + Renders an alert container. + + The alert component displays important messages or feedback to users with styling + appropriate for the context. + + ## Options + + * `:variant` - The visual style of the alert. Available variants: + * `"default"` - Standard alert styling (default) + * `"destructive"` - Red-tinted styling for errors and warnings + * `:class` - Additional CSS classes to apply to the alert container. + + ## Examples + + <.alert> + <.alert_title>Information + <.alert_description>This is an informational message. + + + <.alert variant="destructive" class="mt-4"> + <.alert_title>Warning + <.alert_description>This action cannot be undone. + + """ + attr :variant, :string, default: "default", values: ~w(default destructive) + attr :class, :string, default: nil + slot :inner_block, required: true + attr :rest, :global, default: %{} + + def alert(assigns) do + assigns = assign(assigns, :variant_class, variant(assigns)) + + ~H""" + + """ + end + + @doc """ + Renders an alert title. + + The title component is used to provide a concise heading for the alert message. + + ## Options + + * `:class` - Additional CSS classes to apply to the title. + + ## Examples + + <.alert_title>Success + <.alert_title class="text-primary">Important Notice + """ + attr :class, :string, default: nil + attr :rest, :global, include: ~w(disabled form name value) + slot :inner_block, required: true + + def alert_title(assigns) do + ~H""" +
+ {render_slot(@inner_block)} +
+ """ + end + + @doc """ + Renders an alert description. + + The description provides more detailed information about the alert message. + + ## Options + + * `:class` - Additional CSS classes to apply to the description. + + ## Examples + + <.alert_description>Your account has been updated successfully. + <.alert_description class="text-gray-600"> + Please review the changes before continuing. + + """ + attr :class, :string, default: nil + attr :rest, :global, include: ~w(disabled form name value) + slot :inner_block, required: true + + def alert_description(assigns) do + ~H""" +
+ {render_slot(@inner_block)} +
+ """ + end + + @variants %{ + variant: %{ + "default" => "bg-background text-foreground", + "destructive" => + "bg-background border-destructive/50 text-destructive dark:border-destructive [&>span]:text-destructive" + } + } + + @default_variants %{ + variant: "default" + } + + defp variant(variants) do + variants = Map.merge(@default_variants, variants) + + Enum.map_join(variants, " ", fn {key, value} -> @variants[key][value] end) + end +end diff --git a/lib/river_connect_web/components/ui/alert_dialog.ex b/lib/river_connect_web/components/ui/alert_dialog.ex new file mode 100644 index 0000000..6dca90b --- /dev/null +++ b/lib/river_connect_web/components/ui/alert_dialog.ex @@ -0,0 +1,331 @@ +defmodule RiverConnectWeb.Components.UI.AlertDialog do + @moduledoc """ + Implementation of Alert Dialog component for SaladUI framework. + + Alert Dialogs are modal dialogs that require a user action before they can be dismissed, + used to confirm user decisions or provide critical information. + + ## Examples: + + <.alert_dialog id="delete-confirmation"> + <.alert_dialog_trigger> + <.button variant="destructive">Delete Account + + <.alert_dialog_content> + <.alert_dialog_header> + <.alert_dialog_title>Are you absolutely sure? + <.alert_dialog_description> + This action cannot be undone. This will permanently delete your + account and remove your data from our servers. + + + <.alert_dialog_footer> + <.alert_dialog_cancel>Cancel + <.alert_dialog_action>Continue + + + + """ + use RiverConnectWeb.Components.UI, :component + + @doc """ + The main alert dialog component. + + ## Options + + * `:id` - Required unique identifier for the alert dialog. + * `:open` - Whether the alert dialog is initially open. Defaults to `false`. + * `:on-open` - Handler for alert dialog open event. + * `:on-close` - Handler for alert dialog close event. + * `:class` - Additional CSS classes. + """ + attr :id, :string, required: true, doc: "Unique identifier for the alert dialog" + attr :open, :boolean, default: false, doc: "Whether the alert dialog is initially open" + attr :"on-open", :any, default: nil, doc: "Handler for alert dialog open event" + attr :"on-close", :any, default: nil, doc: "Handler for alert dialog close event" + attr :class, :string, default: nil + attr :rest, :global + slot :inner_block, required: true + + def alert_dialog(assigns) do + # Collect event mappings + event_map = + %{} + |> add_event_mapping(assigns, "opened", :"on-open") + |> add_event_mapping(assigns, "closed", :"on-close") + + assigns = + assigns + |> assign(:event_map, json(event_map)) + |> assign(:initial_state, if(assigns.open, do: "open", else: "closed")) + |> assign( + :options, + json(%{ + animations: get_animation_config() + }) + ) + + ~H""" +
+ {render_slot(@inner_block)} +
+ """ + end + + @doc """ + The trigger element that opens the alert dialog. + """ + attr :class, :string, default: nil + attr :as, :any, default: "div" + slot :inner_block, required: true + attr :rest, :global + + def alert_dialog_trigger(assigns) do + ~H""" + <.dynamic data-part="trigger" data-action="open" tag={@as} class={classes(["", @class])} {@rest}> + {render_slot(@inner_block)} + + """ + end + + @doc """ + The content container of the alert dialog. + """ + attr :class, :string, default: nil + slot :inner_block, required: true + attr :rest, :global + + def alert_dialog_content(assigns) do + ~H""" +