Giter Site home page Giter Site logo

elemental_components's People

Contributors

gotbadger avatar imathis avatar jensljungblad avatar msalzburg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

elemental_components's Issues

Discussion about how to write components

This issue outlines some problems about how we currently define our components. It also compares this to how it is usually done in React.

Desired output:

<div class="card card--success">
  <div class="card__header card__header--large">
    Title of card
  </div>
  <div class="card__section">
    <h5 class="card__title">Title of section</h5>
    <p class="card__text">Lorem</p>
  </div>
  <div class="card__section card__section--large">
    <p class="card__text">Lorem</p>
  </div>
  <div class="card__section">
    <p class="card__text">Lorem</p>
  </div>
  <div class="card__footer">
    Footer of card
  </div>
</div>

Common solution in React:

<Card color="success">
  <CardHeader size="large">Title of card</CardHeader>
  <CardSection>
    <CardTitle>Title of section</CardTitle>
    <CardText>Lorem</CardText>
  </CardSection>
  <CardSection size="large">

  </CardSection>
  ...
</Card>

Number of components: 5, of which 4 "private".

Problems (also occurs when you don't use components):

  • The four private components are not usable outside of the Card component.
  • The private components can be placed in any order, Footer above Header etc.

Our current solution:

class CardComponent
  attribute :color, Components::Types::Enum
  attribute :header, Components::Types::String
  attribute :sections, Components::Types::Array
  attribute :footer, Components::Types::String
end
<%= component :card, color: :success do |attrs| %>
  <% attrs[:header] = capture do %>
    Title of card
  <% end %>

  <% attrs[:sections] << capture do %>
    <h5 class="card__title">Title of section</h5>
    <p class="card__text">Lorem</p>
  <% end %>

  <% attrs[:sections] << capture do %>
    <p class="card__text">Lorem</p>
  <% end %>

  <% attrs[:footer] = capture do %>
    Footer of card
  <% end %>
<% end %>
<div class="card card--<%= color %>">
  <div class="card__header">
    <%= header %>
  </div>
</div>

Our current problems:

  • We can't pass props to our subcomponents, card__header, card__section etc. In fact, these are typed as strings.
  • We lack an abstraction around the private subcomponents card__title, card__text

Eliminating the need for crazy rendering

I think the only reason for the delegation craziness when rendering is because of helpers. Attributes and elements could have their values passed to the template as locals, no need for them to be functions. Only helpers need that. But if helpers are getters only, and calculated at render time, there is no need for them to be passed as functions either.

Without thinking further, either there could be a DSL, or perhaps even simpler:

class FooComponent < Component
  attribute :id
  attribute :data

  has_one :header
  has_one :footer

  def css_classes
    ...
  end

  protected

  def locals
    super.merge(
      css_classes: css_classes
    )
  end
end

Style guide example context

Currently the whole style guide page, including examples are run in an isolated engine, using the Components::PagesController. In order for the examples to be able to access the application's helpers and routes, it does the following:

https://github.com/varvet/components/blob/4b952766562a075fc776c7f40d13ae90d9c8f029/app/controllers/components/pages_controller.rb#L1-L6

It would probably be better if the actual example (more specifically, the contents of the iframe) could be executed in the main application's context. That would mean that the component would also have to be rendered within the iframe, instead of being injected into it. Sounds complicated...

It also causes some kind of issue with the layout, forcing us to specify the layout explicitly in the very same PagesController.

Attributes do not override existing view methods

Attributes and elements should probably override existing methods on the view, if we choose to mix them in (delegate) as we do now. There are a lot of methods on the view, so there are a lot of "reserved" attribute names now. Normal helpers do override existing methods, so it seems reasonable.

Webpacker support?

I see you've made some updates recently! That's awesome!

Unfortunately the project I was on got scrapped so even though we heavily used Elemental and ViewComponent, we're not using it anymore.

I have another project I wanted to play with, but wanted to start with a fresh Rails 6 install which uses Webpacker by default.

Have you gotten it working with Webpacker instead of Sprockets?

Strategies for rendering block content

Currently the content of the block of a component or element is rendered using to_s, like this:

<div class="alert">
  <%= alert %>
</div>

<%= component "alert" do %>
  Hello
<% end %>

Upsides:

  • Short syntax
  • No need to name a variable
  • Will never clash with anything user defined

Downsides:

  • Would need a method for verifying block was given, for validation
  • ?

Alternative 1: Introduce named variable for the captured block

Something like block, or content.

Upsides:

  • Works the same as with other attributes

Downsides:

  • More verbose
  • We need to name the variable
  • Will potentially clash with user defined attributes and methods

Alternative 2: Leverage content_for

Rails has a content_for (and a content_for?) helper that perhaps could be leveraged:

<div class="alert">
  <%= content_for(alert) %>
</div>

<%= component "alert" do %>
  Hello
<% end %>
class Element
  def initialize
    
    @view.content_for(self, &block)
  end
end

Upsides:

  • Familiar syntax
  • No need to name a variable
  • Will never clash with anything user defined

Downsides:

  • Not tested, not sure if it works, or what problems it would bring

Alternative 3: Connect the block to an attribute

This is something I tried out but later discarded because it felt like I was over-engineering things, and muddied the concepts. Basically the user could choose an attribute name for the block to be captured to, and then you could either populate it like an attribute or by passing a block. Something like:

<div class="alert">
  <%= alert.message %>
</div>

<%= component "alert" do %>
  Hello
<% end %>
class AlertComponent < Components::Component
  attribute :message

  capture_block_to :message
end

Upsides:

  • No need to name a variable
  • Will never clash with anything user defined
  • Works the same in templates as with other attributes

Downsides

  • Perhaps blurs concepts together, and perhaps makes the API more difficult to understand at glance

Support any number of element levels, including no elements

This is a suggestion for a slightly different API that would allow 1) capture HTML even when you have no elements and b) support any number of nested elements.

Scenario 1: No elements

<%= component "card" do %>
  foo
<% end %>
component.value == "foo"

Scenario 2: Single element

<%= component "card" do |c| %>
  <% c.header "foo" %>
<% end %>
component.value == ""
component.header.value == "foo"

Scenario 3: Nested element

<%= component "card" do |c| %>
  <% c.header do |cc| %>
    <% cc.left "foo" %>
  <% end %>
<% end %>
component.value == ""
component.header.value == ""
component.header.left.value == "foo"

Scenario 4: Nested element + element value

<%= component "card" do |c| %>
  <% c.header do |cc| %>
    <% cc.left "foo" %>
    bar
  <% end %>
<% end %>
component.value == ""
component.header.value == "bar"
component.header.left.value == "foo"

Treat value as an attribute

The element (and component) value might as well be treated as an attribute:

class Element
  include Attributes
  include Elements

  attribute :value
end

This way the value method becomes less magical. This would also mean we don't have to check value and block args and switch the position of value and attributes. The block should simply always override the value, if set as an argument. It also makes it possible to pass a value to the component itself, which is not possible right now.

<% # sets value to foo %>
<%= component value: "foo" %>

<% # sets value to foo, then sets it again to bar %>
<%= component value: "foo" do %>
  bar
<% end %>

Should also document better that <%= element.value %> is the same as <%= element %>.

Gem name and release on rubygems

Since the name "components" is already used/blocked on rubygems we should probably find a new name, rename the project/files and work towards an offical release on rubygems?

@jensljungblad do you have any names on your mind already?

Support I18n locales for components

It would be nice to store component specific locales in the components directory (with
the components CSS and JS files) and enhance I18ns lazy lookup to support a "components" namespace.

Extending the i18n load path can be quite easy:

# config/application.rb

config.i18n.load_path += Dir[Rails.root.join('app', 'components', '**', '*.yml')]

I have not yet dug up how to define a "components" namespace for the lazy lookup.
Probably it would be nice to have a hierarchy like this:

# components/alert/alert_en.yml

en:
  components:
    alert:
       title: 'foo'

Using components without the asset pipeline

I tried giving this (seemingly very nice) gem a spin but the component generation command failed since we don't use the asset pipeline (anymore, go webpack!). Are there any other parts of components that are dependent on the asset pipeline or is it just the install command?

In case you're wondering, this is where it failed: https://github.com/jensljungblad/components/blob/master/lib/components/engine.rb#L6

As a side note: what are your plans for this project? Is it considered "finished" and will mostly just receive minimal maintenance, or do you have any immediate plans for new features/more compatibility etc.?

Other than that it looks well-made! :)

Open to Pull Requests?

Hi there!

My company is looking at potentially using components for a large project and we were wondering if you would be open to Pull Requests from us and if you had the time to maintain the project.

If so, then we'll start sending them your way as soon as we find things we'd like to add/change.

If not, would you be willing to add some of our developers as maintainers? Or would you prefer if we just did a fork?

We're still debating what framework we will use, but I wanted to start the conversation now so we have some knowledge ahead of time.

Thanks so much!

Element initialization via components attributes Hash is not working

The README describes that elements can also be initialized via the components attributes Hash.
Seems like this feature is not working properly (or missing?).

see README

Another good use case is a navigation component:

# app/components/navigation_component.rb %>

class NavigationComponent < ElementalComponents::Component
  element :items, multiple: true do
    attribute :label
    attribute :url
    attribute :active, default: false
  end
end
An alternative here is to pass a data structure to the component as an attribute, if no HTML needs to be injected when rendering the component:

<%= component "navigation", items: items %>

I already prepared a PR to make it work - or am I overseening sth. here?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.