{"id":24901764,"url":"https://github.com/goose97/orange","last_synced_at":"2025-04-14T04:23:47.740Z","repository":{"id":227870674,"uuid":"772592209","full_name":"Goose97/orange","owner":"Goose97","description":null,"archived":false,"fork":false,"pushed_at":"2025-04-11T14:28:08.000Z","size":700,"stargazers_count":2,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-11T15:42:01.147Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Goose97.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-03-15T13:55:13.000Z","updated_at":"2025-04-11T14:27:51.000Z","dependencies_parsed_at":"2024-03-21T01:30:07.930Z","dependency_job_id":"954c423e-e617-4791-985e-3fdf96344cda","html_url":"https://github.com/Goose97/orange","commit_stats":null,"previous_names":["goose97/orange"],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Goose97%2Forange","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Goose97%2Forange/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Goose97%2Forange/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Goose97%2Forange/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Goose97","download_url":"https://codeload.github.com/Goose97/orange/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248819991,"owners_count":21166578,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-02-01T21:17:41.897Z","updated_at":"2025-04-14T04:23:47.707Z","avatar_url":"https://github.com/Goose97.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Package](https://img.shields.io/badge/-Package-important)](https://hex.pm/packages/orange) [![Documentation](https://img.shields.io/badge/-Documentation-blueviolet)](https://hexdocs.pm/orange)\n\nOrange is a framework to build TUI (terminal UI) applications in Elixir. Its high-level features are:\n\n- A DSL to describe UI component. The syntax is inspired by React. For example, an snippet like this:\n\n  ```elixir\n  rect style: [border: true, padding: {0, 1}, height: 10, width: 20] do\n    rect style: [color: :red] do\n      \"Hello\"\n    end\n\n    rect do\n      \"World\"\n    end\n  end\n  ```\n\n  will render this:\n\n  ![rendered result](https://github.com/Goose97/orange/blob/main/.github/assets/example_syntax.png)\n\n- Support handling terminal events: currently, only keyboard events are supported.\n\n- Support custom components: you can create component from builtin primitives. Custom components can encapsulate state and logic.\n\n- A collection of UI components: Input, VerticalScrollRect, ...\n\n## Important\n\nWhen using Orange, it is essential that you prevent the Erlang VM from reading stdin as it can interfere with the terminal events handling logic. You can achieve this via the `-noinput` flag:\n\n```sh\nelixir --erl \"-noinput\" -S mix run --no-halt\n```\n\n## Examples\n\nFirst, we need to create a root component:\n\n```elixir\ndefmodule Counter.App do\n  @behaviour Orange.Component\n\n  import Orange.Macro\n\n  @impl true\n  # Each component can have an internal state\n  # Also, a component can subscribe to receive terminal events\n  def init(_attrs), do: %{state: %{count: 0}, events_subscription: true}\n\n  @impl true\n  def handle_event(event, state, _attrs) do\n    case event do\n      # Arrow up to increase counter\n      %Orange.Terminal.KeyEvent{code: :up} -\u003e\n        {:update, %{state | count: state.count + 1}}\n\n      # Arrow down to decrease counter\n      %Orange.Terminal.KeyEvent{code: :down} -\u003e\n        {:update, %{state | count: state.count - 1}}\n\n      %Orange.Terminal.KeyEvent{code: {:char, \"q\"}} -\u003e\n        # Quit the application\n        Orange.stop()\n        :noop\n\n      _ -\u003e\n        :noop\n    end\n  end\n\n  @impl true\n  def render(state, _attrs, _update) do\n    rect style: [border: true, padding: 1] do\n      \"Counter: #{state.count}\"\n    end\n  end\nend\n```\n\nThen start the application:\n\n```elixir\nOrange.start(Counter.App)\n```\n\nFor more examples, see [here](/examples).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoose97%2Forange","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgoose97%2Forange","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoose97%2Forange/lists"}