Giter Site home page Giter Site logo

beautiful-skill-tree's Introduction

🌲🌲🌲 Beautiful Skill Tree

A small library to help get you implement beautiful, responsive, and satisfying skill trees into your React applications.

For every star Beautiful Skill Tree gets, £1 will be donated to Trees for Life. As cool as it is creating beautiful trees in your apps, it's even cooler doing so in real life!

Beautiful Skill Tree has currently raised £220 (+ Gift Aid) thanks to the lovely folk starring this repo.

Tested across devices using Browserstack, thanks to their continued support for open source projects.

browserstack logo

This package uses tsdx and np develop, build, package, and publish beautiful-skill-tree. I can't recommend either of them enough and they both make for an excellent TypeScript/JavaScript developer experience.


Sponsor

Learn to build a component library using minimal tech with Component Odyssey. As a result, you'll:

  • Become a more future-proof web developer
  • Build components that your users will love
  • Boost your career opportunities
  • Learn to do more with less

Table of Contents

  1. Examples
  2. Getting started
  3. Motivation
  4. API
  5. Features
  6. Contributing

Examples

Getting started

yarn add beautiful-skill-tree

The package exposes three components SkillTree, SkillTreeGroup and SkillProvider.

The SkillTree takes your data and renders the tree.

The SkillTreeGroup groups skill trees and exposes various methods and properties related to the collection of skill tree.

The SkillProvider is the skill tree's context provider.

For those that like their data typed, you can also import SkillType, SkillGroupDataType, SkillThemeType and SavedDataType from the package.

Wrap your application like this:

import {
  SkillTreeGroup,
  SkillTree,
  SkillProvider,
  SkillType,
  SkillGroupDataType,
} from 'beautiful-skill-tree';

const data: SkillType[] = [];

<SkillProvider>
  <SkillTreeGroup>
    {({ skillCount }: SkillGroupDataType) => (
      <SkillTree
        treeId="first-tree"
        title="Skill Tree"
        data={data}
        collapsible
        description="My first skill tree"
      />
    )}
  </SkillTreeGroup>
</SkillProvider>;

Or, if you are coding in ES6, here's the code:

import {
  SkillTreeGroup,
  SkillTree,
  SkillProvider,
  SkillType,
  SkillGroupDataType
} from 'beautiful-skill-tree';


const data = [];

<SkillProvider>
  <SkillTreeGroup>
    {({ skillCount }) => (
      <SkillTree
        treeId="first-tree"
        title="Skill Tree"
        data={data}
        collapsible
        description="My first skill tree"
      />
    })
    </SkillTreeGroup>
</SkillProvider>

Run your application's starting script and access localhost to find an empty skill tree. The skill tree will remain empty until data of type Skill[] is passed to through as a prop.

Optional SkillTree props include collapsible, disabled and description. collapsible is a boolean that detemrines whether or not the skill tree can collapse when the header is clicked. disabled gives programmatic control over whether a skill tree can be opened or not. The description prop adds a tooltip to the SkillTree header that displays on hover/touch.

Add the following data to your skill tree and see what happens:

const data: SkillType[] = [
  {
    id: 'hello-world',
    title: 'Hello World',
    tooltip: {
      content:
        'This node is the top most level, and will be unlocked, and ready to be clicked.',
    },
    children: [
      {
        id: 'hello-sun',
        title: 'Hello Sun',
        tooltip: {
          content:
            'This is a parent of the top node, and will locked while the parent isn’t in a selected state.',
        },
        children: [],
      },
      {
        id: 'hello-stars',
        title: 'Hello Stars',
        tooltip: {
          content:
            'This is the child of ‘Hello World and the sibling of ‘Hello Sun’. Notice how the app takes care of the layout automatically? That’s why this is called Beautiful Skill Tree and not just ‘Skill Tree’. (Also the npm namespace had already been taken for the latter so (flick hair emoji).',
        },
        children: [],
      },
    ],
  },
];

Go to your browser and you should see this:

Skill Tree Demo


Motivation

Is there anything more satisfying than the feeling of progression; improving at something you care deeply about? Not likely! Be it in video games, web development, or your physical capabilities, very little gives us a sense of pride and accomplishment than gaining new skills and using them. My motivation was to make skill trees that feel satisfying and fun to use.

Unfortunately there aren't any React packages that enable us developers to easily create skill trees in their applications. This is where Beautiful Skill Tree comes in. BST is a small package that allows you to easily create your own skill trees that look great across devices and screen sizes.


API

SkillTree

treeId: string [required]

title: string [required]

data: SkillType [required]

collapsible: boolean [optional]

closedByDefault boolean [optional]

disabled boolean [optional]

description: string [optional]

savedData: SavedDataType [optional]

handleSave: (context: ContextStorage, treeId: string, skills: SkillType) => void [optional]

handleNodeSelect: (event: NodeSelectEvent) => void [optional]

SkillTreeGroup

theme: SkillThemeType [optional]

children: (treeData: SkillGroupDataType) => React.ReactNode [required]

SkillProvider

SkillType

type SkillType[] = {
  id: string;
  title: string;
  optional?: boolean;
  tooltip: {
    content: React.ReactNode;
    direction?: 'top' | 'left' | 'right' | 'bottom', // top = default
  };
  icon?: string;
  children: SkillType[];
}

SkillGroupDataType

type SkillGroupData = {
  skillCount: SkillCount;
  selectedSkillCount: SkillCount;
  resetSkills: () => void;
  handleFilter: (query: string) => void;
};

type SkillCount = {
  optional: number;
  required: number;
};

SavedDataType

type SavedDataType = {
  [key: string]: {
    optional: boolean;
    nodeState: 'selected' | 'unlocked' | 'locked';
  };
};

NodeSelectEvent

type NodeSelectEvent = {
  key: string;
  state: 'selected' | 'unlocked' | 'locked';
};

Features

Filtering

The <SkillTreeGroup /> component exposes the handleFilter() method which can be used to close any trees that don't contain skills that match the query. This can be used in conjunction with your own input component like so:

<input
  style={{ height: '32px' }}
  onChange={e => handleFilter(e.target.value)}
  placeholder="Filter through trees..."
/>

The closedByDefault prop can also be passed through to the skill tree to ensure that the tree isn't open by default.

Custom Themes

It's likely that you're application won't look to hot with a dark blue/rainbow themed skill tree. Fortunately, a custom theme can be supplied to the SkillTreeGroup component. The styles passed through will override the defaults to allow your skill tree to fit nicely into your application. The theme object's type is exported in the package as SkillThemeType. I don't perform any object merging between the default styles and the user-defined object, so you'll need to fill out the whole object.

There are some gotcha related to some of my hacky CSS. Because I like me some gradients, to get the borders looking all swanky, i've had to use the border-image css property to define the border color. This means that you'll need to supply a gradient too if you want to change the border color. To create a solid gradient, pass through:

linear-gradient(
  to right,
  #ffffff 0%,
  #ffffff 100%
)

URL Navigation

As each <SkillTree /> should have a unique treeId, beautiful-skill-tree adds this value to a DOM node surrounding your tree as an id attribute. This means you can navigate to your trees via an anchor tag. For an app that has two skill trees with ids of treeOne and treeTwo respectively, you can create your own navigation like so:

<nav>
  <ul>
    <li>
      <a href="#treeOne">Tree One</a>
    </li>
    <li>
      <a href="#treeTwo">TreeTwo</a>
    </li>
  </ul>
</nav>

Custom Saving

beautiful-skill-tree automatically handles saving out of the box, but the implementation is fairly rudimental. The package saves the skills tree data to local storage when the application loads, which is great for:

  • Creating simple skill trees
  • Web apps that require no authentication
  • Rapid prototyping

Saving to local storage is not great for:

  • Data persistence across devices
  • Web apps that require authentication
  • Giving control to power users

Saving and loading works automatically, but it's possible pass in your own implementation, should you want to extend the save/loading capabilities, or if your application utilises authentication. The SkillTree component takes 2 optional properties that pertain solely to saving: savedData and handleSave. The former is an object with the shape of SavedDataType that sets the current state of the skill tree on load, while the handleSave function is an event handler that fires on save, and takes a Storage object, treeId, and skills.

// the state of the skill tree, as per my custom implementation
const savedData: SavedDataType = {
  'item-one': {
    optional: false,
    nodeState: 'unlocked',
  },
  'item-two': {
    optional: false,
    nodeState: 'locked',
  },
};

function handleSave(
  storage: ContextStorage,
  treeId: string,
  skills: SavedDataType
) {
  return storage.setItem(`skills-${treeId}`, JSON.stringify(skills));
}

const App = () => {
  return (
    <SkillProvider>
      <SkillTreeGroup theme={{ headingFont: 'impact' }}>
        {() => {
          return (
            <SkillTree
              treeId="treeOne"
              title="Save Example"
              data={exampleData} // defined elsewhere
              handleSave={handleSave}
              savedData={savedData}
            />
          );
        }}
      </SkillTreeGroup>
    </SkillProvider>
  );
};

Keyboard only use

The tree is currently fully navigable using the keyboard. Pressing the tab button will cycle through the nodes, while pressing enter will select the focused node.


Contributing

contributing guidelines

Running locally

You'll need to clone the repo on your local machine and install the dependencies using yarn. Once the dependencies have been install start the local server. You'll also need to be using Node 10 or above.

If you're using nvm, you can ran nvm use to automatically use the version of Node specified in the .nvmrc file.

git clone https://github.com/andrico1234/beautiful-skill-tree.git

cd ./beautiful-skill-tree

yarn:test // optional but useful as a sanity check

yarn

yarn start

If you're having issues with any of the steps above, then please open a ticket with any error logging the console outputs. If your local server is working without any issues then open up a new terminal window in the same directory and start the local example. Running the example will spin up a demo app on localhost:1234 which I use as a playground to display bst's feature set.

cd ./example

yarn

yarn start

access localhost:1234 in your browser.

Contributors

beautiful-skill-tree's People

Contributors

andrico1234 avatar dependabot[bot] avatar mikkelking 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  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  avatar  avatar  avatar  avatar

beautiful-skill-tree's Issues

Style improvements

  • Add border shadow to the description tooltip to correctly convey elevation
  • Replace the header focus outline colour with a custom one.

Semantic error TS7016: Could not find a declaration file for module

(typescript) Error: C:/xampp/htdocs/dev/beautiful-skill-tree-master/src/componen
ts/SkillNode.tsx(9,32): semantic error TS7016: Could not find a declaration file
for module '../../'. 'C:/xampp/htdocs/dev/beautiful-skill-tree-master/dist/inde
x.js' implicitly has an 'any' type.

fix: on tsconfig.json

"noImplicitAny": false,

Collapsable trees

I'm stumped with this, when I collapse a skill tree using an accordion then the edges don't render into their correct places.

I've tried creating an accordions context that triggers the calculatePosition() function, but that doesn't seem to work. When the screen is manually resized, everything appears as normal.

As I write this I wonder if this is because the bottom node and top node positions are calculated in two different components, and one isn't calculated correctly... This isn't critical, and I've spent a lot of time on it so far.

Cascade deselection of prerequisite nodes

Whenever a prerequisite node is de-selected (current state set to anything other than 'selected' - usually 'unlocked'), its children still remain selected. This can be confusing because that node can then be unselected but cannot be selected again unless its prerequisite nodes are selected again.

If accepted, this issue would implement cascading behavior to the deselection of nodes. That is, it would be invalid for a node to be selected and its prerequisite node(s) not to be selected.

  • Add tests to verify cascading deselection is enforced
  • Change behavior in SkillNode to update the current state to not be 'selected' (and to be 'locked') if the current state of its parent is updated to no longer be 'selected'

Expose states to user

In preparation of giving the calisthenics-skill-tree authentication and extending the app's behaviour, it makes sense to delegate the saving and loading of the skills data to the users.

Things to consider:

  • performance degradation, we may need to hoist the skill data to the top level App context, to expose the data to the user (which could, or could not, have catastrophic consequences for subscribers of the context lower down in the DOM).
  • the shape of the data exposed to the user, which may or may not be identical to how it's shaped now. i.e.
[{
  [skillKey]: {
    nodeState: NodeState,
    optional: boolean,
  }
}, {...}]
  • how do we load the data back into the skill tree? this could lead to the package requiring more overhead to implement a simple skill tree, which goes against the philosophy of the package. A simple solution to this is giving the option to store data in localStorage to the application. Otherwise giving more complicated applications the ability to pass current state.

css theming

After skill-tree code has been migrated over to its own package. Convert the css code into something more appropriate e.g. style-components or glamourous

  • convert css styles to styled-components
  • add a theme provider to the top fo the skill tree
  • convert static styles into customizable ones

Global:

  • border
  • border-radius

SkillTreeGroup:

  • background-color
  • font-family
  • color
  • margin

SkillNode:

  • background-color (unlocked/locked)
  • background-color (selected)
  • hover border-color
  • width (icon node)
  • font-weight (text node)
  • height (text node)
  • width (text node)
  • font-size

Skill edge:

  • active-color
  • border-color

BUG: Hidden skills are still tabbable

Prevent this from happening.

If the skill is not displayed remove the tabIndex value?

Are there performance implications to consider if we were to remove the elements form the dom and recreate them when the skill tree is toggled between visible/hidden

Focus states that don't look like booty

Currently the focus state is the same as the hover state, which is fine when tabbing across skills. The issue lies when a selected skill is unselected, and the user moves their mouse off of the de-selected node. It still remains in a 'hover-like' state, which creates an incongruity between the application's behaviour and the user's expectation.

Add skills leveling

Allow a Skill / SkillTree to have level (like Diablo 2) and allowing to gate the next skill "Tier" (like Borderlands) with a value.

Even if I dont need this feature for my actual work, I thought it would be a nice enhancement to the actual awesome lib of your. If I find some time, I might look into it.

Refactor lines/edges

I ended up making a bit of a mess in the SkillEdge, Line, AngledLine components. I want to neaten these up so they're tight and the API is consistent between both Line and AngledLine components.

Skilltree

Hi Andrico,

I am a part-time physics teacher in a secondary school (kid ages 13 - 16) and part-time freelance developer of web applications. I really like this library, so much that I was working on an application using this lib. Now I am using this application in my school as experiment. It is working very well so I am thinking about ways to earn coin.

https://skilltree-b6bba.firebaseapp.com/

I wanted to share the link to this application with you because your library was the main inspiration and made it possible to make it. Also I found some smaller and bigger issues with the library:

Node title class
Can you provide a way to add a class to the node title? Right now I have managed to style it by diving into the source code, but the CSS library Bulma has default override for h1 styling.

Theming options
Node desktop width: if you set this larger (or smaller) it will cause the lines to split or overlap.
Schermafbeelding 2020-02-16 om 11 29 15

Heading font: The number of skills in the heading of the skill tree, seem to be set by the font selected as Node Font and not Heading Font. As you can see in the screenshot below, I have set the heading font to Roboto and the node font to Raleway. The skill count is displayed in Raleway font.
Schermafbeelding 2020-02-16 om 11 33 21

Heading font color and size: Changing these values does not have any effect. I am able to set the heading font color by changing the node font color.

Saving the completed state

It would be nice if in the handleSave(
storage: ContextStorage,
treeId: string,
skills: SavedDataType,
) method you also provide access to the number of completed skills and the total number of skills (for all skilltrees in the group) as you are giving them already in the Skilltree Group.
These data are very helpful when trying to update progress bars for the students. Right now I have implemented this with getting textContent from the skilltree group.

Mobile don't save states on window unload

Due to how mobile browsers work, the state of the tree only saves when the page gets refreshed. When the tab changes, app closes or the user shuts down the browser then the save state doesn't change. Implement an event listener that checks that utilises the Page visibility API to save state when the user is no longer viewing the page.

Write to localstorage onunload event

Writing an object to localstorage is an expensive operation and can result in a bottleneck on performance as we're writing to the store on every click.

Research into whether or not using 'beforeUnload' is a good solution to only write to local storage when the window goes to close. The state of the application remains in-memory in the mean time.

The event listener will added to the SkillProvider component.

https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload

Please perform some performance testing before and after.

Connect to children multiple parents

Hi,
great lib. I really enjoy the animations.
Is there a way to connect a child to two parents in the above tree? For example:
You have to achieve these two parents challenges to unlock the one child Kinda like this:
Skill-tree-1212x682

Reset Skill Tree

This might be a good challenging item for someone with little React knowledge to work on.

We want a button that sets everything to 0. It will require familiarity with the context api, so do let me know if you want to pick this up and we can run through things.

Replacing the individual context provider with a single one could work. I initially went against this as it came at a performance cost, but this might have some clues as to how we can have a single provider and no unnecessary rerenders

https://frontarm.com/james-k-nelson/react-context-performance/

Create a Gatsby example project

BST is used with calisthenicsskills, which is built using Gatsby.

SInce gatsby compiles sites to static on the server, it's important to not reference any globals like navigator or window directly in code that gets compiled on the server. Instead any references need to be contained within a componentDidMount(), or useEffect(), so that the global gets referenced at runtime.

It's difficult testing this with a separate site, so having a gatsby example site on the repo for the sole purpose of testing running/compiling with the local version of BST would be incredibly useful.

Remove Lodash dependency

Create 3 functions so we can remove lodash as a dependency

  • mapValues
  • isEmpty
  • throttle

these don't need to be comprehensive or all encompasing, i'm using the default settings for all of them.

ensure that each function is tested.

Make the example site looks presentable

A sorely neglected part of the repo

This is a good opportunity to showcase some of the package's top features in clear detail.

A skill tree that shows of a variety of feature sets

Include features like:
Small trees
Larger trees
Custom styling
Tooltip Content
Descriptions
Collapsible/Non-collapsible trees
Filtering
Resetting data
Saving
Optional Nodes
Utilising the skill count/selected skill count information

And to make it all around presentable

I'd love to have a GitHub page where the example site is hosted

All of these don't need to be created over a single MR, but can be done as part of a much larger project.

I would love to have someone own this.

A great first issue for anyone wanting to become more familiar with the codebase.

link lines misplaced with parent padding

It seams that the position of the lines between two nodes goes off the chart when any upper div uses padding or css library like Bootstrap (tested with v4). It seams that the css of those line use position: absolute which might be the problem with computed values...

In any case, keep up the good work !

Keyboard only use

Can currently tab to navigate the skill tree, but cannot use the enter button to select a node.
This needs to be added.

Bonus: Keypad navigable. Pressing up moves to the parent, pressing right/left moves to the siblings. Pressing down goes to the left most sibling

Styling optional nodes

I'd like to open the discussion about how optional nodes should appear when they are:

  • locked
  • unlocked
  • selected

The appearance of the locked state may not need to change, but 'unlocked' should have a smaller degree of affordance to a required Node, to signify that it's not necessary to progress.

The appearance of the selected state should be so that in terms of the information hierarchy, it's less important than a required Node.

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.